package jcgp.backend.parser;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.Scanner;
import jcgp.backend.population.Chromosome;
import jcgp.backend.population.Connection;
import jcgp.backend.population.Input;
import jcgp.backend.population.Node;
import jcgp.backend.resources.Resources;
/**
 * This class includes a method for parsing .chr files and another
 * for writing .chr files from given chromosomes.
 * 
 * @author Eduardo Pedroni
 *
 */
public abstract class ChromosomeParser {
	/**
	 * Use this method to parse .chr files into a given chromosome.
	 * 
	 * This is not fully defensive as it doesn't check for number of inputs, 
	 * doesn't compare rows and columns individually and doesn't account for levels back. It
	 * is not viable to implement these defensive measures with the chromosome format used 
	 * by CGP.
	 * 
	 * @param file the .chr file to parse from
	 * @param chromosome the chromosome to configure
	 * @param resources the experiment resources
	 */
	public static void parse(File file, Chromosome chromosome, Resources resources) {
		/* 
		 * Count the nodes to make sure the size of the .chr file matches the experiment parameters.
		 * 
		 * We do this by using the scanner to get the node and output portions of the file as they
		 * are separated by 3 tab characters. Every number is replaced by a single known character, 
		 * and the length of the string with the new characters is compared with that of a string
		 * where the new known character has been removed, yielding the total number of values.
		 * 
		 */
		FileReader fr;
		try {
			fr = new FileReader(file);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return;
		}
		Scanner in = new Scanner(fr);
		in.useDelimiter("\\t\\t\\t");
		String geneString = in.next().replaceAll("[0-9]+", "g");
		String outString = in.next().replaceAll("[0-9]+", "o");
		int geneCount = geneString.length() - geneString.replace("g", "").length();
		int outCount = outString.length() - outString.replace("o", "").length();
		in.close();
		
		
		// if the acquired values match the current parameters, apply them to the chromosome
		if ((geneCount == resources.nodes() * (resources.arity() + 1))
				&& outCount == resources.outputs()) {
			// prepare a new scanner
			try {
				fr = new FileReader(file);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				return;
			}
			in = new Scanner(fr);
			
			int gene;
			Connection newConnection;
			Node changingNode;
			// for all nodes, columns first
			for (int c = 0; c < resources.columns(); c++) {
				for (int r = 0; r < resources.rows(); r++) {
					// store the changing node
					changingNode = chromosome.getNode(r, c);
					// for every connection
					for (int i = 0; i < resources.arity(); i++) {
						// get connection number from the .chr file
						gene = in.nextInt();
						System.out.println("r: " + r + " c: " + c + " i: " + i + " gene: " + gene);
						if (gene < resources.inputs()) {
							// connection was an input
							newConnection = chromosome.getInput(gene);
						} else {
							// connection was another node, calculate which from its number
							newConnection = chromosome.getNode((gene - resources.inputs()) % resources.rows(), 
									(gene - resources.inputs()) / resources.rows());
						}
						changingNode.setConnection(i, newConnection);
					}
					// set the function, straight indexing should work - this is not entirely
					// safe, but it is not viable to check for functionset compatibility
					changingNode.setFunction(resources.getFunction(in.nextInt()));
				}
			}
			// outputs
			for (int o = 0; o < resources.outputs(); o ++) {
				gene = in.nextInt();
				if (gene < resources.inputs()) {
					// connection was an input
					newConnection = chromosome.getInput(gene);
				} else {
					// connection was another node, calculate which from its number
					newConnection = chromosome.getNode((gene - resources.inputs()) % resources.rows(), 
							(gene - resources.inputs()) / resources.rows());
				}
				chromosome.getOutput(o).setConnection(0, newConnection);
			}
			in.close();
		} else {
			throw new IllegalArgumentException("The topology of the chromosome in " + file.getName() + " does not match that of the experiment.");
		}
	}
	
	/**
	 * Writes a chromosome into the specified .chr file.
	 * 
	 * 
	 * @param file the file to write to
	 * @param chromosome the chromosome to save
	 */
	public static void save(File file, Chromosome chromosome, Resources resources) {
		PrintWriter writer;
		try {
			writer = new PrintWriter(file);
		} catch (FileNotFoundException e) {
			// something bad happened
			return;
		}
		
		// for all nodes, columns first
		for (int c = 0; c < resources.columns(); c++) {
			for (int r = 0; r < resources.rows(); r++) {
				for (int i = 0; i < resources.arity(); i++) {
					// print the connections, separated by spaces
					Connection conn = chromosome.getNode(r, c).getConnection(i);
					if (conn instanceof Input) {
						writer.print(" " + ((Input) conn).getIndex());
					} else if (conn instanceof Node) {
						writer.print(" " + (((((Node) conn).getColumn() + 1) * resources.inputs()) + ((Node) conn).getRow()));
					} else {
						throw new ClassCastException("Could not handle " + conn.getClass() + " as a subclass of Connection");
					}
				}
				// print the function numbers
				writer.print(" " + resources.getFunctionIndex(chromosome.getNode(r, c).getFunction()));
				// node is done, print tab
				writer.print("\t");
			}
		}
		// nodes are done, print two tabs to separate from output
		writer.print("\t\t");
		for (int o = 0; o < resources.outputs(); o ++) {
			Connection source = chromosome.getOutput(o).getSource();
			if (source instanceof Input) {
				writer.print(" " + ((Input) source).getIndex());
			} else if (source instanceof Node) {
				writer.print(" " + (((((Node) source).getColumn() + 1) * resources.inputs()) + ((Node) source).getRow()));
			} else {
				throw new ClassCastException("Could not handle " + source.getClass() + " as a subclass of Connection");
			}
		}
		
		writer.close();
		System.out.println("writer is closed");
	}
	
}