/*
 * This file is part of the DistSim distributed simulation framework (client)
 * Copyright (C) 2007 Ulf Hermann
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package brn.distsim.client.data;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.nfunk.jep.JEP;

import brn.distsim.client.data.GroupNumericParameters.Parameter;


public class SimulationCreator {

	private Connection definitions;

	private Map<String, Double> currentNumericParams;

	private Map<String, GroupStringParameters.Parameter> currentStringParams;
	
	private List<String> stringNames;

	private int currentNumericPos;
	
	private int currentStringPos;
	
	private int groupId;

	private JEP parser;
	
	private int numDelays;

	private int maxDelays;

	private GroupStringParameters stringParams;

	private GroupNumericParameters numericParams;

	public SimulationCreator(Connection definitions, Group group)
			throws SQLException {
		this.definitions = definitions;
		stringParams = group.getStringParameters();
		numericParams = group.getNumericParameters();
		groupId = group.getId();
		currentNumericParams = new HashMap<String, Double>();
		currentNumericPos = 0;
		currentStringParams = new HashMap<String, GroupStringParameters.Parameter>();
		stringNames = new LinkedList<String>();
		stringNames.addAll(stringParams.keySet());
		currentStringPos = 0;
		numDelays = 0;
		maxDelays = numericParams.size() * (numericParams.size() + 1) / 2;
		parser = new JEP();
		parser.addStandardFunctions();
		parser.addStandardConstants();
		numericParams.commit();
		nextString();
	}

	private boolean nextString() {
		if (currentStringPos >= stringNames.size()) {
			currentStringPos--;
			return true;
		} else if (currentStringPos < 0) {
			return false;
		} else {
			String name = stringNames.get(currentStringPos);
			GroupStringParameters.Parameter par = currentStringParams.get(name);
			parser.removeVariable(name);
			if (par != null) {
				par = par.getNext();
				currentStringParams.put(name, par);
				if (par == null) {
					currentStringPos--;
					return nextString();
				} else {
					currentStringPos++;
				}
			} else {
				par = stringParams.get(name);
				currentStringParams.put(name, par);
				currentStringPos++;
			}
			try {
				double val = Double.parseDouble(par.getValue());
				if (!Double.isNaN(val)) {
					parser.addVariable(name,val);
				}
			} catch (NumberFormatException e) {
				//quietly ignore
				//e.printStackTrace();
			} 
		}
		return nextString();
	}

	public Simulation next() throws SQLException {
		if (nextNumeric() || (nextString() && nextNumeric())) {
			Simulation sim = new Simulation(definitions, groupId);
			for (String name : currentNumericParams.keySet()) {
				sim.put(sim.new Parameter(name, currentNumericParams.get(name)
						.toString()));
			}
			for (String name : stringParams.keySet()) {
				String value = currentStringParams.get(name).getValue();
				sim.put(sim.new Parameter(name, value));
			}
			return sim;
		} else {
			numericParams.loadFromDb();
			return null;
		}
		
	}

	private boolean nextNumeric() throws SQLException {
		if (currentNumericPos >= numericParams.size()) {
			currentNumericPos--;
			return true;
		} else if (currentNumericPos < 0) {
			currentNumericPos = 0;
			numDelays = 0;
			currentNumericParams.clear();
			return false;
		} else {
			Parameter param = numericParams.get(currentNumericPos);
			String name = param.getName();
			double currentVal = 0;
			double lastVal = 0;
			String max = param.getMax();
			if (max.equals("")) 
				max = "0";
			parser.parseExpression(max);
			double maxVal = parser.getValue();
			if (parser.hasError()) {
				delayParam(name);
			} else if (currentNumericParams.containsKey(name)) {
				lastVal = currentNumericParams.get(name);
				String step = param.getStep();
				if (step.equals(""))
					step = "0";
				parser.parseExpression(step);
				double stepVal = parser.getValue();
				currentVal = lastVal + stepVal;
				if (Math.abs(maxVal - lastVal) > Math.abs(maxVal - currentVal)) {
					examineParseResult(name, currentVal);
				} else {
					parser.removeVariable(name);
					currentNumericParams.remove(name);
					currentNumericPos--;
				}
			} else {
				parser.parseExpression(param.getMin());
				currentVal = parser.getValue();
				examineParseResult(name, currentVal);
			}
			return nextNumeric();
		}
	}

	private boolean examineParseResult(String name, double currentVal) {
		if (parser.hasError()) {
			delayParam(name);
			return false;
		} else {
			currentNumericParams.put(name, currentVal);
			parser.removeVariable(name);
			parser.addVariable(name, currentVal);
			currentNumericPos++;
			return true;
		}

	}

	private void delayParam(String name) {
		if (++numDelays > maxDelays) {
			// there has been one complete delay-round for every numeric parameter
			throw new IllegalArgumentException(
					"circular dependency or malformed expression detected in group " + groupId + " with parameter " + name);
		}
		numericParams.add(numericParams.remove(currentNumericPos));
		parser.removeVariable(name);
		currentNumericParams.remove(name);
	}
}
