/**
 *#########################################################################
 *
 * 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.
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * 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.
 *
 * 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.
 *
 * 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.cdm;

import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.util.Codec;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;


/** This class encapsulates all the information about a format command from the collection configuration. This includes both the 'general' formatting commands, and the commands targetted at specific classifiers or classification component types. This class is, unfortunately, the most complex in terms of DOM model support, since we have to maintain a live reference to any classifier this format applies to. We temporarily use the feature name as the format name attribute but since the order of classifiers can change, this of course gets horribly out of date. Before the CollectionDesignManager saves the CollectionConfiguration it calls a method in the FormatManager to force all of the Formats to update their name attributes.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class Format 
    implements DOMProxyListEntry {

    /** The default features (not all of these are in the Greenstone Developer's Guide). */
    static final public String DEFAULT_FEATURES[] = { "", "AllowExtendedOptions", "Document", "DocumentArrowsBottom", "DocumentArrowsTop","DocumentSearchResultLinks", "DocumentButtons", "DocumentContents", "DocumentHeading", "DocumentImages", "DocumentText", "DocumentTitles", "DocumentUseHTML", "NavigationBar", "RelatedDocuments", "Search", "SearchTypes", "AllowUserComments" };
    /** The list of known feature parts. */
    static final public String DEFAULT_PARTS[] = { "", "DateList", "HList", "VList" };

      
    /** Only "" and "Search" can have parts */
    static public boolean canHavePart(String name) {
	return (name.startsWith("<html>") || name.equalsIgnoreCase("") || name.equalsIgnoreCase("Search") || name.equalsIgnoreCase("Document"));
    }
	
    static public String generateName(Object feature, String part) {
	if(feature instanceof Classifier) {
	    return ((Classifier)feature).getPositionString() + part;
	}
	else {
	    return feature.toString() + part;
	}
    }


    /** Returns true if the parameter is a boolean type, false otherwise. */
    static public boolean isParamType(String name)
    {
	if (name.equalsIgnoreCase("AllowExtendedOptions") || name.equalsIgnoreCase("DocumentArrowsBottom") || name.equalsIgnoreCase("DocumentArrowsTop") || name.equalsIgnoreCase("DocumentSearchResultLinks") || name.equalsIgnoreCase("DocumentContents") || name.equalsIgnoreCase("DocumentImages") || name.equalsIgnoreCase("DocumentTitles") || name.equalsIgnoreCase("DocumentUseHTML") || name.equalsIgnoreCase("AllowUserComments") ) {
	    return true;
	}
	else {
	    return false;
	}
    }

   
    /** If this format is altering a Classifier then this is the classifier in question. */
    private Classifier classifier = null;
    /** The element this format is based on. */
    private Element element = null;
    /** We keep a copy of the part because its slightly more computationally tricky to calculate. */
    private String part = null;
    /** We keep a copy of the feature name */
    private String feature_name = null;
    /** Cached result of toString. */
    private String text = null;

    /** Constructor only to be used during DOMProxyListModel initialization. */
    public Format() {
	   
    }

    public Format(Element element) {

	this.element = element;
	// Immediately check if we are dealing with a classifier, by retrieving the feature name. If it is a classifier, then restore the live reference immediately before anything changes it.
	Object object = getFeature();

	// There may be format statements for no-longer-existing classifiers
	if (object == null) {
	    DebugStream.println("No matching classifier for format statement!");
	    return;
	}

	if(object instanceof Classifier) {
	    classifier = (Classifier) object;
	}
	else {
	    String feature = (String) object;
	    if(feature.toUpperCase().startsWith(Classifier.CLASSIFIER_PREFIX)) {
		// Extract the integer index
		int index = Integer.parseInt(feature.substring(Classifier.CLASSIFIER_PREFIX.length()));
		// Subtract one (as java is 0-2 where G2 is 1-3)
		index = index - 1;
		// Retrieve the appropriate classifier
		classifier = CollectionDesignManager.classifier_manager.getClassifier(index);
	    }
	}

      	 
    }

    /** Constructor for a flag format command.
     * @param feature The <Strong>Object</strong> this format affects.
     * @param part The specific part of the control to format as a <strong>String</strong>.
     * @param state A <i>boolean</i> indicating this formats state.
     */
    public Format(Object feature, String part, boolean state) {

	element = CollectionConfiguration.createElement(StaticStrings.FORMAT_ELEMENT);
	setName(feature, part);
	setState(state);
	 
    }

    /** Constructor for a format-string format command.
     * @param feature The <Strong>Object</strong> this format affects.
     * @param part The specific part of the control to format as a <strong>String</strong>.
     * @param value The format <strong>String</strong> which may be a label name, or a piece of HTML formatting.
     */
    public Format(Object feature, String part, String value) {

	element = CollectionConfiguration.createElement(StaticStrings.FORMAT_ELEMENT);       
	setName(feature, part);
	setValue(value);
	 
    }

     
 
    public boolean canHavePart() {
	return canHavePart(getFeatureName());
    }

    public int compareTo(Object object) {
	if(object == null) { // It seems that occasionally compareTo is called and somehow null ends up in comparison.
	    return -1;
	}
	if(object instanceof Format && element != null) {
	    return element.getAttribute(StaticStrings.NAME_ATTRIBUTE).compareTo(((Format)object).getName());
	}
	return toString().compareTo(object.toString());
    }

    public DOMProxyListEntry create(Element element) {
	return new Format(element);
    }

    public boolean equals(Object object) {
	return (compareTo(object) == 0);
    }

    public Element getElement() {
	return element;
    }

    /** Method to retrieve the value of feature, which may either be a String or a Classifier.
     * @return The value of feature as an <strong>Object</strong>.
     */
    public Object getFeature() {
	if(classifier != null) {
	    return classifier;
	}
	else if(element != null) {
	    String feature = getFeatureName();
	    // If the feature now refers to a classifier, retrieve it.
	    if(feature.toUpperCase().startsWith(Classifier.CLASSIFIER_PREFIX)) {
		// Extract the integer index
		int index = Integer.parseInt(feature.substring(Classifier.CLASSIFIER_PREFIX.length()));
		// Subtract one (as java is 0-2 where G2 is 1-3)
		index = index - 1;
		// Retrieve the appropriate classifier
		classifier = CollectionDesignManager.classifier_manager.getClassifier(index);
		return classifier;
	    }
	    else {
		return feature;
	    }
	}
	else {
	    return "Error";
	}
    }

    public String getFeatureName() {
	if (feature_name == null) {
	    String name  = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	    // Remove part
	    String part = getPart();
	    if(part != null) {
		feature_name = name.substring(0, name.length() - part.length());
	    }
	    else {
		feature_name = name;
	    }
	    part = null;
	    name = null;
	}

	return feature_name;
	    
    }
    public String getName() {
	if(element != null) {
	    String name  = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
           return element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	}
	return "Error";
    }

    /** Method to retrieve the value of part. Of course there may not be one, in which case return ""
     * @return The value of part as a <Strong>String</strong>.
     */
    public String getPart() {
	if(part == null && element != null) {
	    // To determine a part, we retrieve the Format name, then look for one of our recognized parts
	    String name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	    // DEFAULT_PARTS[0] is an empty string to correctly enable comboboxes.
	    name = name.toLowerCase();
	    for(int i = 1; part == null && i < DEFAULT_PARTS.length; i++) {
		if(name.endsWith(DEFAULT_PARTS[i].toLowerCase())) {
		    part = DEFAULT_PARTS[i];
		}
	    }
	    if(part == null) {
		part = DEFAULT_PARTS[0];
	    }
	}
	return part;
    }

    /** Method to retrieve the value of state.
     * @return value of state as a <i>boolean</i>.
     */
    public boolean getState() {
	return (element != null && element.getAttribute(StaticStrings.VALUE_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
    }

    /** Retrieve the value of value.
     * @return A <strong>String</strong> which is the value of value.
     */
    public String getValue() {
	String value = "Error";
	if(element != null) {
	   value = XMLTools.getValue(element);
	   // Decode into text
	   value = Codec.transform(value, Codec.DOM_TO_TEXT);
	}
	return value;
    }

    public boolean isAssigned() {
	return (element != null && !element.getAttribute(StaticStrings.ASSIGNED_ATTRIBUTE).equals(StaticStrings.FALSE_STR));
    }

    public boolean isParamType() {
	return (element != null && element.getAttribute(StaticStrings.VALUE_ATTRIBUTE).length() > 0);
    }

    public void setAssigned(boolean assigned) {
	if(element != null) {
	    element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, (assigned ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	}
    }

    public void setElement(Element element) {
	this.element = element;
	part = null;
	text = null;
	// Establish the classifier reference, if necessary
	getFeature();
    }

    public void setName(Object feature, String part) {
	// Can't do anything unless an element exists.
        
	if(element != null) {
	    this.part = part; // Easier for later on.
	    if(feature instanceof Classifier) {
		classifier = (Classifier) feature;
		feature_name = classifier.getPositionString(); 
		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, classifier.getPositionString() + part);
	    }
	    else {
                if  (feature.toString().startsWith("<html>")){
		    feature_name = "";
		}
		else{
		    feature_name = feature.toString();
		}
		element.setAttribute(StaticStrings.NAME_ATTRIBUTE, feature_name + part);
	    }
	    text = null;
	}
    }

    /** Set the value of state.
     * @param state The new value for state as a <i>boolean</i>.
     */
    public void setState(boolean state) {
	if(element != null) {
	    element.setAttribute(StaticStrings.VALUE_ATTRIBUTE, (state ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	    text = null;
	}
    }

    /** Set the value of value. Hmmm.
     * @param value The new value from value as a <strong>String</strong>.
     */
    public void setValue(String value) {
	if(element != null) {
	    // Strip off any enclosing speech marks
	    if(value.startsWith(StaticStrings.SPEECH_CHARACTER) && value.startsWith(StaticStrings.SPEECH_CHARACTER)) {
		value = value.substring(1, value.length() - 1);
	    }
	    // Encode
	    value = Codec.transform(value, Codec.TEXT_TO_DOM);
	    // Store in DOM
	    XMLTools.setValue(element, value);
	    text = null;
	}
    }

    /** Method to translate this classes information into a line of text as you would expect in the collection configuration file.
     * @return A <strong>String</strong> containing the format command.
     */
    public String toString() {
	if(text == null && element != null) {
	    StringBuffer temp = new StringBuffer(" ");
	    temp.append(element.getAttribute(StaticStrings.NAME_ATTRIBUTE));
	    temp.append(" ");
	    String value = element.getAttribute(StaticStrings.VALUE_ATTRIBUTE);
	    if(value.length() > 0) {
		temp.append(value);
	    }
	    else {
		value = XMLTools.getValue(element);
		if(value.equalsIgnoreCase(StaticStrings.TRUE_STR) || value.equalsIgnoreCase(StaticStrings.FALSE_STR)) {
		    temp.append(value);
		}
		else {
		    temp.append("\"");
		    // Retrieve the DOM encoded value, decode and then append
		    value = Codec.transform(value, Codec.DOM_TO_TEXT);
		    temp.append(value);
		    temp.append("\"");
		}
	    }
	    text = temp.toString();
	    temp = null;
	}
	return text;
    }

    public void update() {
	if(classifier != null) {
	    element.setAttribute(StaticStrings.NAME_ATTRIBUTE, classifier.getPositionString() + getPart());
	    feature_name = classifier.getPositionString(); 
	    text = null;
	}
    }
}
