package background;

import java.util.ArrayList;

import background.expressions.MathTree;
import background.systemVariables.*;

public class VariableManager {

	public ArrayList<SimpleVariable> variables;
	public ArrayList<ArrayVariable> arrays;
	public ArrayList<TableVariable> tables;
	public ArrayList<SimpleVariable> actives;	
	
	public ArrayList<String> usedNames;
	
	public ErrorHandler errorHandler;
	
	public VariableManager(ErrorHandler eh) {
		errorHandler = eh;
		reset();
	}
	
	public void reset() {
		variables = new ArrayList<SimpleVariable>();
		arrays = new ArrayList<ArrayVariable>();
		tables = new ArrayList<TableVariable>();
		actives = new ArrayList<SimpleVariable>();
		usedNames = new ArrayList<String>();		
	}
	
	public void resetActives() {
		for (SimpleVariable var: actives) {
			var.setInactive();			
		}
		actives = new ArrayList<SimpleVariable>();
	}
	
	public void makeActive(SimpleVariable s) {
		s.setActive();
		actives.add(s);
	}

	
	/* ======================================================================================== */
	
	public void addVariable(String name, int value) {
		this.addVariable(name, new IntegerVariable(value));
	}
	
	public void addVariable(String name, String value) {
		this.addVariable(name, new StringVariable(value, this));
	}

	public void addVariable(String name, Boolean value) {
		this.addVariable(name, new BooleanVariable(value));
	}

	public void addVariable(String name, char value) {
		this.addVariable(name, new CharacterVariable(value));			
	}	
	
	public void addVariable(String name, String type, String value, Boolean vis) {		
		if (value==null) { value = getDefaultValueForType(type); }
		SimpleVariable x = getNewVariableOfType(type, value);
		x.setVizualize(vis);
		this.addVariable(name, x);		
	}
	

	public void addVariable(String name, String type, String value) {
		if (value==null) { value = getDefaultValueForType(type); }
		this.addVariable(name, getNewVariableOfType(type, value));		
	}
	
	public void addVariable(String name, SimpleVariable var) {
		if (usedNames.contains(name)) errorHandler.errorHappened("UsedVarName");
		else {		
			var.name = name;
			variables.add(var);	
			usedNames.add(name);
			makeActive(var);
		}
	}
	
	public void addArray(String name, int size, String type) {
		addArray(name, 0, size, type, false);
	} 
	
	public void addArray(String name, int size, String type, Boolean vis) {
		addArray(name, 0, size, type, vis);
	} 
	
	public void addArray(String name, int posun, int size, String type, Boolean vis) {
		if (usedNames.contains(name)) errorHandler.errorHappened("UsedVarName"); 
		else {
			ArrayVariable a = new ArrayVariable(posun, size, type, this);
			a.name = name;
			a.vizualize = vis;
			arrays.add(a);
			usedNames.add(name);
		}
	} 

	
	public void addTable(String name, int size1, int size2, String type) {
		addTable(name, 0, size1, 0, size2, type, false);
	} 
	
	public void addTable(String name, int size1, int size2, String type, Boolean vis) {		
		addTable(name, 0, size1, 0, size2, type, vis);
	} 

	public void addTable(String name, int posun1, int size1, int posun2, int size2, String type, Boolean vis) {
		if (usedNames.contains(name)) errorHandler.errorHappened("UsedVarName");
		else {
			TableVariable a = new TableVariable(posun1, size1, posun2, size2, type, this);
			a.name = name;
			a.vizualize = vis;
			tables.add(a);
			usedNames.add(name);
		}
	} 

	
	/* ======================================================================================== */
	
	public void updateVariable(String name, String newValue) {		
		SimpleVariable a = getVariableByName(name);
		a.setValue(newValue);
		a.initialized = true;
		makeActive(a);
	}
	
	public void updateArray(String name, ArrayVariable a) {
		arrays.remove(getArrayVariableByName(name));
		arrays.add(a);		
	}
	
	public void updateTable(String name, TableVariable a) {
		tables.remove(getTableVariableByName(name));
		tables.add(a);		
	}
	
	public void updateArrayVariable(String name, int index, String newValue) {
		ArrayVariable a = getArrayVariableByName(name);
		
		if ((index>=a.size+a.posun) || (index<a.posun)) { errorHandler.errorHappened("Index");}
		else {
			a.updateNth(index, this.getNewVariableOfType(a.getType(), newValue));
			makeActive(a.getNth(index));
		}	
	}
	
	public void updateTableVariable(String name, int index, int index2, String newValue) {
		TableVariable a = getTableVariableByName(name);
				
		if ((index2>=a.sizeX+a.posunX) || (index2<a.posunX) || (index>=a.sizeY+a.posunY) || (index<a.posunY)) { errorHandler.errorHappened("Index");}
		else { a.updateNth(index, index2, this.getNewVariableOfType(a.getType(), newValue));
			makeActive(a.getNth(index, index2));
		}
	}

	
	/* ======================================================================================== */

	
	public ArrayList<SimpleVariable> getAllVariables() {
		return variables;
	}
	
	public ArrayList<ArrayVariable> getAllArrays() {
		return arrays;
	}	
	
	public ArrayList<TableVariable> getAllTables() {
		return tables;
	}
	
	public ArrayList<SimpleVariable> getVizualizedVariables() {
		ArrayList<SimpleVariable> vars = new ArrayList<SimpleVariable>();
		for (SimpleVariable var: variables) {
			if (var.vizualize) {vars.add(var);}
		}		
		return vars;
	}

	public ArrayList<SimpleVariable> getActiveVisibleVariables() {
		ArrayList<SimpleVariable> vars = new ArrayList<SimpleVariable>();
		for (SimpleVariable var: variables) {
			if (var.vizualize && var.isActive()) {vars.add(var);}
		}		
		return vars;
	}

	
	public ArrayList<ArrayVariable> getVizualizedArrays() {
		ArrayList<ArrayVariable> vars = new ArrayList<ArrayVariable>();
		for (ArrayVariable var: arrays) {
			if (var.vizualize) { vars.add(var); }
		}		
		return vars;
	}
	
	public ArrayList<TableVariable> getVizualizedTables() {
		ArrayList<TableVariable> vars = new ArrayList<TableVariable>();
		for (TableVariable var: tables) {
			if (var.vizualize) { vars.add(var); }
		}		
		return vars;
	}
	
	
	/* ======================================================================================== */
	
	public SimpleVariable getNewVariableOfType(String type, String value) {
		switch (type.toLowerCase()) {
			case "int": return new IntegerVariable(value);
			case "string": return new StringVariable(value, this);
			case "char": return new CharacterVariable(value);
			case "boolean": return new BooleanVariable(value);
			default: return null;
		}
	}
	
	public SimpleVariable getVariableByName(String name) {		
		if (name.contains("[") && name.contains("]")) {
			String arrayV[] = name.split("[\\[\\]]");
			if (arrayV.length>2) {
				for (TableVariable variable: this.tables) {
					if (variable.name.equals(arrayV[0])) {											// najdem spravne pole						
						MathTree mt = new MathTree(arrayV[1], this);
						int index = mt.getTreeValue().intValue();
						MathTree mt2 = new MathTree(arrayV[3], this);
						int index2 = mt2.getTreeValue().intValue();						
						if ((index2>=variable.sizeY+variable.posunY) || (index2<variable.posunY) || (index>=variable.sizeX+variable.posunX) || (index<variable.posunX)) { errorHandler.errorHappened("Index"); return null;}
						else return variable.getNth(index2, index);					// vyberiem z neho tu premennu ktora je na mieste urcenom premennou v zatvorkach
					}
				}
			}	
			
			
			for (ArrayVariable variable: this.arrays)
				if (variable.name.equals(arrayV[0])) {											// najdem spravne pole
			
					MathTree mt = new MathTree(arrayV[1], this);
					int index = mt.getTreeValue().intValue();
					
					if ((index>=variable.size+variable.posun) || (index<variable.posun)) { errorHandler.errorHappened("Index"); return null;}
					else {
						if ((arrayV.length>2) && variable.getType().equals("string")) {
							MathTree mt2 = new MathTree(arrayV[3], this);
							int index2 = mt2.getTreeValue().intValue();
							StringVariable str = (StringVariable) variable.getNth(index);
							if ((index2>str.getValue("string").length()) || (index2<0)) { errorHandler.errorHappened("Index"); return null;}
							else return str.getCharAt(index2);					// vyberiem z neho tu premennu ktora je na mieste urcenom premennou v zatvorkach
						} else {																				
							return variable.getNth(index);					// vyberiem z neho tu premennu ktora je na mieste urcenom premennou v zatvorkach
						}
					}
				}					
			
			
			for (SimpleVariable variable: this.variables) {
				if (variable.name.equals(arrayV[0]) && variable.getType().equals("string")) {											// najdem spravne pole
					MathTree mt = new MathTree(arrayV[1], this);
					StringVariable x = (StringVariable) variable;
					int index = mt.getTreeValue().intValue(); 
					if ((index>x.getValue("string").length()) || (index<0)) { errorHandler.errorHappened("Index"); return null;}
					else return x.getCharAt(index);					// vyberiem z neho tu premennu ktora je na mieste urcenom premennou v zatvorkach
				}
			}
		} else {
			for (SimpleVariable variable: this.variables) {
				if (variable.name.equals(name)) return variable;
			}				
		}		
		
		errorHandler.errorHappened("VarNotFound");
		return null;
	}
		
	public ArrayVariable getArrayVariableByName(String name) {
		for (ArrayVariable variable: this.arrays) 
			if (variable.name.equals(name)) return variable;						
		return null;
	}
	
	public TableVariable getTableVariableByName(String name) {
		for (TableVariable variable: this.tables) 
			if (variable.name.equals(name)) return variable;						
		return null;
	}

	public String getDefaultValueForType(String type) {
		switch (type.toLowerCase()) {
			case "int": return "0";
			case "char": return " ";
			case "string": return "";
			case "boolean": return "false";
		}
		return null;
	}
	
	public static ArrayList<String> getVariableTypes() {
		ArrayList<String> result = new ArrayList<String>();
		String[] types = new String[]{ "int","integer","char","string","boolean"};
		for (int i=0; i<types.length; i++) {
			result.add(types[i]);
		}
		return result;		
	}
	
	
	
	/* ===================================  DEBUG  ============================================= */
	
	public void writeAllVariables() {
		for (SimpleVariable var: variables) {
			writeVariable(var, true);
			System.out.print("   ");
		}
		System.out.println();
	}

	public void writeVisibleVariables() {
		for (SimpleVariable var: this.getVizualizedVariables()) {
			writeVariable(var, true);
			System.out.print("   ");
		}
		System.out.println();
	}

	
	public void writeAllArrays() {
		
		for (ArrayVariable a: arrays) {
			System.out.print(a.name + ": ");
			for (int i=a.posun; i<a.size+a.posun; i++) {
				System.out.print("[" + i + "] -> ");
				writeVariable(a.getNth(i), false);
				System.out.print("   ");
			}
			System.out.println();
		}	
	}

	public void writeAllTables() {
		
		for (TableVariable a: tables) {
			System.out.print(a.name + ": ");
			for (int i=a.posunX; i<a.sizeX+a.posunX; i++) {
				for (int j=a.posunY; j<a.sizeY+a.posunY; j++) {
					System.out.print("[" + i + "," +j+"] -> ");
					writeVariable(a.getNth(j, i), false);
					System.out.print("   ");
				}
				System.out.println();
			}
			System.out.println();
		}	
	}

	
	private void writeVariable(SimpleVariable v, Boolean showName) {
		if (showName && v.vizualize) System.out.print("*");
		if (!v.initialized) System.out.print("!");
		if (showName) System.out.print(v.name + " = ");
		switch (v.getType()) {
			case "int": System.out.print(v.getValue(1)); break;
			case "char": System.out.print("'"+v.getValue('c')+"'"); break;
			case "string": System.out.print("\""+v.getValue("string")+"\""); break;
			case "boolean": System.out.print(v.getValue(true)); break;
			case "": System.out.print(""); break;
		}
	}
	
	
}
