/*******************************************************************************
 * Copyright (c) 2015, 2016 CNRS, IRIT, IRT AESE (IRT Saint Exupéry).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the LGPL 3.0
 * which accompanies this distribution, and is available at
 * https://www.gnu.org/licenses/lgpl-3.0-standalone.html
 *
 * Contributors:
 *     Faiez Zalila - initial API and implementation and/or initial documentation
 *******************************************************************************/
package fr.laas.libtts;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.apache.commons.lang3.math.Fraction;
import org.apache.thrift.TException;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import tts.Clock;
import tts.Error;
import tts.Interval;
import tts.InvalidDelay;
import tts.Net;
import tts.NotFireable;
import tts.Place;
import tts.StateNotFound;
import tts.TTS.Client;
import tts.TTSState;
import tts.Transition;
import tts.TransitionNotFound;

public class TTSSimulator {
	private Net pn;
	private Client client;
	private HashMap<Long, Transition> allTransitions;
	private HashMap<Long, Place> allPlaces;
	private TTSState state;
	private Stack<TTSState> previous_states;

	public Stack<TTSState> getPrevious_states() {
		return previous_states;
	}

	public void setPrevious_states(Stack<TTSState> previous_states) {
		this.previous_states = previous_states;
	}

	public TTSSimulator(Net apn, Client aclient) {
		pn = apn;
		client = aclient;
		HashMap<Long, Transition> atransitions = new HashMap<Long, Transition>();
		for (Transition transition : pn.getTransitions()) {
			atransitions.put(transition.getUid(), transition);
		}
		HashMap<Long, Place> aplaces = new HashMap<Long, Place>();
		for (Place place : pn.getPlaces()) {
			aplaces.put(place.getUid(), place);
		}
		allPlaces = aplaces;
		allTransitions = atransitions;
		previous_states = new Stack<TTSState>();
		try {
			state = client.initial_state();
		} catch (Error e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Net getPn() {
		return pn;
	}

	public void setPn(Net pn) {
		this.pn = pn;
	}

	public Client getClient() {
		return client;
	}

	public void setClient(Client client) {
		this.client = client;
	}

	public HashMap<Long, Transition> getAllTransitions() {
		return allTransitions;
	}

	public void setAllTransitions(HashMap<Long, Transition> transitions) {
		this.allTransitions = transitions;
	}

	public HashMap<Long, Place> getAllPlaces() {
		return allPlaces;
	}

	public void setAllPlaces(HashMap<Long, Place> places) {
		this.allPlaces = places;
	}

	public TTSState getState() {
		return state;
	}

	public void setState(TTSState state) {
		this.state = state;
	}

	public void simulate() {
		try {
			printCurrent();
			boolean quit = false;
			while (!quit) {
				BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
				String s;
				try {
					s = bufferRead.readLine();
					String[] line = s.split(" ");
					switch (line.length) {
					case 1:
						switch (line[0]) {
						case "q":
							quit = quit();
							break;
						case "h":
							help();
							break;
						case "p":
							printCurrent();
							break;
						default:
							System.out.println("Unknown command");
						}
						break;
					case 2:
						switch (line[0]) {
						case "f":
							fireTransition(Long.parseLong(line[1]));
							break;
						case "d":
							delay(line[1]);
							break;
						default:
							System.out.println("Unknown command");
						}
						break;
					default:
						System.out.println("Invalid usage");
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (TException x) {
			x.printStackTrace();
		}
	}

	public boolean quit() {
		System.out.println("Quit! Bye.");
		return true;
	}

	public void help() {
		System.out.println("LibTTS commands: ");
		System.out.println("h \t //this mode ");
		System.out.println("f n \t //fire the transition n (n is the transition uid)");
		System.out.println("d n \t //let delay n elapse (n int or float)");
		System.out.println("p \t //Print the current state");
		System.out.println("q \t //Quit LibTTS");
	}

	public void printCurrent() {
		System.out.println("Printing the current state");
		printStatesAndValues();
		printTransitions();
	}

	public void fireTransition(long parseLong)
			throws Error, StateNotFound, TransitionNotFound, NotFireable, TException {
			TTSState previous_state = state;
			state = client.fire(state.uid, parseLong);
			//System.out.println("do firing: " + allTransitions.get(parseLong).name);
			previous_states.add(previous_state);
			//System.out.println("previous "+ previous_states);
			//printCurrent();
	}

	public void delay(String d) throws Error, StateNotFound, InvalidDelay, TException {
//		try {
			TTSState previous_state = state;
			Fraction frac = Fraction.getFraction(d);
			Clock delay = new Clock();
			delay.numerator = String.valueOf(frac.getNumerator());
			delay.denominator = String.valueOf(frac.getDenominator());

//				try {
					state = client.advance_time(state.uid, delay);
					System.out.println("do delay: ");
					//printCurrent();
//				} catch (Error e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				} catch (StateNotFound e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				} catch (InvalidDelay e) {
//					// TODO Auto-generated catch block
//					System.out.println("not possible date:" + d);
//				} catch (TException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//				
//			
//		} catch (NumberFormatException nfe) {
//			System.out.println("Unknown Number Format:" + d);
//		}
		previous_states.add(previous_state);
		//System.out.println("previous "+ previous_states);			
	}
	public void undo(){
		state = previous_states.pop();
	}
	public void printStatesAndValues() {

		System.out.print("Places:");
		for (Long id : state.getMarkings()) {
			System.out.print(allPlaces.get(id).getName() + " ");
		}
		System.out.println("");
		//System.out.println("Values:" + state.getValues());
		// The following printing of values is just to debug the current values of variables
		System.out.println("Values:");
		JSONParser parser = new JSONParser();
		try {
			JSONObject json_values = (JSONObject) parser.parse(state.getValues());
			ArrayList list = new ArrayList(json_values.keySet());     
			Collections.sort(list);
			for (Object key: list) {
				System.out.print("\t"+key+":");
				System.out.println(json_values.get(key));
			}
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public ArrayList<String> getCurrentPlaces(){
		ArrayList<String> places = new ArrayList<String>();
		for (Long id : state.getMarkings()) {
			places.add(allPlaces.get(id).getName());
		}
		return places;
	}
	public HashMap<String,String> getFireableTransitions(){
		HashMap<String,String> transitions = new HashMap<String,String>();
		try {
			for (Long transition_id : client.enabled(state.getUid())) {
				if (client.fireable(state.getUid()).contains(transition_id)) {
					transitions.put(allTransitions.get(transition_id).getName(), getInterval(allTransitions.get(transition_id).getInterval(),state.getClocks().get(transition_id)));
				}
			}
		} catch (Error e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StateNotFound e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return transitions;
	}
	
	public HashMap<String,String> getEnabledTransitions(){
		HashMap<String,String> transitions = new HashMap<String,String>();
		try {
			for (Long transition_id : client.enabled(state.getUid())) {
				if (!(client.fireable(state.getUid()).contains(transition_id))) {
					transitions.put(allTransitions.get(transition_id).getName(), getInterval(allTransitions.get(transition_id).getInterval(),state.getClocks().get(transition_id)));
				}
			}
		} catch (Error e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (StateNotFound e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return transitions;
	}
	public void printTransitions() {
		System.out.println("Enabled (Fireable) transitions in the state " + state.getUid());
		try {
			for (Long transition_id : client.enabled(state.getUid())) {
				String res_transition = "";
				if (client.fireable(state.getUid()).contains(transition_id)) {
					res_transition = "+ ";
				} else {
					res_transition = "- ";
				}
				res_transition += transition_id + " " + allTransitions.get(transition_id).getName();
				res_transition += " ";
				res_transition += getInterval(allTransitions.get(transition_id).getInterval(),
						state.getClocks().get(transition_id));
				res_transition += " ";
				System.out.println(res_transition);
			}
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private String getInterval(Interval interval, Clock clock) {
		String res_interval = "";

		if (interval == null) {
			res_interval = " [0, w[";
		} else {
			Fraction low = Fraction.getFraction(interval.low.numerator + "/" + interval.low.denominator);
			Fraction dynamic_clock = Fraction.getFraction(clock.getNumerator() + "/" + clock.getDenominator());
			if (interval.open_low) {
				if ((low.subtract(dynamic_clock)).floatValue() < 0) {
					res_interval += " [";
				} else {
					res_interval += " ]";
				}
			} else {
				res_interval += " [";
			}

			if ((low.subtract(dynamic_clock)).floatValue() < 0) {
				res_interval += "0";
			} else {
				res_interval += (low.subtract(dynamic_clock)).floatValue();
			}
			res_interval += ", ";
			if (interval.high == null) {
				res_interval += "w";
			} else {
				Fraction high = Fraction.getFraction(interval.high.numerator + "/" + interval.high.denominator);
				if ((high.subtract(dynamic_clock)).floatValue() < 0) {
					res_interval += "0";
				} else {
					res_interval += (high.subtract(dynamic_clock)).floatValue();
				}
			}
			if (interval.open_high) {
				res_interval += "[ ";
			} else {
				res_interval += "] ";
			}
		}
		return res_interval;
	}
}
