/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * <BR><BR>
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.collection;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.cdm.CollectionDesignManager;
import org.greenstone.gatherer.cdm.CollectionMeta;
import org.greenstone.gatherer.cdm.CollectionMetaManager;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;

/** Collection provides a common collection point for all the information about a certain collection build session. It keeps a record of several other managers that actually handle the content of the collection, such as metadata sets and metadata itself.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3c
 */
public class Collection {
    /** A reference to the Collection Design Manager. */
    public CollectionDesignManager cdm;
    /** A reference to the build ScriptOptions. */
    public ScriptOptions build_options;
    /** A reference to the import ScriptOptions. */
    public ScriptOptions import_options;
    /** A reference to the schedule ScriptOptions */ 
    public ScriptOptions schedule_options; 
    /** true if an error has occurred during construction */
    public boolean error = false;
    /** <i>true</i> if the currently loaded collection has been saved since the last significant change, <i>false</i> otherwise. */
    private boolean saved = false;
    /*<i>true</i> if the currently loaded collection has had files added since its last build */
    private boolean filesChanged = false;
    /*<i>true</i> if the currently loaded collection has had metadata added since its last build */
    private boolean metadataChanged = false;
    /** The document around which this collection class is based. - in memory version of gli.col */
    private Document document;

     /** The file the collection is in (the file may not actually exist, such in the case of a legacy collection)! "gli.col" */
     private File file;
    /** The name of the argument element. */
    static final private String ARGUMENT = "Argument";
    static final private String BASE_COLLECTION = "base_collection";
    /** The name of the build element. */
    static final private String BUILD = "Build";
    /** The name of the build config element. */
    static final private String BUILD_CONFIG = "BuildConfig";
    /** The name of the collection xml (gli.col) template. */
    static final private String COLLECTION_XML_TEMPLATE = "xml/template.col";
    /** The name of the import element. */
    static final private String IMPORT = "Import";
    /** The name of the schedule element. */
    static final private String SCHEDULE = "Schedule"; 
    /** The name of the export element. */
    static final private String EXPORT = "Export";
    /** The name of the imported attribute. */
    static final private String IMPORTED = "imported";
      /** The name of the imported attribute. */
    static final private String SKIM_FILE = "skimfile"; 
     

    /** Constructor. */
    public Collection(File collection_xml) {
	this.file = collection_xml;
	// Try to load this collections details.
       	document = XMLTools.parseXMLFile(collection_xml);
	// If that fails load the default settings for a collection.
	if(document == null) {
	    document = XMLTools.parseXMLFile(COLLECTION_XML_TEMPLATE, true);
	}
	if (document == null) {
	    error = true;
	    return;
	}
	// Point the Configuration class at our gatherer config arguments.
	Configuration.setCollectionConfiguration(document);
	if (Gatherer.g_man != null) {
	    Gatherer.g_man.updateUI();
	}
	// Finally create all of the child managers that are directly dependant on a collection

	if (Configuration.fedora_info != null && Configuration.fedora_info.isActive()) {
	    build_options = new ScriptOptions(getValues(BUILD), "g2f-buildcol.pl");
	    import_options = new ScriptOptions(getValues(IMPORT), "g2f-import.pl");
	}
	else {
	    build_options = new ScriptOptions(getBuildValues(), "buildcol.pl");
	    import_options = new ScriptOptions(getImportValues(), "import.pl");
	    schedule_options = new ScriptOptions(getScheduleValues(), "schedule.pl");
	}
    }
    
    /** Destructor.
     * @see org.greenstone.gatherer.Configuration
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.cdm.CollectionDesignManager
     */
    public void destroy() {
	cdm.destroy();
	Configuration.setCollectionConfiguration(null);
	// this is called after calling collection.destroy, so we shouldn't need it here too.
// 	if (Gatherer.g_man != null) {
// 	    Gatherer.g_man.updateUI();
// 	}
	cdm = null;
	document = null;
    }
    
    /** Determine the number of documents and folders in this collection. */
    public int getCount() {
	return getCount((TreeNode)Gatherer.c_man.getCollectionTreeModel().getRoot(), true, true);
    }
    
    /** Calculates the number of documents in this collection. */
    public int getDocumentCount() {
	return getCount((TreeNode)Gatherer.c_man.getCollectionTreeModel().getRoot(), false, true);
    }
    
    /** Retrieve the description of this collection.
     * @return a String
     */
    public String getDescription() {
	if(cdm == null) {
	    return StaticStrings.EMPTY_STR;
	}
	CollectionMeta collection_extra_collectionmeta = cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_COLLECTIONEXTRA_STR);
	return collection_extra_collectionmeta.getValue(CollectionMeta.TEXT);
    }
    
    /** Retrieve the authors email for this collection.
     * @return The email as a <strong>String</strong>.
     */
    public String getEmail() {
	if(cdm == null) {
	    return StaticStrings.EMPTY_STR;
	}
	CollectionMeta creator_collectionmeta = new CollectionMeta(CollectionDesignManager.collect_config.getCreator());
	return creator_collectionmeta.getValue(CollectionMeta.TEXT);
    }

    /** Retrieve the short name for this collection.
     * @return The name as a <strong>String</strong>.
     */
    public String getName() {
	return getCollectionTailName();
    }

    /** @return just the tail-name of the collection: without any collection group prefix */
    public String getCollectionTailName() {
	return file.getParentFile().getName();
    }

    /** @return just the name of the collection group if any, else returns "" */
    public String getCollectionGroupName() {
	String groupAndColName = getGroupQualifiedName(true);

	int slash = groupAndColName.indexOf('/');
	if(slash == -1) {
	    return "";
	} else { // remove the colName from the end, to get the groupName
	    return groupAndColName.substring(0, slash);
	}
    }

    /** 
     * If the collection is part of a collection group, returns the 
     * colgroupname/colname, else returns the colname.
     * If url = true, then returns the sub-path as a URL (containing / only), 
     * and if url = false, then the sub-path is returned in filepath form 
     * (\ or /, depending on the OS).
     * @return collection-group-name/collection-name
     */
    public String getGroupQualifiedName(boolean url) {
	// Subtracting collect dir's path from the current collection's path

	String groupAndCol;
	String collectDir = Gatherer.getCollectDirectoryPath();
	String currentCollDir = file.getParentFile().getAbsolutePath(); 
   
	if(currentCollDir.startsWith(collectDir)) {
	    groupAndCol = currentCollDir.substring(collectDir.length());	    
	} else {
	    // shouldn't happen, but in case it does, just return the collection name.
	    System.err.println("Current collection " + currentCollDir + " is not located inside collectdir " + collectDir);
	    groupAndCol = getCollectionTailName();
	}

	if(url) {
	    groupAndCol = groupAndCol.replace('\\', '/');
	}

	return groupAndCol;
    }
    
    public File getCollectionDirectory() {
	return file.getParentFile();
    }

    // return full file path of the collection object
    public String getCollectionPath() {
	return file.getAbsolutePath();
    }

    /** Determine if this collection has been saved since the last major change.
     * @return <i>true</i> if it has been saved recently, <i>false</i> otherwise.
     */
    public boolean getSaved() {
	return saved;
    }

    /** Determine if this collection has had its collection files changed since the last build.
     * @return <i>true</i> if they have been saved, <i>false</i> otherwise.
     */
    public boolean getFilesChanged() {
	return filesChanged;
    }
    
    /** Set the flag that marks weather or not the files has been changed since the last build.
	@param changed if files has been added/removed, as a <strong>boolean</strong>
    */
    public void setFilesChanged(boolean changed) {
	filesChanged = changed;
    }

    /** Determine if this collection has had metadata changed since the last build.
     * @return <i>true</i> if it has been changed, <i>false</i> otherwise.
     */
    public boolean getMetadataChanged() {
	return metadataChanged;
    }
    
    /** Set the flag that marks weather or not the metadata has been changed since the last build.
	@param changed if metadata has been added/changed/removed, as a <strong>boolean</strong>
    */
    public void setMetadataChanged(boolean changed) {
	metadataChanged = changed;
    }

    /** Retrieve the title of this collection.
     * @return The title as a <strong>String</strong>.
     */
    public String getTitle() {
	if(cdm == null) {
	    return StaticStrings.EMPTY_STR;
	}
	CollectionMeta collection_name_collectionmeta = cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR);
	return collection_name_collectionmeta.getValue(CollectionMeta.TEXT);
    }

    /** Save this xml document to the given file. */
    public void save() {
	XMLTools.writeXMLFile(file, document);
	saved = true;
    }

    public void setBaseCollection(String base_collection) {
	set(BASE_COLLECTION, base_collection);
    }
    
    /** Set the value of imported to the given value.
     * @param value The new value for imported, <i>true</i> if the collection has been imported successfully, <i>false</i> otherwise.
     */
    public void setImported(boolean value) {
	set(IMPORTED, value);
	saved = false;
    }

    /** Set the value of saved to the given value.
     * @param value The new value for saved, <i>true</i> if the collection has been saved recently, <i>false</i> otherwise.
     */
    public void setSaved(boolean value) {
	saved = value;
    }

    /** Set the value of title to the given value.
     * @param title The new <strong>String</strong> title.
     */
    public void setTitle(String title) {
	if(cdm != null) {
	    CollectionMeta collection_name_collectionmeta = cdm.collectionmeta_manager.getMetadatum(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR);
	    collection_name_collectionmeta.setValue(title);
	}
    }

    /** Method called to return a textual representation of a class, which in this case is the collections title.
     * @return A <strong>String</strong> containing the collections title.
     */
    public String toString() {
	return getTitle();
    }
 
    public boolean toSkimFile(){
	 Element document_element = document.getDocumentElement();
	 NodeList arguments = document_element.getElementsByTagName(ARGUMENT);
          for(int i = 0;  i < arguments.getLength(); i++) {
	      Element argument_element = (Element) arguments.item(i);
              if(argument_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equalsIgnoreCase(SKIM_FILE)) {
		  String skimfile = XMLTools.getValue(argument_element); 
		  if (skimfile !=null && skimfile.trim().toLowerCase().equals("false")){
		      return false;
		  } 
	      }
	     
	  }
	  
	  return true;
    }

    
    /** Get the value of a collection argument. */
    private boolean get(String name) {
	boolean result = false;
	try {
	    Element document_element = document.getDocumentElement();
	    NodeList arguments = document_element.getElementsByTagName(ARGUMENT);
	    boolean found = false;
	    for(int i = 0; !found && i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		if(argument_element.getParentNode() == document_element) {
		    if(argument_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equalsIgnoreCase(name)) {
			String value = XMLTools.getValue(argument_element);
			if(value.equalsIgnoreCase(StaticStrings.TRUE_STR)) {
			    result = true;
			}
			found = true;
			value = null;
		    }
		}
		argument_element = null;
	    }
	    arguments = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return result;
    }
    
    /** Get the value of a collection argument. */
    private String getString(String name) {
	String result = "";
	try {
	    Element document_element = document.getDocumentElement();
	    NodeList arguments = document_element.getElementsByTagName(ARGUMENT);
	    boolean found = false;
	    for(int i = 0; !found && i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		if(argument_element.getParentNode() == document_element) {
		    if(argument_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equalsIgnoreCase(name)) {
			result = XMLTools.getValue(argument_element);
			found = true;
		    }
		}
		argument_element = null;
	    }
	    arguments = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return result;
    }

    

    /** Method to retrieve the current import/build/export/whatever options associated with this Collection. */
    private Element getValues(String val_type) {
	Element values_element = null;
	try {
	    Element document_element = document.getDocumentElement();
	    Element config_element = (Element) XMLTools.getNodeFromNamed(document_element, BUILD_CONFIG);
	    values_element = (Element) XMLTools.getNodeFromNamed(config_element, val_type);
	    config_element = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return values_element;
    }

    
    /** Method to retrieve the current build options associated with this Collection. */
    private Element getBuildValues() {
	Element build_values_element = null;
	try {
	    Element document_element = document.getDocumentElement();
	    Element build_config_element = (Element) XMLTools.getNodeFromNamed(document_element, BUILD_CONFIG);
	    build_values_element = (Element) XMLTools.getNodeFromNamed(build_config_element, BUILD);
	    build_config_element = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return build_values_element;
    }

    private Element getScheduleValues() {
	Element schedule_values_element = null;
	try {
	    Element document_element = document.getDocumentElement();
	    Element build_config_element = (Element) XMLTools.getNodeFromNamed(document_element, BUILD_CONFIG);
	    schedule_values_element = (Element) XMLTools.getNodeFromNamed(build_config_element, SCHEDULE);
	    build_config_element = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	} 
	return schedule_values_element;
    }
    
    /** Count either documents or folders, depending on the state of the given boolean. */
    private int getCount(TreeNode node, boolean count_folders, boolean count_files) {
	int count = 0;
	File file = ((CollectionTreeNode)node).getFile();
	if(file.isFile() && count_files) {
	    count++;
	}
	else if(file.isDirectory() && count_folders) {
	    count++;
	}
	for(int i = 0; !file.getName().equals("CVS") && i < node.getChildCount(); i++) {
	    count = count + getCount(node.getChildAt(i), count_folders, count_files);
	}
	return count;
    }
    
    /** Method to retrieve the current import options associated with this Collection. */
    public Element getImportValues() {
	Element import_values_element = null;
	try {
	    Element document_element = document.getDocumentElement();
	    Element build_config_element = (Element) XMLTools.getNodeFromNamed(document_element, BUILD_CONFIG);
	    import_values_element = (Element) XMLTools.getNodeFromNamed(build_config_element, IMPORT);
	    build_config_element = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return import_values_element;
    }
    
    /** Set the value of a collection argument. */
    private void set(String name, boolean value) {
	set(name, (value ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
    }
    
    private void set(String name, String value) {
	try {
	    Element document_element = document.getDocumentElement();
	    NodeList arguments = document_element.getElementsByTagName(ARGUMENT);
	    boolean found = false;
	    for(int i = 0; !found && i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		if(argument_element.getParentNode() == document_element) {
		    if(argument_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equalsIgnoreCase(name)) {
			// Strip any current value nodes.
			while(argument_element.hasChildNodes()) {
			    argument_element.removeChild(argument_element.getFirstChild());
			}
			// Append new value
			argument_element.appendChild(document.createTextNode(value));
			found = true;
		    }
		}
		argument_element = null;
	    }
	    // Append it
	    if(!found) {
		Element argument_element = document.createElement(ARGUMENT);
		argument_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, name);
		argument_element.appendChild(document.createTextNode(value));
		document_element.appendChild(argument_element);
		argument_element = null;
	    }
	    arguments = null;
	    document_element = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
    }
}
