/FileExtraction/Java_unseen/crisis-economics_CRISIS/CRISIS/src/eu/crisis_economics/abm/dashboard/GASearchHandler.java
/*
 * This file is part of CRISIS, an economics simulator.
 * 
 * Copyright (C) 2015 AITIA International, Inc.
 * Copyright (C) 2015 Ross Richardson
 * Copyright (C) 2015 John Kieran Phillips
 *
 * CRISIS 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.
 *
 * CRISIS 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 CRISIS.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.crisis_economics.abm.dashboard;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;

import org.apache.commons.math3.random.MersenneTwister;
import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessEvaluator;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.jgap.RandomGenerator;
import org.jgap.impl.DefaultConfiguration;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import ai.aitia.meme.paramsweep.batch.IModelInformation.ModelInformationException;
import ai.aitia.meme.paramsweep.batch.IParameterSweepResultReader;
import ai.aitia.meme.paramsweep.batch.ReadingException;
import ai.aitia.meme.paramsweep.batch.ResultValueInfo;
import ai.aitia.meme.paramsweep.batch.output.RecordableInfo;
import ai.aitia.meme.paramsweep.batch.param.ParameterTree;
import ai.aitia.meme.paramsweep.colt.DoubleArrayList;
import ai.aitia.meme.paramsweep.generator.IStatisticInfoGenerator;
import ai.aitia.meme.paramsweep.generator.OperatorsInfoGenerator;
import ai.aitia.meme.paramsweep.generator.StatisticsInfoGenerator;
import ai.aitia.meme.paramsweep.gui.info.ISubmodelGUIInfo;
import ai.aitia.meme.paramsweep.gui.info.MemberInfo;
import ai.aitia.meme.paramsweep.gui.info.OperatorGeneratedMemberInfo;
import ai.aitia.meme.paramsweep.gui.info.ParameterInfo;
import ai.aitia.meme.paramsweep.gui.info.RecordableElement;
import ai.aitia.meme.paramsweep.gui.info.SimpleGeneratedMemberInfo;
import ai.aitia.meme.paramsweep.gui.info.SubmodelInfo;
import ai.aitia.meme.paramsweep.intellisweepPlugin.utils.ga.GeneInfo;
import ai.aitia.meme.paramsweep.internal.platform.InfoConverter;
import ai.aitia.meme.paramsweep.operatorPlugin.Operator_TimeSeries;
import ai.aitia.meme.paramsweep.plugin.IIntelliContext;
import ai.aitia.meme.paramsweep.plugin.IIntelliDynamicMethodPlugin;
import ai.aitia.meme.paramsweep.plugin.IStatisticsPlugin;
import ai.aitia.meme.paramsweep.utils.WizardLoadingException;
import ai.aitia.meme.utils.Utils;
import eu.crisis_economics.abm.dashboard.ga.BestChromosomeSelectorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.CrossoverOperatorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.DashboardBreeder;
import eu.crisis_economics.abm.dashboard.ga.GeneAveragingCrossoverOperatorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.IGAOperatorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.IGASelectorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.IIdentifiableGene;
import eu.crisis_economics.abm.dashboard.ga.IWorkspaceAware;
import eu.crisis_economics.abm.dashboard.ga.IdentifiableDoubleGene;
import eu.crisis_economics.abm.dashboard.ga.IdentifiableListGene;
import eu.crisis_economics.abm.dashboard.ga.IdentifiableLongGene;
import eu.crisis_economics.abm.dashboard.ga.MutationOperatorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.TournamentSelectorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.WeightedRouletteSelectorConfigurator;
import eu.crisis_economics.abm.dashboard.ga.operators.StandardPostSelectorFixed;
import eu.crisis_economics.abm.dashboard.generated.GeneralParameter;
import eu.crisis_economics.abm.dashboard.generated.Model;
import eu.crisis_economics.abm.dashboard.generated.ObjectFactory;
import eu.crisis_economics.abm.dashboard.generated.Operator;
import eu.crisis_economics.abm.dashboard.generated.Parameter;
import eu.crisis_economics.abm.dashboard.generated.ParameterType;
import eu.crisis_economics.abm.dashboard.generated.Property;
import eu.crisis_economics.abm.dashboard.generated.Selector;
import eu.crisis_economics.abm.dashboard.generated.SubmodelParameter;
import eu.crisis_economics.abm.dashboard.jaxb.util.ParameterUtil;

@SuppressWarnings("unchecked")
public class GASearchHandler implements GASearchPanelModel, IIntelliDynamicMethodPlugin, IWorkspaceAware {
													 
	//====================================================================================================
	// members

	private static final int DEFAULT_TIMESERIES_LENGTH = 20;
	private static final long serialVersionUID = 45791730954138881L;
	private static final Logger log = LoggerFactory.getLogger(GASearchHandler.class);
	
	private static final String POPULATION_SIZE = "Population size";
	private static final String POPULATION_RANDOM_SEED = "Population random seed";
	private static final String FIX_NUMBER_OF_GENERATIONS = "Fix number of generations";
	private static final String NUMBER_OF_GENERATIONS = "Number of generations";
	private static final String FITNESS_LIMIT_CRITERION = "Fitness limit";
	private static final String OPTIMIZATION_DIRECTION = "Optimization direction";
	private static final String FITNESS_FUNCTION = "Fitness function"; 
	private static final String FITNESS_STATISTICS = "Fitness statistics"; 
	private static final String FITNESS_TIMESERIES_LENGTH = "Fitness time-series length"; 

	protected static final String ext = ".txt";
	protected static final String bckExt = ".bak";
	
	private static final List<Class<?>> acceptableReturnTypesForFitnessFunction;
	private static final List<IStatisticsPlugin> fitnessStatistics;
	
	public static final IStatisticsPlugin noStatistics = new IStatisticsPlugin() {

		@Override
		public String getLocalizedName() {
			return null;
		}

		@Override
		public int getNumberOfParameters() {
			return 0;
		}

		@Override
		public String getName() {
			return "none";
		}

		@Override
		public String getDescription() {
			return "no statistics selected";
		}

		@SuppressWarnings("rawtypes")
		@Override
		public Class getReturnType() {
			return null;
		}

		@SuppressWarnings("rawtypes")
		@Override
		public List<Class> getParameterTypes() {
			return null;
		}

		@Override
		public List<String> getParameterNames() {
			return null;
		}

		@Override
		public List<String> getParameterDescriptions() {
			return null;
		}

		@Override
		public String getFullyQualifiedName() {
			return null;
		}
	};
	
	static {
		Class<?>[] array = { Byte.TYPE, Byte.class, Short.TYPE, Short.class, Integer.TYPE, Integer.class, Long.TYPE, Long.class, Float.TYPE, Float.class,
							 Double.TYPE, Double.class };
		acceptableReturnTypesForFitnessFunction = Arrays.asList(array);

		Reflections reflections = new Reflections("ai.aitia.meme.paramsweep.coltPlugin");
		Set<Class<? extends IStatisticsPlugin>> subTypesOf = reflections.getSubTypesOf(IStatisticsPlugin.class);
		fitnessStatistics = new ArrayList<IStatisticsPlugin>();
		
		fitnessStatistics.add(noStatistics);
		
		for (Class<? extends IStatisticsPlugin> pluginClass : subTypesOf) {
			try {
				final IStatisticsPlugin plugin = pluginClass.newInstance();
				@SuppressWarnings("rawtypes")
            List<Class> parameterTypes = plugin.getParameterTypes();
				if (parameterTypes.size() == 1 && parameterTypes.get(0).isAssignableFrom(DoubleArrayList.class)) {
					fitnessStatistics.add(plugin);
				}
			} catch (InstantiationException e) {
				log.error("Could not instantiate " + pluginClass.getName(), e);
			} catch (IllegalAccessException e) {
				log.error("Could not instantiate " + pluginClass.getName(), e);
			}
		}
	}

	private double numberOfTurns = Dashboard.NUMBER_OF_TURNS;
	private int populationSize = 12;
	private int populationGenerationSeed = 1;
	private int numberOfGenerations = 12;
	private double fitnessLimitCriterion = 0;
	private boolean fixNumberOfGenerations = true;
	private FitnessFunctionDirection optimizationDirection = FitnessFunctionDirection.MINIMIZE;
	private RecordableInfo selectedFunction;

	private IStatisticsPlugin selectedFitnessStatistics;
	
	private final List<IGASelectorConfigurator> selectedSelectionOperators = new ArrayList<IGASelectorConfigurator>();

	private final List<IGAOperatorConfigurator> selectedGeneticOperators = new ArrayList<IGAOperatorConfigurator>();

	private DefaultConfiguration gaConfiguration;

	/**
	 * The available selection operators.
	 */
	private List<IGASelectorConfigurator> selectors;

	/**
	 * The available genetic operators.
	 */
	private List<IGAOperatorConfigurator> geneticOperators;

	/**
	 * The list of fitness functions.
	 */
	private final List<RecordableInfo> fitnessFunctions = new ArrayList<RecordableInfo>();

	private int fitnessTimeSeriesLength = DEFAULT_TIMESERIES_LENGTH;
	
	private final String populationFileName = "population";
	
	private DefaultTreeModel chromosomeTree;
	private List<GeneInfo> genes = null;
	private int iterationCounter = 1;
	private boolean reachFitnessLimit = false;
	
	private Genotype genotype;
	private List<ParameterInfo> paramList;

	private final List<ModelListener> listeners = new ArrayList<GASearchPanelModel.ModelListener>();
	
	private File workspace = new File(".");
	private IModelHandler currentModelHandler = null;
	
	//====================================================================================================
	// methods

	//----------------------------------------------------------------------------------------------------
	public GASearchHandler(final IModelHandler currentModelHandler) {
		selectors = Arrays.asList(new TournamentSelectorConfigurator(),new WeightedRouletteSelectorConfigurator(),new BestChromosomeSelectorConfigurator());
		geneticOperators = Arrays.asList(new GeneAveragingCrossoverOperatorConfigurator(), new CrossoverOperatorConfigurator(),new MutationOperatorConfigurator());
		
		init(currentModelHandler);
	}
	
	//----------------------------------------------------------------------------------------------------
	public void init(final IModelHandler modelHandler) {
		this.currentModelHandler = modelHandler;
		numberOfTurns = Dashboard.NUMBER_OF_TURNS;
		populationSize = 12;
		populationGenerationSeed = 1;
		numberOfGenerations = 12;
		fitnessLimitCriterion = 0;
		fixNumberOfGenerations = true;
		optimizationDirection = FitnessFunctionDirection.MINIMIZE;
		selectedFunction = new RecordableInfo("Please select a function!",Object.class,"Please select a function!");

		selectedFitnessStatistics = fitnessStatistics.get(0);
		fitnessTimeSeriesLength = DEFAULT_TIMESERIES_LENGTH;
		
		selectedSelectionOperators.clear();
		selectedGeneticOperators.clear();

		gaConfiguration = null;
		fitnessFunctions.clear();

		iterationCounter = 1;
		reachFitnessLimit = false;
		
		genotype = null;
		paramList = null;
		
		workspace = new File(".");
		
		fitnessFunctions.add(selectedFunction);
		
		
		selectedSelectionOperators.add(selectors.get(2));

		selectedGeneticOperators.add(geneticOperators.get(0));
		selectedGeneticOperators.add(geneticOperators.get(2));
	}
	
	//----------------------------------------------------------------------------------------------------
	public void saveConfiguration(final ObjectFactory factory, final Model config) {
		saveGeneralParameters(factory,config);
		saveSelectors(factory,config);
		saveOperators(factory,config);
		saveChromosome(factory,config);
	}
	
	//----------------------------------------------------------------------------------------------------
	private void saveGeneralParameters(final ObjectFactory factory, final Model config) {
		final List<GeneralParameter> generalParameters = config.getGeneralParameterList();

		GeneralParameter gp = factory.createGeneralParameter();
		gp.setName(ParameterTreeUtils.NUMBER_OF_TURNS_XML_NAME);
		gp.setValue(String.valueOf(numberOfTurns));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(POPULATION_SIZE);
		gp.setValue(String.valueOf(populationSize));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(POPULATION_RANDOM_SEED);
		gp.setValue(String.valueOf(populationGenerationSeed));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(FIX_NUMBER_OF_GENERATIONS);
		gp.setValue(String.valueOf(fixNumberOfGenerations));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(NUMBER_OF_GENERATIONS);
		gp.setValue(String.valueOf(numberOfGenerations));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(FITNESS_LIMIT_CRITERION);
		gp.setValue(String.valueOf(fitnessLimitCriterion));
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(OPTIMIZATION_DIRECTION);
		gp.setValue(optimizationDirection.name());
		generalParameters.add(gp);
		
		gp = factory.createGeneralParameter();
		gp.setName(FITNESS_FUNCTION);
		gp.setValue(createUniqueName(selectedFunction));
		generalParameters.add(gp);

		gp = factory.createGeneralParameter();
		gp.setName(FITNESS_STATISTICS);
		gp.setValue(selectedFitnessStatistics.getName());
		generalParameters.add(gp);

		gp = factory.createGeneralParameter();
		gp.setName(FITNESS_TIMESERIES_LENGTH);
		gp.setValue(String.valueOf(fitnessTimeSeriesLength));
		generalParameters.add(gp);
}

	//----------------------------------------------------------------------------------------------------
	private String createUniqueName(final RecordableInfo fitnessFunction) {
		//TODO: support 'the fitness function is part of a submodel parameter' case too
		return fitnessFunction.getName();
	}
	
	//----------------------------------------------------------------------------------------------------
	private void saveSelectors(final ObjectFactory factory, final Model config) {
		final List<Selector> selectors = config.getSelectorList();
		
		for (final IGASelectorConfigurator selectorOperator : selectedSelectionOperators) {
			final Selector selector = factory.createSelector();
			selector.setType(selectorOperator.getName());
			final List<Property> selectorProperties = selector.getSelectorProperties();
			
			for (final Entry<String,String> entry : selectorOperator.getConfiguration().entrySet()) {
				final Property property = factory.createProperty();
				property.setKey(entry.getKey());
				property.setValue(entry.getValue());
				selectorProperties.add(property);
			}
			
			selectors.add(selector);
		}
	}
	
	//----------------------------------------------------------------------------------------------------
	private void saveOperators(final ObjectFactory factory, final Model config) {
		final List<Operator> operators = config.getOperatorList();
		
		for (final IGAOperatorConfigurator geneticOperator : selectedGeneticOperators) {
			final Operator operator = factory.createOperator();
			operator.setType(geneticOperator.getName());
			final List<Property> operatorProperties = operator.getOperatorProperties();
			
			for (final Entry<String,String> entry : geneticOperator.getConfiguration().entrySet()) {
				final Property property = factory.createProperty();
				property.setKey(entry.getKey());
				property.setValue(entry.getValue());
				operatorProperties.add(property);
			}
			
			operators.add(operator);
		}
	}
	
	//----------------------------------------------------------------------------------------------------
	private void saveChromosome(final ObjectFactory factory, final Model config) {
		final eu.crisis_economics.abm.dashboard.generated.Chromosome chromosome = factory.createChromosome();
		
		@SuppressWarnings("rawtypes")
		final Enumeration nodes = ((DefaultMutableTreeNode)chromosomeTree.getRoot()).children();
		while (nodes.hasMoreElements()) {
			final DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes.nextElement();
			saveParameterOrGene(node,factory,chromosome);
		}
		
		config.setChromosome(chromosome);
	}
	
	//----------------------------------------------------------------------------------------------------
	private void saveParameterOrGene(final DefaultMutableTreeNode node, final ObjectFactory factory, final Object parent) {
		final ParameterOrGene userObj = (ParameterOrGene) node.getUserObject();
		final ParameterInfo info = userObj.getInfo();
		
		if (userObj.isGene()) {
			final GeneInfo geneInfo = userObj.getGeneInfo();
			
			final eu.crisis_economics.abm.dashboard.generated.Gene gene = factory.createGene();
			gene.setName(info.getName());
			gene.setGeneType(geneInfo.getValueType());
			
			if (GeneInfo.INTERVAL.equals(geneInfo.getValueType())) {
				gene.setInteger(geneInfo.isIntegerVals());
				gene.setMin(geneInfo.isIntegerVals() ? new BigDecimal(geneInfo.getMinValue().longValue()) : new BigDecimal(geneInfo.getMinValue().doubleValue()));
				gene.setMax(geneInfo.isIntegerVals() ? new BigDecimal(geneInfo.getMaxValue().longValue()) : new BigDecimal(geneInfo.getMaxValue().doubleValue()));
			} else {
				final List<String> geneValues = gene.getGeneValueList();
				for (final Object value : geneInfo.getValueRange()) 
					geneValues.add(String.valueOf(value));
			}
			
			if (parent instanceof eu.crisis_economics.abm.dashboard.generated.Chromosome) {
				final eu.crisis_economics.abm.dashboard.generated.Chromosome chromosome = (eu.crisis_economics.abm.dashboard.generated.Chromosome) parent;
				chromosome.getGeneList().add(gene);
			} else if (parent instanceof SubmodelParameter) {
				final SubmodelParameter submodelParameter = (SubmodelParameter) parent;
				submodelParameter.getGeneList().add(gene);
			}
			
		} else if (info instanceof SubmodelInfo) {
			final SubmodelInfo sInfo = (SubmodelInfo) info;
			
			final SubmodelParameter parameter = factory.createSubmodelParameter();
			parameter.setName(info.getName());
			parameter.setType(sInfo.getActualType() == null ? "null" : sInfo.getActualType().getName());
			
			if (parent instanceof eu.crisis_economics.abm.dashboard.generated.Chromosome) {
				final eu.crisis_economics.abm.dashboard.generated.Chromosome chromosome = (eu.crisis_economics.abm.dashboard.generated.Chromosome) parent;
				chromosome.getSubmodelParameterList().add(parameter);
			} else if (parent instanceof SubmodelParameter) {
				final SubmodelParameter submodelParameter = (SubmodelParameter) parent;
				submodelParameter.getSubmodelParameterList().add(parameter);
			}
			
			if (node.getChildCount() > 0) {
				@SuppressWarnings("rawtypes")
				final Enumeration childNodes = node.children();
				while (childNodes.hasMoreElements()) {
					final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) childNodes.nextElement();
					saveParameterOrGene(childNode,factory,parameter);
 				}
			}
		} else {
			final Parameter parameter = factory.createParameter();
			parameter.setName(info.getName());
			parameter.setParameterType(ParameterType.CONSTANT);
			parameter.getContent().add(info.getValue().toString());
			
			if (parent instanceof eu.crisis_economics.abm.dashboard.generated.Chromosome) {
				final eu.crisis_economics.abm.dashboard.generated.Chromosome chromosome = (eu.crisis_economics.abm.dashboard.generated.Chromosome) parent;
				chromosome.getParameterList().add(parameter);
			} else if (parent instanceof SubmodelParameter) {
				final SubmodelParameter submodelParameter = (SubmodelParameter) parent;
				submodelParameter.getParameterList().add(parameter);
			}
		}
	}
	
	//----------------------------------------------------------------------------------------------------
	/**
	 * This method creates a list of {@link ParameterInfo} instances for the {@link #loadConfiguration(Model, List)} method. How the list of {@link ai.aitia.meme.paramsweep.batch.param.ParameterInfo} is converted 
	 * to {@link ParameterInfo} is copied from {@link Page_Parameters#createAndDisplayAParameterPanel} method.
	 * 
	 * @param config the object representing the XML file
	 * @param parameters the list of model parameters
	 * @throws ModelInformationException if {@link #loadConfiguration(Model, List)} throws an exception
	 */
	public void loadConfigurationFromBatchInfo(final Model config, final List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> parameters) throws ModelInformationException {
		final List<ParameterInfo> info = new ArrayList<ParameterInfo>();
		for (ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?> batchParameterInfo : parameters) {
			final ParameterInfo parameterInfo = InfoConverter.parameterInfo2ParameterInfo(batchParameterInfo);
			// prepare the parameterInfo for the param sweeps
			parameterInfo.setRuns(0);
			parameterInfo.setDefinitionType(ParameterInfo.CONST_DEF);
			parameterInfo.setValue(batchParameterInfo.getDefaultValue());
			info.add(parameterInfo);
		}
		
		loadConfiguration(config, info);
	}
	//----------------------------------------------------------------------------------------------------
	@Override
	public void loadConfiguration(final Model config, final List<ParameterInfo> parameters) throws ModelInformationException {
		final List<GeneralParameter> generalParameters = config.getGeneralParameterList();
		loadGeneralParameters(generalParameters);
		
		final List<Selector> selectors = config.getSelectorList();
		loadSelectors(selectors);
		
		final List<Operator> operators = config.getOperatorList();
		loadOperators(operators);
		
		loadChromosome(config.getChromosome(),parameters);
	}

	//----------------------------------------------------------------------------------------------------
	private void loadGeneralParameters(final List<GeneralParameter> generalParameters) {
		for (final GeneralParameter generalParameter : generalParameters) {
			if (ParameterTreeUtils.NUMBER_OF_TURNS_XML_NAME.equals(generalParameter.getName()))
				numberOfTurns = Double.parseDouble(generalParameter.getValue());
			else if (POPULATION_SIZE.equals(generalParameter.getName()))
				populationSize = Integer.parseInt(generalParameter.getValue());
			else if (POPULATION_RANDOM_SEED.equals(generalParameter.getName()))
				populationGenerationSeed = Integer.parseInt(generalParameter.getValue());
			else if (FIX_NUMBER_OF_GENERATIONS.equals(generalParameter.getName()))
				fixNumberOfGenerations = Boolean.parseBoolean(generalParameter.getValue());
			else if (NUMBER_OF_GENERATIONS.equals(generalParameter.getName()))
				numberOfGenerations = Integer.parseInt(generalParameter.getValue());
			else if (FITNESS_LIMIT_CRITERION.equals(generalParameter.getName()))
				fitnessLimitCriterion = Double.parseDouble(generalParameter.getValue());
			else if (OPTIMIZATION_DIRECTION.equals(generalParameter.getName()))
				optimizationDirection = FitnessFunctionDirection.valueOf(generalParameter.getValue());
			else if (FITNESS_FUNCTION.equals(generalParameter.getName()))
				loadSelectedFitnessFunction(generalParameter.getValue());
			else if (FITNESS_STATISTICS.equals(generalParameter.getName())) {
				loadSelectedFitnessStatistics(generalParameter.getValue());
			} else if (FITNESS_TIMESERIES_LENGTH.equals(generalParameter.getName())) {
				fitnessTimeSeriesLength = Integer.parseInt(generalParameter.getValue());
			}
		}
	}

	private void loadSelectedFitnessStatistics(final String fitnessStatisticsName) {
		for (IStatisticsPlugin statistics : fitnessStatistics) {
			if (statistics.getName().equals(fitnessStatisticsName)) {
				selectedFitnessStatistics = statistics;
				break;
			}
		}
	}

	//----------------------------------------------------------------------------------------------------
	private void loadSelectedFitnessFunction(final String identifier) {
		for (final RecordableInfo function : fitnessFunctions) {
			//TODO: support 'the fitness function is part of a submodel parameter' case too
			if (identifier.equals(function.getName())) {
				selectedFunction = function;
				break;
			}
		}
	}
	
	//----------------------------------------------------------------------------------------------------
	private void loadSelectors(final List<Selector> selectorList) throws ModelInformationException {
		selectedSelectionOperators.clear();
		
		for (final Selector selector : selectorList) {
			final String selectorType = selector.getType();
			final IGASelectorConfigurator matchedConfigurator = findSelectorConfigurator(selectorType);
			final Map<String,String> config = convertPropertiesToMap(selector.getSelectorProperties());
			matchedConfigurator.setConfiguration(config);
			selectedSelectionOperators.add(matchedConfigurator);
		}
	}

	//----------------------------------------------------------------------------------------------------
	private IGASelectorConfigurator findSelectorConfigurator(final String selectorType) throws ModelInformationException {
		for (final IGASelectorConfigurator configurator : selectors) {
			if (configurator.getName().equals(selectorType))
				return configurator;
		}
		
		throw new ModelInformationException("Unrecognized selector: " + selectorType + ".");
	}
	
	//----------------------------------------------------------------------------------------------------
	private void loadOperators(final List<Operator> operatorList) throws ModelInformationException {
		selectedGeneticOperators.clear();
		
		for (final Operator operator : operatorList) {
			final String operatorType = operator.getType();
			final IGAOperatorConfigurator matchedConfigurator = findOperatorConfigurator(operatorType);
			final Map<String,String> config = convertPropertiesToMap(operator.getOperatorProperties());
			matchedConfigurator.setConfiguration(config);
			selectedGeneticOperators.add(matchedConfigurator);
		}
	}

	//----------------------------------------------------------------------------------------------------
	private IGAOperatorConfigurator findOperatorConfigurator(final String operatorType) throws ModelInformationException {
		for (final IGAOperatorConfigurator configurator : geneticOperators) {
			if (configurator.getName().equals(operatorType))
				return configurator;
		}
		
		throw new ModelInformationException("Unrecognized genetic operator: " + operatorType + ".");
	}
	
	//----------------------------------------------------------------------------------------------------
	private void loadChromosome(final eu.crisis_economics.abm.dashboard.generated.Chromosome chromosome, final List<ParameterInfo> parameters) 
																																	throws ModelInformationException {
		final int childCount = getChromosomeTree().getChildCount(getChromosomeTree().getRoot());
		if (childCount > 0) {
			final DefaultMutableTreeNode root = (DefaultMutableTreeNode) getChromosomeTree().getRoot();
			for (int i = childCount - 1; i >= 0; --i) {
				final MutableTreeNode node = (MutableTreeNode) root.getChildAt(i);
				getChromosomeTree().removeNodeFromParent(node); 
			}
			genes = null;
		}
		
		final DefaultMutableTreeNode root = (DefaultMutableTreeNode) getChromosomeTree().getRoot();
		loadChromosomeTreeLevel(root,parameters,chromosome.getParameterList(),chromosome.getSubmodelParameterList(),chromosome.getGeneList());
	}
	
	//----------------------------------------------------------------------------------------------------
	private void loadChromosomeTreeLevel(final DefaultMutableTreeNode parent, final List<ParameterInfo> infos, final List<Parameter> parameters, 
										 final List<SubmodelParameter> submodelParameters, final List<eu.crisis_economics.abm.dashboard.generated.Gene> genes)
												 																					throws ModelInformationException {
		
		for (final eu.crisis_economics.abm.dashboard.generated.Gene gene : genes) {
			final ParameterInfo info = findParameterInfo(infos,gene.getName());
			if (info == null)
				throw new ModelInformationException("Unrecognized parameter: " + gene.getName() + ".");
			
			//initializeParent(info,parent);
			final ParameterOrGene userObj = initializeUserObjectFromGene(gene,info);
			final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObj);
			getChromosomeTree().insertNodeInto(newNode,parent,parent.getChildCount());
			
		}
		
		
		for (final SubmodelParameter parameter : submodelParameters) {
			final ParameterInfo info = findParameterInfo(infos,parameter.getName());
			if (info == null)
				throw new ModelInformationException("Unrecognized parameter: " + parameter.getName() + ".");
			
			//initializeParent(info,parent);
			final SubmodelInfo submodelInfo = (SubmodelInfo) info;
			final String strType = parameter.getType();
			try {
				final Class<?> clazz = Class.forName(strType,true,currentModelHandler.getCustomClassLoader());
				if (submodelInfo.getActualType() == null || ! strType.equals(submodelInfo.getActualType().getName())) {
					submodelInfo.setActualType(clazz, null);
				}
				
				final ParameterOrGene userObj = new ParameterOrGene(info);
				final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObj);
				getChromosomeTree().insertNodeInto(newNode,parent,parent.getChildCount());
				
				final List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> batchSubparameters = ParameterTreeUtils.fetchSubparameters(currentModelHandler,
																																			 submodelInfo);
				final List<ParameterInfo> subparameters = new ArrayList<ParameterInfo>(batchSubparameters.size());
				for (final ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?> parameterInfo : batchSubparameters) 
					subparameters.add(InfoConverter.parameterInfo2ParameterInfo(parameterInfo));
				
				loadChromosomeTreeLevel(newNode,subparameters,parameter.getParameterList(),parameter.getSubmodelParameterList(),parameter.getGeneList());
			} catch (final ClassNotFoundException e) {
				throw new ModelInformationException("Invalid actual type " + strType + " for submodel parameter: " + submodelInfo.getName(),e);
			}	
		}
		
		for (final Parameter parameter : parameters) {
			final ParameterInfo info = findParameterInfo(infos,parameter.getName());
			if (info == null)
				throw new ModelInformationException("Unrecognized parameter: " + parameter.getName() + ".");
			
			//initializeParent(info,parent);
			info.setDefinitionType(ParameterInfo.CONST_DEF);
			String parameterValue = ParameterUtil.getParameterValue(parameter);
			Object value;
			if (info.getType().equals("File") && parameterValue == null){
				value = new File("");
			} else {
				value = ParameterInfo.getValue(parameterValue,info.getJavaType());
			}
			info.setValue(value);
			
			final ParameterOrGene userObj = new ParameterOrGene(info);
			final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObj);
			getChromosomeTree().insertNodeInto(newNode,parent,parent.getChildCount());
		}
		
	}

	//----------------------------------------------------------------------------------------------------
	private ParameterInfo findParameterInfo(final List<ParameterInfo> parameters, final String name) {
		for (final ParameterInfo info : parameters) {
			if (name.equals(info.getName()))
				return info;
		}
		
		return null;
	}
	
	//----------------------------------------------------------------------------------------------------
//	private void initializeParent(final ParameterInfo parameterInfo, final DefaultMutableTreeNode parent) {
//		if (parent.getUserObject() != null && parameterInfo instanceof ISubmodelGUIInfo) {
//			final ISubmodelGUIInfo sgi = (ISubmodelGUIInfo) parameterInfo;
//			final ParameterOrGene parentUserObject = (ParameterOrGene) parent.getUserObject();
//			final SubmodelInfo parentInfo = (SubmodelInfo) parentUserObject.getInfo();
//			sgi.setParentValue(parentInfo.getActualType());
//		}
//	}
	
	//----------------------------------------------------------------------------------------------------
	private ParameterOrGene initializeUserObjectFromGene(final eu.crisis_economics.abm.dashboard.generated.Gene gene, final ParameterInfo info) {
		if (GeneInfo.INTERVAL.equals(gene.getGeneType())) {
			final boolean integerInterval = gene.isInteger();
			return integerInterval ? new ParameterOrGene(info,gene.getMin().longValue(),gene.getMax().longValue()) :
									 new ParameterOrGene(info,gene.getMin().doubleValue(),gene.getMax().doubleValue());
		} else {
			final List<Object> valueList = new ArrayList<Object>(gene.getGeneValueList().size());
			
			for (final String geneValue : gene.getGeneValueList()) 
				valueList.add(parseListElement(info.getJavaType(),geneValue));
			return new ParameterOrGene(info,valueList);
		}
	}

	//----------------------------------------------------------------------------------------------------
	private Map<String,String> convertPropertiesToMap(final List<Property> properties) {
		final Map<String,String> result = new HashMap<String,String>();
		for (final Property property : properties) 
			result.put(property.getKey(),property.getValue());
		
		return result;
	}
	
	//----------------------------------------------------------------------------------------------------
	private Object parseListElement(final Class<?> type, final String strValue) {
		if (type.equals(Double.class) || type.equals(Float.class) || type.equals(Double.TYPE) || type.equals(Float.TYPE))
			return Double.parseDouble(strValue);
		
		return Long.parseLong(strValue);
	}

	//----------------------------------------------------------------------------------------------------
	public void addFitnessFunction(final RecordableInfo fitnessFunction) {
		fitnessFunctions.add((RecordableInfo) fitnessFunction);
		
		for (final ModelListener listener : listeners) 
			listener.fitnessFunctionAdded();
	}
	
	//----------------------------------------------------------------------------------------------------
	public void removeAllFitnessFunctions() {
		fitnessFunctions.clear();
		fitnessFunctions.add(new RecordableInfo("Please select a function!",Object.class,"Please select a function!"));
		
		for (final ModelListener listener : listeners) 
			listener.fitnessFunctionsRemoved();
	}
	
	//----------------------------------------------------------------------------------------------------
	public List<IGASelectorConfigurator> getSelectionOperators() { return selectors; }
	public List<RecordableInfo> getFitnessFunctions() { return fitnessFunctions; }
	@Override
	public List<IStatisticsPlugin> getFitnessStatistics() {	return fitnessStatistics; }
	public List<IGAOperatorConfigurator> getGeneticOperators() { return geneticOperators; }
	public double getNumberOfTurns() { return numberOfTurns; }
	public int getPopulationSize() { return populationSize; }
	public int getPopulationRandomSeed() { return populationGenerationSeed;	}
	public int getNumberOfGenerations() { return numberOfGenerations; }
	public double getFitnessLimitCriterion() { return fitnessLimitCriterion; }
	public FitnessFunctionDirection getFitnessFunctionDirection() { return optimizationDirection; }
	public RecordableInfo getSelectedFitnessFunction() { return selectedFunction; }
	@Override
	public IStatisticsPlugin getSelectedFitnessStatistics() { return selectedFitnessStatistics;	}
	@Override
	public int getFitnessTimeSeriesLength() { return fitnessTimeSeriesLength; }
	public List<IGAOperatorConfigurator> getSelectedGeneticOperators() { return selectedGeneticOperators; }
	public List<IGASelectorConfigurator> getSelectedSelectionOperators() { return selectedSelectionOperators; }
	public boolean isFixNumberOfGenerations() { return fixNumberOfGenerations; }
	public File getWorkspace() { return workspace; }

	//----------------------------------------------------------------------------------------------------
	public void setNumberOfTurns(final double numberOfTurns) { this.numberOfTurns = numberOfTurns; }
	public void setPopulationSize(final int populationSize) { this.populationSize = populationSize; }
	public void setPopulationRandomSeed(final int seed) { this.populationGenerationSeed = seed; }
	public void setNumberOfGenerations(final int numberOfGenerations) { this.numberOfGenerations = numberOfGenerations; }
	public void setFitnessLimitCriterion(final double fitnessLimit) { this.fitnessLimitCriterion = fitnessLimit; }
	public void setFitnessFunctionDirection(final FitnessFunctionDirection direction) { this.optimizationDirection = direction; }
	@Override
	public void setSelectedFitnessFunction(final RecordableInfo fitnessFunction) { this.selectedFunction = (RecordableInfo) fitnessFunction; }
	public void setSelectedFitnessFunction(final String fitnessFunctionName){loadSelectedFitnessFunction(fitnessFunctionName);}
	@Override
	public void setSelectedFitnessStatistics(IStatisticsPlugin fitnessStatistics) { this.selectedFitnessStatistics = fitnessStatistics; }
	public void setSelectedFitnessStatistics(final String fitnessStatisticsName) {loadSelectedFitnessStatistics(fitnessStatisticsName);}
	@Override
	public void setFitnessTimeSeriesLength(int fitnessTimeSeriesLength) { this.fitnessTimeSeriesLength = fitnessTimeSeriesLength; }
	public void setFixNumberOfGenerations(final boolean fixNumberOfGenerations) { this.fixNumberOfGenerations = fixNumberOfGenerations; }
	public void setWorkspace(final File workspace) { this.workspace = workspace; }

	//----------------------------------------------------------------------------------------------------
	public DefaultTreeModel getChromosomeTree() {
		if (chromosomeTree == null)
			chromosomeTree = new DefaultTreeModel(new DefaultMutableTreeNode());
		return chromosomeTree;
	}
	//----------------------------------------------------------------------------------------------------
	public void setSelectedSelectionOperator(final IGASelectorConfigurator operator) {
		if (operator != null && !selectedSelectionOperators.contains(operator)) 
			selectedSelectionOperators.add(operator);
	}

	//----------------------------------------------------------------------------------------------------
	public boolean unsetSelectedSelectionOperator(final IGASelectorConfigurator operator) {
		return selectedSelectionOperators.remove(operator);
	}

	//----------------------------------------------------------------------------------------------------
	public void setSelectedGeneticOperator(final IGAOperatorConfigurator operator) {
		if (operator != null && !selectedGeneticOperators.contains(operator)) 
			selectedGeneticOperators.add(operator);
	}

	//----------------------------------------------------------------------------------------------------
	public boolean unsetSelectedGeneticOperator(final IGAOperatorConfigurator operator) {
		return selectedGeneticOperators.remove(operator);
	}
	
	//----------------------------------------------------------------------------------------------------
	public boolean addModelListener(final ModelListener listener) {
		return listeners.add(listener);
	}

	//----------------------------------------------------------------------------------------------------
	public boolean removeModelListener(final ModelListener listener) {
		return listeners.remove(listener);
	}

	public void addSubmodelParameter(final SubmodelInfo sInfo, final DefaultMutableTreeNode node) throws ModelInformationException {
		if (sInfo.getActualType() == null) {
			if (node.getChildCount() > 0) {
				node.removeAllChildren();
				chromosomeTree.nodeStructureChanged(node);
			}
			
			return;
		}
		
		final List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> subparameters = ParameterTreeUtils.fetchSubparameters(
																											currentModelHandler,sInfo);
		
		final List<ParameterInfo> convertedSubparameters = new ArrayList<ParameterInfo>(subparameters.size());
		for (ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?> parameterInfo : subparameters) {
			final ParameterInfo converted = InfoConverter.parameterInfo2ParameterInfo(parameterInfo);
			converted.setRuns(0);
			convertedSubparameters.add(converted);
		}
		Collections.sort(convertedSubparameters);
		
		if (node.getChildCount() > 0) {
			node.removeAllChildren();
			chromosomeTree.nodeStructureChanged(node);
		}
		
		for (final ParameterInfo pInfo : convertedSubparameters) {
			final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(new ParameterOrGene(pInfo));
			chromosomeTree.insertNodeInto(newNode,node,node.getChildCount());
			
			if (pInfo instanceof SubmodelInfo) {
				final SubmodelInfo ssInfo = (SubmodelInfo) pInfo;
				if (ssInfo.getActualType() != null)
					addSubmodelParameter(ssInfo, newNode);
			}

		}
	}
	
	//----------------------------------------------------------------------------------------------------
	public void addParameter(final ParameterOrGene parameterOrGene) throws ModelInformationException {
		if (chromosomeTree == null) { 
			chromosomeTree = new DefaultTreeModel(new DefaultMutableTreeNode());
			genes = null;
		}
		
		final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(parameterOrGene);
		final DefaultMutableTreeNode root = (DefaultMutableTreeNode) chromosomeTree.getRoot();
		chromosomeTree.insertNodeInto(newNode,root,root.getChildCount());
		
		if (parameterOrGene.info instanceof SubmodelInfo) {
			final SubmodelInfo ssInfo = (SubmodelInfo) parameterOrGene.info;
			if (ssInfo.getActualType() != null)
				addSubmodelParameter(ssInfo, newNode);
		}
		
		for (final ModelListener listener : listeners) 
			listener.parameterAdded();
	}
	
	//----------------------------------------------------------------------------------------------------
	public void removeAllParameters() {
		if (chromosomeTree == null)  
			chromosomeTree = new DefaultTreeModel(new DefaultMutableTreeNode());
		else 
			chromosomeTree.setRoot(new DefaultMutableTreeNode());
		
		genes = null;
		
		for (final ModelListener listener : listeners) 
			listener.parametersRemoved();
	}
	
	//----------------------------------------------------------------------------------------------------
	public String[] checkGAModel() {
		final List<String> errors = new ArrayList<String>();
		
		final RecordableInfo invalid = new RecordableInfo("Please select a function!",Object.class,"Please select a function!");
		if (selectedFunction.equals(invalid))
			errors.add("Please select a valid fitness function!");
		
		if (selectedSelectionOperators.isEmpty())
			errors.add("Please select at least one selector!");
		
		if (selectedGeneticOperators.isEmpty())
			errors.add("Please select at least one genetic operator!");

		if (calculateNumberOfGenes() == 0)
			errors.add("Please specify at least one gene!");
		
		return errors.size() == 0 ? null : errors.toArray(new String[0]);
	}
	
	//----------------------------------------------------------------------------------------------------
	public boolean canBeFitnessFunction(final RecordableInfo candidate) {
		return acceptableReturnTypesForFitnessFunction.contains(candidate.getType());
	}

	//----------------------------------------------------------------------------------------------------
	public int getNumberOfIterations() {
		if (fixNumberOfGenerations)
			return numberOfGenerations - 1;
		else
			return Integer.MAX_VALUE;
	}
	
	//----------------------------------------------------------------------------------------------------
	public int getCurrentIteration() {
		return iterationCounter;
	}
	
	//----------------------------------------------------------------------------------------------------
	public boolean noMoreIteration() {
		if (fixNumberOfGenerations) 
			return iterationCounter > getNumberOfGenerations();
		else 
			return reachFitnessLimit;
	}
	
	//----------------------------------------------------------------------------------------------------
	public ParameterTree getNextParameterTree(final IParameterSweepResultReader reader) {
		if (iterationCounter == 1) // first time
			initPopFile();
		
		iterationCounter++;
		
		((ResultFileFitnessFunction)gaConfiguration.getFitnessFunction()).setCurrentReader(reader);
		
		printPopulation(iterationCounter - 1);
		
		if (!fixNumberOfGenerations) {
			final IChromosome fittestChromosome = genotype.getFittestChromosome();
			final double fitnessValue = gaConfiguration.getFitnessFunction().getFitnessValue(fittestChromosome);
			reachFitnessLimit = optimizationDirection == FitnessFunctionDirection.MINIMIZE ? fitnessValue <= fitnessLimitCriterion 
																						   : fitnessValue >= fitnessLimitCriterion;
																						   
			if (reachFitnessLimit) 
				return null;
		} 
		
		if (iterationCounter > getNumberOfGenerations()) return null;
		
		genotype.evolve();
		
		final ParameterTree nextTree = new ParameterTree();
		final Population descendants = genotype.getPopulation();

		for (int i = 0; i < paramList.size(); ++i) {
			final ParameterInfo paramInfo = paramList.get(i);
			paramInfo.setRuns(1); // Run is always 1

			final List<Object> values = new ArrayList<Object>();

			final int genIdx = whichGene(calculateName(paramInfo));
			for (int j = 0; j < descendants.size(); ++j) {
				IChromosome chromosome = descendants.getChromosome(j);
				if ( chromosome.getFitnessValueDirectly() > -1.0D ) {
					if (genIdx >= 0) {
						final String strValue = String.valueOf(chromosome.getGene(genIdx).getAllele());
						values.add(ParameterInfo.getValue(strValue,paramInfo.getType()));
						paramInfo.setDefinitionType(ParameterInfo.LIST_DEF);
					} else {
						values.add(paramInfo.getValue());
						paramInfo.setDefinitionType(ParameterInfo.CONST_DEF);
					}
				}
			}

			paramInfo.setValues(values);

			nextTree.addNode(InfoConverter.parameterInfo2ParameterInfo(paramInfo));
		}

		return nextTree;
	}

	//----------------------------------------------------------------------------------------------------
	public boolean alterParameterTree(final IIntelliContext ctx) {

		// create initial population
		final DefaultMutableTreeNode root = ctx.getParameterTreeRootNode();
		final DefaultMutableTreeNode newRoot = getAlteredParameterTreeRootNode(ctx);
		root.removeAllChildren();
		final int count = newRoot.getChildCount();

		for (int i = 0; i < count; ++i) 
			root.add((DefaultMutableTreeNode) newRoot.getChildAt(0));
		
		return true;
	}

	//----------------------------------------------------------------------------------------------------
	protected DefaultMutableTreeNode getAlteredParameterTreeRootNode(final IIntelliContext ctx) {
		genotype = generateInitialPopulation(ctx);

		final Population descendants = genotype.getPopulation();
		final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Parameter file");

		paramList = ctx.getParameters();
		for (int i = 0; i < paramList.size(); ++i) {
			final ParameterInfo paramInfo = paramList.get(i);
			paramInfo.setRuns(1); // Run is always 1

			final List<Object> values = new ArrayList<Object>();

			final int genIdx = whichGene(calculateName(paramInfo));
			for (int j = 0; j < populationSize; ++j) {
				if (genIdx >= 0) {
					final String strValue = String.valueOf(descendants.getChromosome(j).getGene(genIdx).getAllele());
					values.add(ParameterInfo.getValue(strValue,paramInfo.getType()));
					paramInfo.setDefinitionType(ParameterInfo.LIST_DEF);
				} else {
					values.add(paramInfo.getValue());
					paramInfo.setDefinitionType(ParameterInfo.CONST_DEF);
				}
			}

			paramInfo.setValues(values);

			// add and save the node
			root.add(new DefaultMutableTreeNode(paramInfo));
		}

		return root;
	}

	//----------------------------------------------------------------------------------------------------
	/**
	 * Searches the selected genes list for the specified parameter, and returns
	 * its index.
	 * 
	 * @param name
	 *            the parameter's name that is searched in the selected genes list
	 * @return the index of the gene that represents the given parameter, or -1
	 *         if the parameter is not selected as a gene
	 */
	private int whichGene(final String name) {
		for (int i = 0; i < genes.size(); i++) {
			final GeneInfo geneInfo = genes.get(i);
			if (geneInfo.getName().equals(name)) 
				return i;
		}

		return -1;
	}
	
	//----------------------------------------------------------------------------------------------------
	private List<GeneInfo> getSelectedGenes() {
		if (genes == null) {
			genes = new ArrayList<GeneInfo>();
			
			final DefaultMutableTreeNode root = (DefaultMutableTreeNode) chromosomeTree.getRoot();
			@SuppressWarnings("rawtypes")
			final Enumeration nodes = root.preorderEnumeration();
			nodes.nextElement();
			while (nodes.hasMoreElements()) {
				final DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes.nextElement();
				final ParameterOrGene userObj = (ParameterOrGene) node.getUserObject();
				if (userObj.isGene()) {
					convertGeneName(userObj);
					genes.add(userObj.getGeneInfo());
				}
			}
		}
		
		return genes;
	}
	
	//----------------------------------------------------------------------------------------------------
	private void convertGeneName(final ParameterOrGene param) {
		ParameterInfo info = param.getInfo();
		final String newName = calculateName(info);  
		param.getGeneInfo().setName(newName);
	}
	
	//----------------------------------------------------------------------------------------------------
	private String calculateName(final ParameterInfo info) {
		ParameterInfo _info = info;
		
		if (_info != null) {
			String resultName = _info.getName();
			while ((_info instanceof ISubmodelGUIInfo) && ((ISubmodelGUIInfo)_info).getParent() != null) {
				_info = ((ISubmodelGUIInfo)_info).getParent();
				resultName = _info.getName() + "#" + resultName;
			}
			
			return resultName;
		}
		
		return null;
	}

	//----------------------------------------------------------------------------------------------------
	@SuppressWarnings("serial")
	protected Genotype generateInitialPopulation(final IIntelliContext context) {
		try {
			iterationCounter = 1;
			genes = null;
			
			Configuration.reset();
			gaConfiguration = new DefaultConfiguration();
			Configuration.reset();
			gaConfiguration.removeNaturalSelectors(true);
			gaConfiguration.removeNaturalSelectors(false);
			gaConfiguration.getGeneticOperators().clear();
			gaConfiguration.setAlwaysCaculateFitness(false);
			gaConfiguration.setPreservFittestIndividual(true);
			gaConfiguration.setKeepPopulationSizeConstant(false);
			gaConfiguration.setFitnessEvaluator(new FitnessEvaluator() {
				public boolean isFitter(IChromosome a_chrom1, IChromosome a_chrom2) {
					return isFitter(a_chrom1.getFitnessValue(),a_chrom2.getFitnessValue());				}
				
				public boolean isFitter(double a_fitness_value1, double a_fitness_value2) {
					return optimizationDirection == FitnessFunctionDirection.MINIMIZE ? a_fitness_value1 < a_fitness_value2 : a_fitness_value1 > a_fitness_value2;
				}
			});
			gaConfiguration.setFitnessFunction(new ResultFileFitnessFunction());
			gaConfiguration.setRandomGenerator(new RandomGenerator() {
				private static final long serialVersionUID = 1L;

				final MersenneTwister randomGenerator = new MersenneTwister(populationGenerationSeed);

				public long nextLong() {
					return randomGenerator.nextLong();
				}

				public int nextInt(final int a_ceiling) {
					return randomGenerator.nextInt(a_ceiling);
				}

				public int nextInt() {
					return randomGenerator.nextInt();
				}

				public float nextFloat() {
					return randomGenerator.nextFloat();
				}

				public double nextDouble() {
					return randomGenerator.nextDouble();
				}

				public boolean nextBoolean() {
					return randomGenerator.nextBoolean();
				}
			});
			
			final Gene[] initialGenes = new Gene[getSelectedGenes().size()];
			int i = 0;
			for (final GeneInfo geneInfo : getSelectedGenes()) {
				if (GeneInfo.INTERVAL.equals(geneInfo.getValueType())) {
					if (geneInfo.isIntegerVals()) {
						initialGenes[i] = new IdentifiableLongGene(geneInfo.getName(),gaConfiguration,geneInfo.getMinValue().longValue(),
																	  geneInfo.getMaxValue().longValue());
					} else {
						initialGenes[i] = new IdentifiableDoubleGene(geneInfo.getName(),gaConfiguration,geneInfo.getMinValue().doubleValue(),
																	 geneInfo.getMaxValue().doubleValue());
					}
				} else {
					final IdentifiableListGene listGene = new IdentifiableListGene(geneInfo.getName(),gaConfiguration);
					final List<Object> values = geneInfo.getValueRange();
					for (final Object value : values) {
						listGene.addAllele(value);
					}
					listGene.setAllele(values.get(0));
					initialGenes[i] = listGene;
				}

				i++;
			}
			gaConfiguration.setMinimumPopSizePercent(100);
			gaConfiguration.setBreeder(new DashboardBreeder());
			
			final Chromosome sampleChromosome = new Chromosome(gaConfiguration,initialGenes);
			gaConfiguration.setSampleChromosome(sampleChromosome);
			gaConfiguration.setPopulationSize(populationSize);

			for (final IGAOperatorConfigurator operator : selectedGeneticOperators) 
				gaConfiguration.addGeneticOperator(operator.getConfiguredOperator(gaConfiguration));
			
			for (IGASelectorConfigurator selectorConfig : selectedSelectionOperators)
				gaConfiguration.addNaturalSelector(selectorConfig.getSelector(gaConfiguration),true);

			gaConfiguration.addNaturalSelector(new StandardPostSelectorFixed(gaConfiguration),false);

			final Genotype initialGenotype = Genotype.randomInitialGenotype(gaConfiguration);
			
			return initialGenotype;
		} catch (final InvalidConfigurationException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	//----------------------------------------------------------------------------------------------------
	/**
	 * Prints the header of the population file.
	 */
	protected void initPopFile() {
		// create backup file when file already exists
		final File file = new File(workspace,populationFileName + ext);
		if (file.exists()) {
			for (int i = 1; i <= 1000; ++i) {
				final File newFile = new File(workspace,populationFileName + bckExt + i + ext);
				if (!newFile.exists() || i == 1000) {
					file.renameTo(newFile);
					break;
				}
			}
		}
		
		try {
			final PrintWriter popWriter = new PrintWriter(new FileWriter(file,true));

			popWriter.print("#pop");
			for (int i = 0; i < genes.size(); ++i) 
				popWriter.print(";" + genes.get(i).getName());
			
			popWriter.print(";fitness");
			popWriter.println();
			popWriter.close();
		} catch (final IOException e) {
			log.error("Cannot print population to file:\n",e);
		}
	}
	
	//----------------------------------------------------------------------------------------------------
	protected void printPopulation(final int iteration) {
		try {
			final PrintWriter popWriter = new PrintWriter(new FileWriter(new File(workspace,populationFileName + ext),true));
			
			Population population = genotype.getPopulation();
			for (int i = 0;i < population.size();++i) {
				popWriter.print(iteration);
				for (int j = 0;j < population.getChromosome(i).size(); ++j) 
					popWriter.print(";" + population.getChromosome(i).getGene(j).getAllele().toString());
				
				popWriter.print(";" + population.getChromosome(i).getFitnessValue());
				popWriter.println();
			}
			popWriter.close();
		} catch (final IOException e) {
			log.error( "Cannot print population to file.",e);
		}
	}

	
	//----------------------------------------------------------------------------------------------------
	private int calculateNumberOfGenes() {
		int count = 0;
		
		final DefaultMutableTreeNode root = (DefaultMutableTreeNode) chromosomeTree.getRoot();
		@SuppressWarnings("rawtypes")
		final Enumeration nodes = root.breadthFirstEnumeration();
		nodes.nextElement();
		
		while (nodes.hasMoreElements()) {
			final DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes.nextElement();
			final ParameterOrGene userObj = (ParameterOrGene) node.getUserObject();
			if (userObj.isGene())
				count++;
		}
		
		return count;
	}
	
	//====================================================================================================
	// not implemented methods
	
	//----------------------------------------------------------------------------------------------------
	public void setRecordableVariables(final DefaultMutableTreeNode root) {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public String settingsOK(final DefaultMutableTreeNode recorders) {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public void setParameterTreeRoot(final DefaultMutableTreeNode root) {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}


	//----------------------------------------------------------------------------------------------------
	public boolean getReadyStatus() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public String getReadyStatusDetail() { 
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public JPanel getSettingsPanel(final IIntelliContext ctx) {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public void save(final Node node) {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public void load(final IIntelliContext context, final Element element) throws WizardLoadingException {
		throw new UnsupportedOperationException(); // intentionally not implemented
	} 

	//----------------------------------------------------------------------------------------------------
	public void invalidatePlugin() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public String getDescription() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public int getMethodType() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	//----------------------------------------------------------------------------------------------------
	public boolean isImplemented() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}
	
	//----------------------------------------------------------------------------------------------------
	public String getLocalizedName() {
		throw new UnsupportedOperationException(); // intentionally not implemented
	}

	@Override
	public RecordableInfo getSelectedFitnessStatisticsInfo() {
		final RecordableInfo selectedFitnessFunction = getSelectedFitnessFunction();
		IStatisticsPlugin selectedFitnessStatistics = getSelectedFitnessStatistics();
		int fitnessTimeSeriesLength = getFitnessTimeSeriesLength();
		// this seems to be useless, as the GASearchPanelModel can directly return the selected fitness function
//		final List<RecordableInfo> recordables = recorder.getRecordables();
//		for (final RecordableInfo recordableInfo : recordables) {
//			if (recordableInfo.equals(selectedFitnessFunction))
//				return recordableInfo;
//		}
		
		if (selectedFitnessStatistics.equals(GASearchHandler.noStatistics)){
			return selectedFitnessFunction;
		}
		
		// create a time series of the specified length using the selected fitness function
		Operator_TimeSeries timeSeriesOperator = new Operator_TimeSeries();
		OperatorsInfoGenerator timeSeriesInfoGenerator = new OperatorsInfoGenerator(timeSeriesOperator);
		
		List<Object> parameters = new ArrayList<Object>();
		
		MemberInfo fitnessMemberInfo = new MemberInfo(selectedFitnessFunction.getAccessibleName(), selectedFitnessFunction.getType().getSimpleName(), selectedFitnessFunction.getType());

		parameters.add(fitnessMemberInfo);
		parameters.add(fitnessTimeSeriesLength);
		
		OperatorGeneratedMemberInfo timeSeriesMemberInfo = timeSeriesInfoGenerator.generateInfoObject("timeseries", parameters.toArray());
		List<String> errors = timeSeriesInfoGenerator.getError();
		if (errors != null && errors.size() > 0) {
			throw new IllegalStateException("Could not create the timeseries of the last '" + selectedFitnessFunction.getName() + "' values!\n" + Utils.join(errors,"\n"));
		}
		timeSeriesMemberInfo.setGeneratorName(timeSeriesOperator.getClass().getName());
		timeSeriesMemberInfo.setBuildingBlock(parameters);
		
		// apply the selected statistics on the timeseries
		IStatisticInfoGenerator statisticsInfoGenerator = new StatisticsInfoGenerator(selectedFitnessStatistics);
		parameters = new ArrayList<Object>(selectedFitnessStatistics.getNumberOfParameters());
		List<Object> collectionParameter = new ArrayList<Object>();
		collectionParameter.add(timeSeriesMemberInfo);
		parameters.add(collectionParameter);
		SimpleGeneratedMemberInfo statMemberInfo = statisticsInfoGenerator.generateInfoObject(selectedFitnessStatistics.getName() + "_last" + fitnessTimeSeriesLength + "_" + selectedFitnessFunction.getName(), parameters.toArray());
		errors = statisticsInfoGenerator.getError();
		if (errors != null && errors.size() > 0) {
			throw new IllegalStateException("Could not create the specified statistics (" + selectedFitnessStatistics.getName() + ") of '" + selectedFitnessFunction.getName() + "'!\n" + Utils.join(errors,"\n"));
		}
		statMemberInfo.clearBuildingBlocks();
		statMemberInfo.setGeneratorName(selectedFitnessStatistics.getClass().getName());
		
		List<MemberInfo> l = new ArrayList<MemberInfo>(1);
		l.add(timeSeriesMemberInfo);
		statMemberInfo.addBuildingBlock(l);
		
		String alias = statMemberInfo.getName().endsWith("()") ? statMemberInfo.getName().substring(0, statMemberInfo.getName().length() - 2) : statMemberInfo.getName();
		RecordableElement stdRecordableElement = new RecordableElement(statMemberInfo, alias); 
		
		
		return InfoConverter.convertRecordableElement2RecordableInfo(stdRecordableElement);
	}

	
	//====================================================================================================
	// nested classes
	
	//----------------------------------------------------------------------------------------------------
	@SuppressWarnings("serial")
	private class ResultFileFitnessFunction extends FitnessFunction {
		
		//====================================================================================================
		// members
		
		private IParameterSweepResultReader currentReader;
		
		//====================================================================================================
		// methods
		
		//----------------------------------------------------------------------------------------------------
		public void setCurrentReader(final IParameterSweepResultReader currentReader) { this.currentReader = currentReader; } 

		//----------------------------------------------------------------------------------------------------
		protected double evaluate(final IChromosome a_subject) {
			if (currentReader == null)
				throw new IllegalStateException("Fitness function evaluation cannot access the results.");
			
			final List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> combination = createCombinationFromChromosome(a_subject);
			try {
				final List<ResultValueInfo> infos = currentReader.getResultValueInfos(getSelectedFitnessStatisticsInfo(),combination);
				if (infos == null || infos.size() == 0) {
					return 1e19; // dirty hack to make Ross going; 
				}
				
				if(Double.isInfinite(getFitnessValue(infos).doubleValue())){
					return Double.MAX_VALUE;
				}
				if(Double.isNaN(getFitnessValue(infos).doubleValue())){
					return 1e19;
				}
				return getFitnessValue(infos).doubleValue();
			} catch (final ReadingException e) {
				throw new RuntimeException(e);
			}
		}

		//----------------------------------------------------------------------------------------------------
		private List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> createCombinationFromChromosome(final IChromosome a_subject) {
			final List<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>> combination = 
																	new ArrayList<ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>>(a_subject.getGenes().length);
			
			for (int i = 0;i < a_subject.getGenes().length;++i) {
				final IIdentifiableGene geneWithId = (IIdentifiableGene) a_subject.getGene(i);
				final GeneInfo geneInfo = genes.get(whichGene(geneWithId.getId()));
				final ParameterInfo info = new ParameterInfo(geneInfo.getName(),geneInfo.getType(),geneInfo.getJavaType());
				
				final String strValue = String.valueOf(geneWithId.getAllele());
				info.setValue(ParameterInfo.getValue(strValue,info.getType()));
				
				combination.add((ai.aitia.meme.paramsweep.batch.param.ParameterInfo<?>)InfoConverter.parameterInfo2ParameterInfo(info));
			}
			
			return combination;
		}
		
		//----------------------------------------------------------------------------------------------------
		private Number getFitnessValue(final List<ResultValueInfo> candidates) {
			if (candidates == null || candidates.isEmpty())
				return new Double(FitnessFunction.NO_FITNESS_VALUE);
//				return new Double(optimizationDirection == FitnessFunctionDirection.MAXIMIZE ? 0. :  Double.MAX_VALUE);
			
			Collections.sort(candidates,new Comparator<ResultValueInfo>() {
				public int compare(final ResultValueInfo info1, final ResultValueInfo info2) {
					final double tick1 = (Double) info1.getLabel();
					final double tick2 = (Double) info2.getLabel();
					
					return Double.compare(tick1,tick2);
				}
			});
			
			final Object value = candidates.get(candidates.size() - 1).getValue();
			return (Number) value;
		}
	}
}