/*
 * This file is part of Jstacs.
 *
 * Jstacs 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Jstacs 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 Jstacs. If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information on Jstacs, visit http://www.jstacs.de
 */

package de.jstacs.parameters;

import de.jstacs.NonParsableException;
import de.jstacs.io.XMLParser;

/**
 * A class for a {@link ParameterSet} that can be expanded by additional
 * {@link Parameter}s at runtime. The {@link Parameter}s are all of type
 * {@link ParameterSetContainer}. The contents of these {@link Parameter}s are
 * provided by a {@link ParameterSet}-template, which is cloned each time
 * {@link #addParameterToSet()} is called and set as value of a
 * {@link ParameterSetContainer} that is added to the {@link Parameter}s of the
 * {@link ExpandableParameterSet}. Already added {@link Parameter}s can also be
 * removed from the set using {@link #removeParameterFromSet()}.
 * 
 * @author Jan Grau
 */
public class ExpandableParameterSet extends ParameterSet {

	/**
	 * The template for each {@link ParameterSet}
	 */
	protected ParameterSet template;
	/**
	 * A template for the name of the enclosing {@link ParameterSetContainer}
	 */
	protected String nameTemplate;
	/**
	 * A template for the comment of the enclosing {@link ParameterSetContainer}
	 */
	protected String commentTemplate;

	private int count;

	private int initCount;

	/**
	 * Creates a new {@link ExpandableParameterSet} from a {@link Class} that
	 * can be instantiated using this {@link ExpandableParameterSet} and
	 * templates for the {@link ParameterSet} in each element of the array, the
	 * name and the comment that are displayed for the
	 * {@link ParameterSetContainer}s enclosing the {@link ParameterSet}s.
	 * Initially, the method {@link #loadParameters()} creates one copy of the
	 * template.
	 * 
	 * @param template
	 *            the template of the {@link ParameterSet}
	 * @param nameTemplate
	 *            the name-template
	 * @param commentTemplate
	 *            the comment-template
	 */
	public ExpandableParameterSet(ParameterSet template, String nameTemplate,
			String commentTemplate) {
		super();
		this.template = template;
		this.nameTemplate = nameTemplate;
		this.commentTemplate = commentTemplate;
		this.count = 0;
		this.initCount = 1;
	}

	/**
	 * Creates a new {@link ExpandableParameterSet} from a {@link Class} that
	 * can be instantiated using this {@link ExpandableParameterSet} and
	 * templates for the {@link ParameterSet} in each element of the array, the
	 * name and the comment that are displayed for the
	 * {@link ParameterSetContainer}s enclosing the {@link ParameterSet}s. Using
	 * this constructor, the method {@link #loadParameters()} initially creates
	 * <code>initCount</code> copies of the template.
	 * 
	 * @param template
	 *            the template of the {@link ParameterSet}
	 * @param nameTemplate
	 *            the name-template
	 * @param commentTemplate
	 *            the comment-template
	 * @param initCount
	 *            the number of initial copies of the template
	 */
	public ExpandableParameterSet(ParameterSet template, String nameTemplate,
			String commentTemplate, int initCount) {
		super();
		this.template = template;
		this.nameTemplate = nameTemplate;
		this.commentTemplate = commentTemplate;
		this.count = 0;
		this.initCount = initCount;
	}

	/**
	 * The standard constructor for the interface {@link de.jstacs.Storable}.
	 * Creates a new {@link ExpandableParameterSet} from its XML representation.
	 * 
	 * @param representation
	 *            the XML represenation as {@link StringBuffer}
	 * 
	 * @throws NonParsableException
	 *             if the {@link StringBuffer} <code>representation</code> could
	 *             not be parsed
	 */
	public ExpandableParameterSet(StringBuffer representation)
			throws NonParsableException {
		super(representation);
	}

	/**
	 * Creates a new {@link ExpandableParameterSet} from a {@link ParameterSet}
	 * -array. The first element of this array is taken as a template for
	 * further calls of {@link #addParameterToSet()}.
	 * 
	 * @param templateAndContent
	 *            the content (and template)
	 * @param nameTemplate
	 *            the name-template
	 * @param commentTemplate
	 *            the comment-template
	 */
	public ExpandableParameterSet(ParameterSet[] templateAndContent,
			String nameTemplate, String commentTemplate) {
		if (templateAndContent.length == 0) {
			throw new IllegalArgumentException(
					"You must provide at least one ParameterSet.");
		}
		this.template = templateAndContent[0];
		this.nameTemplate = nameTemplate;
		this.commentTemplate = commentTemplate;

		if (notAllGivenParameterSetsAreOfTemplateType(templateAndContent)) {
			throw new IllegalArgumentException(
					"At least one of the given ParameterSets is not of "
							+ "the specified template-Type");
		}

		initParameterList(templateAndContent.length);
		for (int i = 0; i < templateAndContent.length; i++) {
			parameters.add(new ParameterSetContainer(nameTemplate + " no. "
					+ (i + 1), commentTemplate, templateAndContent[i]));
		}
		this.count = templateAndContent.length;
		this.initCount = this.count;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.parameters.ParameterSet#clone()
	 */
	@Override
	public ExpandableParameterSet clone() throws CloneNotSupportedException {
		ExpandableParameterSet clone = (ExpandableParameterSet) super.clone();
		clone.template = template.clone();
		return clone;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.parameters.ParameterSet#loadParameters()
	 */
	@Override
	protected void loadParameters() throws Exception {
		if (parameters == null) {
			initParameterList();
			for (int i = 0; i < initCount; i++) {
				addParameterToSet();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * de.jstacs.parameters.ParameterSet#replaceParametersWithRangedInstance()
	 */
	@Override
	protected void replaceParametersWithRangedInstance() throws Exception {
		this.template.makeRanged();
		super.replaceParametersWithRangedInstance();
	}

	/**
	 * Adds a new {@link ParameterSetContainer} containing a clone of the
	 * {@link ParameterSet}-template to the set of {@link Parameter}s.
	 * 
	 * @throws CloneNotSupportedException
	 *             if the template could not be cloned
	 */
	public void addParameterToSet() throws CloneNotSupportedException {
		if (parameters == null) {
			try {
				loadParameters();
				if (ranged) {
					replaceParametersWithRangedInstance();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		ParameterSetContainer simplePar = new ParameterSetContainer(
				nameTemplate + " no. " + (++count), commentTemplate, template
						.clone());
		parameters.add(simplePar);

	}

	/**
	 * First removes all previous added {@link ParameterSetContainer}s and
	 * afterwards adds all given {@link ParameterSet}s (in the given order)
	 * enclosed in new {@link ParameterSetContainer}s.
	 * 
	 * @param paramSetArray
	 *            the {@link ParameterSet}s to be set
	 * 
	 * @return <code>true</code>, if all given {@link ParameterSet}s are of the
	 *         same class as the defined template-{@link ParameterSet} of this
	 *         {@link ExpandableParameterSet}, <code>false</code> otherwise (In
	 *         this case, neither the previous added
	 *         {@link ParameterSetContainer}s are removed nor is any given
	 *         {@link ParameterSet} added. Hence the
	 *         {@link ExpandableParameterSet} stays unchanged.)
	 */
	public boolean replaceContentWith(ParameterSet[] paramSetArray) {

		if (notAllGivenParameterSetsAreOfTemplateType(paramSetArray)) {
			return false;
		}

		initParameterList(paramSetArray.length);
		for (int i = 0; i < paramSetArray.length; i++) {
			parameters.add(new ParameterSetContainer(nameTemplate + " no. "
					+ (i + 1), commentTemplate, paramSetArray[i]));
		}
		this.count = paramSetArray.length;

		return true;
	}

	private boolean notAllGivenParameterSetsAreOfTemplateType(
			ParameterSet[] temp) {

		Class clazz = template.getClass();

		for (int i = 0; i < temp.length; i++) {
			if (temp[i].getClass() != clazz)
				return true;
		}

		return false;
	}

	/**
	 * Returns <code>true</code> if there is still a {@link Parameter} that can
	 * be removed from the set.
	 * 
	 * @return
	 *         <code>true</coede> if a {@link Parameter} can be removed from the set
	 */
	public boolean parameterRemovable() {
		return count > 0;
	}

	/**
	 * Removes the last {@link Parameter} from set.
	 */
	public void removeParameterFromSet() {
		if (parameters == null) {
			try {
				loadParameters();
				if (ranged) {
					replaceParametersWithRangedInstance();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		if (count > 0) {
			parameters.remove(parameters.size() - 1);
			count--;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.parameters.ParameterSet#toXML()
	 */
	@Override
	public StringBuffer toXML() {
		StringBuffer buf = super.toXML();
		XMLParser.addTags(buf, "superParameters");
		XMLParser.appendObjectWithTags(buf, template, "template");
		XMLParser.appendObjectWithTags(buf, nameTemplate, "nameTemplate");
		XMLParser.appendObjectWithTags(buf, commentTemplate, "commentTemplate");
		XMLParser.appendObjectWithTags(buf, initCount, "initCount");
		XMLParser.addTags(buf, "expandableParameterSet");
		return buf;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.jstacs.parameters.ParameterSet#fromXML(java.lang.StringBuffer)
	 */
	@Override
	protected void fromXML(StringBuffer representation)
			throws NonParsableException {
		representation = XMLParser.extractForTag(representation,
				"expandableParameterSet");
		super.fromXML(XMLParser
				.extractForTag(representation, "superParameters"));
		template = XMLParser.extractObjectForTags( representation, "template", ParameterSet.class );// TODO XMLP14CONV This and (possibly) the following lines have been converted automatically
		nameTemplate = XMLParser.extractObjectForTags(representation, "nameTemplate", String.class );
		commentTemplate = XMLParser.extractObjectForTags(representation, "commentTemplate", String.class );
		initCount = XMLParser.extractObjectForTags(representation, "initCount", int.class );// TODO XMLP14CONV This and (possibly) the following lines have been converted automatically
	}

}
