package com.idega.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import javax.ejb.CreateException;
import javax.ejb.EJBLocalObject;
import javax.ejb.FinderException;

import com.idega.util.CoreUtil;
import com.idega.util.ListUtil;
import com.idega.util.StringUtil;

/**
 * Title:        idegaclasses
 * Description:
 * Copyright:    Copyright (c) 2001-2002
 *  Company:      idega
 * @author <a href="tryggvi@idega.is">Tryggvi Larusson</a>
 * @version 1.0
 */

public abstract class IDOFactory implements IDOHome,java.io.Serializable{

	private static final long serialVersionUID = 463763865403762367L;

	protected String dataSource = GenericEntity.DEFAULT_DATASOURCE;

  protected IDOFactory(){
  }

  @Override
  public String getDatasource() {
	  return ((GenericEntity)this.idoCheckOutPooledEntity()).getDatasource();
  }

  @Override
  public void setDatasource(String dataSource) {
	  setDatasource(dataSource, true);
  }

  @Override
  public void setDatasource(String dataSource, boolean reloadEntity) {
	  if (dataSource != null) {
		 this.dataSource = dataSource;
		 GenericEntity ent = ((GenericEntity) this.idoCheckOutPooledEntity());
		 ent.setDatasource(dataSource, reloadEntity);
		 idoCheckInPooledEntity(ent);
	  }
  }

  public <T extends IDOEntity> T idoCreate(Class<T> entityInterfaceClass) throws CreateException {
    try{
      T entity = null;
      try{
        entity = IDOContainer.getInstance().createEntity(entityInterfaceClass);
      }
      catch(Error e){
        System.err.println("Error creating bean for : "+this.getClass().getName());
        e.printStackTrace();
      }
      ((IDOEntityBean) entity).setEJBLocalHome(this);
	  entity.setDatasource(this.dataSource);
      return entity;
      //return (IDOEntity)beanClass.newInstance();
    }
    catch(Exception e){
      e.printStackTrace();
      throw new javax.ejb.CreateException(e.getMessage());
    }
  }

  public <T extends IDOEntity> T idoFindByPrimaryKey(Class<T> entityInterfaceClass,int id)throws javax.ejb.FinderException{
    return this.idoFindByPrimaryKey(entityInterfaceClass,new Integer(id));
  }

  public <T extends IDOEntity> T idoFindByPrimaryKey(Class<T> entityInterfaceClass,Integer id)throws javax.ejb.FinderException{
    return idoFindByPrimaryKey(entityInterfaceClass,(Object)id);
  }

  public <T extends IDOEntity> T idoFindByPrimaryKey(Class<T> entityInterfaceClass,Object pk)throws javax.ejb.FinderException{
      T theReturn = IDOContainer.getInstance().findByPrimaryKey(entityInterfaceClass,pk,this, this.dataSource);
      return theReturn;
  }

  @Override
  public <T extends IDOEntity> T createIDO() throws CreateException {
	  Class<T> interfaceClass = getEntityInterfaceClass();
	  return idoCreate(interfaceClass);
  }

  public <T extends IDOEntity> T createEntity() throws CreateException {
	  Class<T> interfaceClass = getEntityInterfaceClass();
	  return idoCreate(interfaceClass);
  }

  /**
   * @deprecated use {@link IDOFactory#createIDO()}
   */
  @Deprecated
  public <T extends IDOEntity> T idoCreate() throws CreateException{
    return createIDO();
  }

  public <T extends IDOEntity> T idoFindByPrimaryKey(int primaryKey) throws FinderException {
	  Class<T> interfaceClass = getEntityInterfaceClass();
	  return idoFindByPrimaryKey(interfaceClass, primaryKey);
  }

  public <T extends IDOEntity> T idoFindByPrimaryKey(Integer primaryKey) throws FinderException {
	  Class<T> interfaceClass = getEntityInterfaceClass();
	  return idoFindByPrimaryKey(interfaceClass, primaryKey);
  }

  /**
   * @deprecated use {@link IDOFactory#findByPrimaryKeyIDO(Object)}
   */
  @Deprecated
public <T extends IDOEntity> T idoFindByPrimaryKey(Object primaryKey) throws FinderException{
    return findByPrimaryKeyIDO(primaryKey);
  }

  @Override
  public <T extends IDOEntity> T findByPrimaryKeyIDO(Object primaryKey) throws FinderException{
    Object realPK = primaryKey;
    if(primaryKey instanceof IDOEntity){
    	try{
    		throw new FinderException("Argument of type: "+primaryKey.getClass()+" should not be passed as a parameter to findByPrimaryKey(). This currently works but will be removed in future APIs. Please remove this usage !!!");
    	}
    	catch(FinderException fe){
    		fe.printStackTrace(System.err);
    	}
    	realPK = ((IDOEntity)primaryKey).getPrimaryKey();
    }
    Class<T> interfaceClass = getEntityInterfaceClass();
    return idoFindByPrimaryKey(interfaceClass, realPK);
  }

  
	/**
	 * 
	 * <p>Makes a search for entity with primary key in all hierarchy.</p>
	 * @param primaryKeys is {@link Collection} of 
	 * {@link EJBLocalObject#getPrimaryKey()}, not <code>null</code>;
	 * @return entities, extending this entity by given primary key or 
	 * entities of this type by primary key;
	 * @author <a href="mailto:martynas@idega.is">Martynas Stakė</a>
	 */
	public <T extends IDOEntity> List<T> findSubTypesByPrimaryKeysIDO(Collection<Object> primaryKeys) {
		if (ListUtil.isEmpty(primaryKeys)) {
			return Collections.emptyList();
		}

		ArrayList<T> entities = new ArrayList<T>(primaryKeys.size());
		for (Object primaryKey : primaryKeys) {
			T entity = findSubTypeByPrimaryKeyIDO(primaryKey);
			if (entity != null) {
				entities.add(entity);
			}
		}

		return entities;
	}

	/**
	 * 
	 * @param primaryKey is {@link IDOEntity#getPrimaryKey()} to search by,
	 * not <code>null</code>;
	 * @return entity itself or a sub-type which has given id or 
	 * <code>null</code> failure;
	 * @author <a href="mailto:martynas@idega.is">Martynas Stakė</a>
	 */
	public <T extends IDOEntity> T findSubTypeByPrimaryKeyIDO(Object primaryKey) {
		if (primaryKey == null || StringUtil.isEmpty(primaryKey.toString())) {
			return null;
		}

		Class<T> interfaceClass = getEntityInterfaceClass();
		if (interfaceClass == null) {
			return null;
		}

		/* Getting sub-types */
		Collection<Class<? extends T>> subTypes = CoreUtil.getSubTypesOf(
				interfaceClass, true);
		if (ListUtil.isEmpty(subTypes)) {
			/* Checking original entity */
			try {
				return findByPrimaryKeyIDO(primaryKey);
			} catch (FinderException e) {
				java.util.logging.Logger.getLogger(getClass().getName()).info(
						"Instances of " + interfaceClass.getSimpleName()
								+ " not found");
			}

			return null;
		}

		/* Getting homes */
		Set<? extends IDOHome> homes = getHomesForSubtypes(subTypes);
		if (ListUtil.isEmpty(homes)) {
			return null;
		}

		/* Searching for instance by primary keys in sub-types */
		for (IDOHome home : homes) {
			try {
				T entity = home.findByPrimaryKeyIDO(primaryKey);
				if (entity != null) {
					java.util.logging.Logger.getLogger(getClass().getName()).info(
							"Found subtype " + entity.getClass().getSimpleName()
									+ " by primary key: '" + primaryKey + "'");
					return entity;
				}
			} catch (FinderException e) {
				java.util.logging.Logger.getLogger(getClass().getName()).info(
						"No instances in " + home.getClass().getSimpleName()
								+ " by id: '" + primaryKey
								+ "' proceeding to next one! ");
			}
		}
		
		return null;
	}

	/**
	 * 
	 * @return {@link Set} of different {@link IDOHome}s or 
	 * {@link Collections#emptyList()} on failure;
	 * @author <a href="mailto:martynas@idega.is">Martynas Stakė</a>
	 */
	protected <T extends IDOEntity> Set<? extends IDOHome> getHomesForSubtypes() {
		Collection<Class<? extends IDOEntity>> subTypes = CoreUtil.getSubTypesOf(
				getEntityInterfaceClass(), true);
		if (!ListUtil.isEmpty(subTypes)) {
			return getHomesForSubtypes(subTypes);
		}

		return Collections.emptySet();
	}
	
	/**
	 * 
	 * @param subTypes is {@link IDOEntity}s to get {@link IDOHome}s for, 
	 * not <code>null</code>;
	 * @return {@link Set} of different {@link IDOHome}s or 
	 * {@link Collections#emptyList()} on failure;
	 * @author <a href="mailto:martynas@idega.is">Martynas Stakė</a>
	 */
	protected <T extends IDOEntity> Set<? extends IDOHome> getHomesForSubtypes(
			Collection<Class<? extends T>> subTypes) {
		if (ListUtil.isEmpty(subTypes)) {
			return Collections.emptySet();
		}

		Set<IDOHome> homes = new HashSet<IDOHome>();
		for (Class<? extends T> subType : subTypes) {
			IDOHome home = null;
			try {
				home = IDOLookup.getHome(subType);
			} catch (IDOLookupException e) {
				java.util.logging.Logger.getLogger(getClass().getName()).log(
						Level.WARNING, 
						"Failed to get home for " + subType.getSimpleName() + 
						" cause of: ", e);
			}

			if (home != null) {
				homes.add(home);
			}
		}

		return homes;
	}

  public IDOEntity findByPrimaryKeyIDO(int primaryKey) throws FinderException{
    return idoFindByPrimaryKey(getEntityInterfaceClass(),primaryKey);
  }

  @Override
public <T extends IDOEntity> Collection<T> findByPrimaryKeyCollection(Collection<?> p0) throws FinderException{
	com.idega.data.IDOEntity entity = this.idoCheckOutPooledEntity();
	Collection<?> ids = ((GenericEntity)entity).ejbFindByPrimaryKeyCollection(p0);
	idoCheckInPooledEntity(entity);
	return this.getEntityCollectionForPrimaryKeys(ids);
}

  @Override
public void remove(Object primaryKey){
    try{
      IDOEntity entity = findByPrimaryKeyIDO(primaryKey);
      entity.remove();
    }
    catch(Exception e){
      throw new javax.ejb.EJBException(e.getMessage());
    }
  }

  protected abstract <T extends IDOEntity> Class<T> getEntityInterfaceClass();

  protected <T extends IDOEntity> Class<T> getEntityBeanClass() {
	  Class<T> interfaceClass = getEntityInterfaceClass();
	  return IDOLookup.getBeanClassFor(interfaceClass);
  }

  /**
   *
   * @param setOfPrimaryKeys
   * @return Set of IDOEntity objects for this Factory
   * @throws FinderException
   */
  protected <T extends IDOEntity> Set<T> getEntitySetForPrimaryKeys(Set<?> setOfPrimaryKeys)throws FinderException{
    Set<T> theReturn = new HashSet<T>();
    for (Iterator<?> iter = setOfPrimaryKeys.iterator(); iter.hasNext();) {
      Object pk = iter.next();
      T entityObject = this.idoFindByPrimaryKey(pk);
      theReturn.add(entityObject);
    }
    return theReturn;
  }

  /**
   *
   * @param collectionOfPrimaryKeys
   * @return Collection of IDOEntity objects for this Factory
   * @throws FinderException
   */
  @Override
public <T extends IDOEntity> Collection<T> getEntityCollectionForPrimaryKeys(Collection<?> collectionOfPrimaryKeys) throws FinderException {
  	if (collectionOfPrimaryKeys instanceof IDOPrimaryKeyList) {
  		return getIDOEntityListForPrimaryKeys(collectionOfPrimaryKeys);
  	} else {
	  	Collection<T> theReturn = new ArrayList<T>();
	    if (collectionOfPrimaryKeys != null){
		    for (Object pk: collectionOfPrimaryKeys) {
		      if(pk instanceof IDOEntity){
		      	theReturn.add((T) pk);
		      } else {
			      T entityObject = this.findByPrimaryKeyIDO(pk);
			      theReturn.add(entityObject);
		      }
		    }
	    }
	    return theReturn;
  	}
  }

    /**
   *
   * @param collectionOfPrimaryKeys
   * @return Collection of IDOEntity objects for this Factory
   * @throws FinderException
   */
  private <T extends IDOEntity> Collection<T> getIDOEntityListForPrimaryKeys(Collection<?> collectionOfPrimaryKeys) throws FinderException{
    Collection<T> theReturn = new IDOEntityList<T>(collectionOfPrimaryKeys);
    return theReturn;
  }

  protected IDOEntity idoCheckOutPooledEntity(){
    /**
     * @todo: Change implementation
     */
	 GenericEntity ent = (GenericEntity) com.idega.data.GenericEntity.getStaticInstanceIDO(this.getEntityInterfaceClass(),this.dataSource);
	 ent.setDatasource(this.dataSource, false);
	 return ent;
  }


  protected void idoCheckInPooledEntity(IDOEntity entity){
    /**
     * @todo: implement
     */
  }

	@Override
	public Object decode(String pkString){
		IDOEntity theReturn = this.idoCheckOutPooledEntity();
		return theReturn.decode(pkString);
	}

	@Override
	public Collection<?> decode(String[] pkString){
		IDOEntity theReturn = this.idoCheckOutPooledEntity();
		return theReturn.decode(pkString);
	}

}
