package com.k_int.discover.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.k_int.aggregator.datamodel.AggregatorCollectionHDO;
import com.k_int.aggregator.dto.AggregatorCollectionDTO;
import com.k_int.discover.datamodel.dto.CultureGrid_BaseDTO;
import com.k_int.discover.datamodel.dto.CultureGrid_ItemDTO;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

/**
 * Determine the list of collections that a resource should be associated with
 * based on the default specified collection and any isPartOfs in the data
 *
 * @author rpb rich@k-int.com
 * @version 1.0 26.08.09
 */
public class CollectionParser {

	private static Log log = LogFactory.getLog(CollectionParser.class);

        public static Set<AggregatorCollectionHDO> getCollCodesAsHDO(Session sess, String defaultCollCode, CultureGrid_BaseDTO baseDTO) {
                Set<String> isPartOfs = null;
                
                if ( baseDTO instanceof CultureGrid_ItemDTO ) {
                    CultureGrid_ItemDTO itemDTO = (CultureGrid_ItemDTO)baseDTO;
                    isPartOfs = itemDTO.getIsPartOf();
                }
                
                List<AggregatorCollectionDTO> collDTOs = getCollCodes(sess, defaultCollCode, isPartOfs);
                
                // Loop through the return coll codes and set up the set of actual collections for use
                Set<AggregatorCollectionHDO> returnColls = new LinkedHashSet<AggregatorCollectionHDO>();
                for(AggregatorCollectionDTO aColl: collDTOs) {
                    AggregatorCollectionHDO matchedColl = AggregatorCollectionHDO.lookup(sess, aColl.getCollectionCode());
                    if ( matchedColl != null ) {
                        returnColls.add(matchedColl);
                    }
                }
                
		return returnColls;
	}
        
	public static List<AggregatorCollectionDTO> getCollCodes(Session sess, String defaultCollCode, Set<String> isPartOfs) {
		List<AggregatorCollectionDTO> returnValue = new ArrayList<AggregatorCollectionDTO>();
		
		log.debug("getCollCodes called with defaultCollCode: " + defaultCollCode);
		if ( isPartOfs == null ) 
			log.debug("isPartOfs is null!");
		else 
			log.debug("isPartOf != null and isPartOf.size = " + isPartOfs.size());

		// If we don't have any isPartOfs, but we do have a defaultCollCode then use the defaultCollCode 
		if ( isPartOfs == null || isPartOfs.size() == 0 ) {
			if ( defaultCollCode != null ) {
				AggregatorCollectionHDO defaultColl = AggregatorCollectionHDO.lookup(sess, defaultCollCode);
                                
                if ( defaultColl != null && defaultColl.getManagementInfo().getDeletedBy() == null ) {
                    // The collection exists and hasn't been deleted
    				returnValue.add(new AggregatorCollectionDTO(defaultColl));
                }
			} else {
				returnValue = null;
			}
		} else {
			// Go and get the default coll from the database
			AggregatorCollectionHDO defaultColl = AggregatorCollectionHDO.lookup(sess, defaultCollCode);
            if ( defaultColl != null && defaultColl.getManagementInfo().getDeletedBy() != null ) {
                // The default collection has been deleted - stop considering it
                defaultColl = null;
            }
            
            // TODO - is teh above consideration of deleted colls correct?
			
			// Loop through the isPartOfs and find the relevant collection codes in the datamodel
			Iterator<String> isPartOfIter = isPartOfs.iterator();
            boolean globalCollFound = false;
            String firstIsPartOf = null;
                        
			while(isPartOfIter.hasNext()) {
				String isPartOf = isPartOfIter.next();
                            
                // Remember the first is part of we encounter to create if required..
                if ( firstIsPartOf == null ) {
                    firstIsPartOf = isPartOf;
                }
				
				// First try to match using the other urls / identifiers in the collection hdo
				AggregatorCollectionHDO collHDO = AggregatorCollectionHDO.lookupByOtherUrl(sess, isPartOf);
				
				if ( collHDO != null && collHDO.getManagementInfo().getDeletedBy() == null ) {
					// Existing collection - just remember the code
					log.debug("Existing collection found by looking at identifiers / urls - collCode: " + collHDO.getCollectionCode());
					returnValue.add(new AggregatorCollectionDTO(collHDO));
                    globalCollFound = true;
					break;
				}
				
				// Now try looking at the name of the collection hdo for a match
				List<AggregatorCollectionHDO> sameNameColls = AggregatorCollectionHDO.lookupByName(sess, isPartOf);
				Iterator<AggregatorCollectionHDO> sameNameIter = sameNameColls.iterator();
				boolean collFound = false;
				while(sameNameIter.hasNext()) {
					AggregatorCollectionHDO nextColl = sameNameIter.next();
                                        
                    if ( nextColl.getManagementInfo().getDeletedBy() == null ) {
                        if ( defaultColl.getId() == nextColl.getId() ) {
                            // Found the collection that is the default collection..
                            log.debug("Existing collection found by looking at names. This collection is the default collection so using it. Coll code: " + nextColl.getCollectionCode());
                            returnValue.add(new AggregatorCollectionDTO(nextColl));
                            collFound = true;
                            globalCollFound = true;
                            break;
                        } else if ( isDescendent(defaultColl, nextColl) ) {
                            // Found a collection with the same name that is a descendent of the parent collection - therefore
                            // it is ok to use
                            log.debug("Existing collection found by looking at names that is a child of the default collection - using it. Coll code: " + nextColl.getCollectionCode());
                            returnValue.add(new AggregatorCollectionDTO(nextColl));
                            collFound = true;
                            globalCollFound = true;
                            break;
                        } else {
                            log.info("Existing collection found by looking at names, but it isn't a child of the default collection - can't use it. Coll code: " + nextColl.getCollectionCode());
                        }
                    }
				}
				if ( collFound ) {
					break;
				}
            }
            
            if ( !globalCollFound ) {
				
				// No suitable collection found that we can use, need to create one
				log.debug("No existing suitable collection found from the isPartOf / default coll code combination - creating one..");
				Transaction tx = null;
				try {
                                    
					tx = sess.beginTransaction();
					
					AggregatorCollectionHDO parentColl = AggregatorCollectionHDO.lookup(sess, defaultCollCode);
					AggregatorCollectionHDO newColl = null;
					String tempCollCode = "Collection-temp-" + (new Random()).nextInt();

					newColl = new AggregatorCollectionHDO(tempCollCode, firstIsPartOf, null, parentColl.getCollectionType());
					sess.persist(newColl);
					
					newColl = AggregatorCollectionHDO.lookup(sess, tempCollCode);
					String newCollCode = "Collection-" + newColl.getId();
					newColl.setCollectionCode(newCollCode);
					sess.merge(newColl);
					
					parentColl.addChildCollection(newColl);
					sess.merge(parentColl);
					
					tx.commit();
					
					// Now retrieve the collection back again and remember it in the return list for future processing
					AggregatorCollectionHDO createdColl = AggregatorCollectionHDO.lookup(sess, newCollCode);
					returnValue.add(new AggregatorCollectionDTO(createdColl));
						
				} catch (HibernateException he) {
					log.error("Hibernate exception thrown when creating a new collection from an isPartOf. isPartOf: " + firstIsPartOf + ". Error: " + he.getMessage());
					if ( tx != null ) 
						tx.rollback();
				}
			}
		}
                
        log.debug("About to return from working out collection data.. returnValue.size() + " + returnValue.size() + " and contents...");
        Iterator<AggregatorCollectionDTO> collIter = returnValue.iterator();
        while(collIter.hasNext()) {
            AggregatorCollectionDTO nextColl = collIter.next();
            log.debug("collection code: " + nextColl.getCollectionCode());
        }
		
		return returnValue;
	}
	
	public static LinkedHashMap<String,String> workoutAncestorCollectionInfo(Session sess, AggregatorCollectionHDO coll, LinkedHashMap<String,String> infoSoFar) {
		
		if ( coll != null ) {
			// Add this collection to the map and then start again with its parent
			String collCode = coll.getCollectionCode();
			infoSoFar.put(collCode, coll.getName());
			
			if ( !"TOP_LEVEL".equals(coll.getCollectionType()) ) {
				// Collection is not a top level, so has a parent - go and get it and then
				// start again with the parent
				
				AggregatorCollectionHDO parent = AggregatorCollectionHDO.lookupByChildCollectionCode(sess, collCode);
				if ( parent != null ) {
					infoSoFar = workoutAncestorCollectionInfo(sess, parent, infoSoFar);
				}
			}
			
		}
		
		return infoSoFar;
	}

	public static boolean isDescendent(AggregatorCollectionHDO ancestorColl, AggregatorCollectionHDO possibleDescendentColl) {
		boolean returnValue = false;

		if ( ancestorColl != null && possibleDescendentColl != null && ancestorColl.getId() != possibleDescendentColl.getId()) {
			// We have two different collections specified
			
			// Check whether the possibleDescendentColl is a direct child of the ancestor coll 
			List<AggregatorCollectionHDO> children = ancestorColl.getChildrenCollections();
			if (children != null) {
				Iterator<AggregatorCollectionHDO> childIter = children.iterator();
				while(childIter.hasNext()) {
					AggregatorCollectionHDO child = childIter.next();
					if ( child != null && child.getId() == possibleDescendentColl.getId() ) {
						// Match
						returnValue = true;
						break;
					}
				}
				
				if ( !returnValue ) {
					// No match yet, so look through the descendents of the children (and so on)
					childIter = null;
					childIter = children.iterator();
					while(childIter.hasNext()) {
						AggregatorCollectionHDO child = childIter.next();
						if ( child != null ) {
							returnValue = isDescendent(child, possibleDescendentColl);
							if ( returnValue ) {
								// Match found in the children somewhere, can stop looking
								break;
							}
						}
					}
					
				}
			}
		}
	
		return returnValue;
	
	}
	
}
