package Logica;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;

public class Meses {
	private ArrayList<Mes> meses;
	private int mesActual=0;
	public Meses() {
		this.meses = new ArrayList<Mes>();
	}
	
	/**
	 * Aniade una gestion vacia a la lista de meses
	 * 
	 * @param nombre		nombre de la gestion
	 * @param anio			anio de la gestion
	 * @param mes			mes de la transaccion
	 * @param isPositivo	tipo de la gestion
	 * 
	 * @return Devuelve la gestion que se a aniadido
	 */
	public Gestion aniadirGestion(String nombre, int anio, Month mes, boolean isPositivo) {
		Gestion ret;
		try {
			for(Mes mesSelect:this.meses) {
				if(mesSelect.getMes().equals(mes) && mesSelect.getAnio() == anio){
					for(Gestion gestion:mesSelect.getGestiones()) {
						if(gestion.getNombre().equals(nombre)) {
							return gestion;
						}
					}
					ret = new Gestion(nombre, isPositivo);
					mesSelect.getGestiones().add(ret);
					return ret;
				}
			}
		}catch (NullPointerException e) {
			ArrayList<Gestion> gestiones=new ArrayList<Gestion>();
			ret = new Gestion(nombre, isPositivo);
			gestiones.add(ret);
			this.meses.add(new Mes(gestiones,anio,mes));
			return ret;
		}
		ArrayList<Gestion> gestiones=new ArrayList<Gestion>();
		ret = new Gestion(nombre, isPositivo);
		gestiones.add(ret);
		this.meses.add(new Mes(gestiones,anio,mes));
		return ret;
	}
	
	/**
	 * Aniade una transaccion y la coloca en el mes y gestion que le corresponde, creandolos si no existen todavia
	 * 
	 * @param transaccion	transaccion a aniadir
	 * @param nombre	nombre de la gestion a la que pertenece
	 * @param isPositivo	tipo de gestion a la que pertenece
	 */
	public void aniadirTransaccion(Transaccion transaccion, String nombre, boolean isPositivo) {
		for(Mes mes:this.meses) {
			if(transaccion.getDia().getMonth().equals(mes.getMes()) &&
					transaccion.getDia().getYear() == mes.getAnio()){
				mes.aniadirTransaccion(transaccion, nombre, isPositivo);
				return;
			}
		}
		this.meses.add(new Mes(new ArrayList<Gestion>(),transaccion.getDia().getYear(),
				transaccion.getDia().getMonth()));
		this.meses.get(this.meses.size()-1).aniadirTransaccion(transaccion, nombre, isPositivo);
	}
	
	/**
	 * Define el mes en que nos encontramos a la hora de devolver objetos
	 * 
	 * @param anio	anio en el que queremos situarnos
	 * @param mes	mes en el que queremos situarnos
	 */
	public void elegirMes(int anio, Month mes) {
		for(int i = 0; i < this.meses.size(); i++) {
			if(this.meses.get(i).getAnio()==anio && this.meses.get(i).getMes().equals(mes)) {
				this.mesActual = i;
				return;
			}
		}
		this.meses.add(new Mes(new ArrayList<Gestion>(), anio, mes));
		this.mesActual = this.meses.size()-1;
	}
	
	/**
	 * Devuelve la lista de gestiones en la que nos encontramos
	 * 
	 * @return	Lista de gestiones en la que nos encontramos
	 */
	public ArrayList<Gestion> getGestionesActuales(){
		return this.meses.get(this.mesActual).getGestiones();
	}
	
	/**
	 * devuelve el dinero que nos queda este mes (o el que debemos)
	 * 
	 * @return	dinero que nos queda o debemos este mes
	 */
	public float getTotal() {
		return this.meses.get(this.mesActual).getTotal();
	}
	
	/**
	 * guarda las gestiones que hemos introducido en un fichero para garantizar la persistencia de los datos
	 * 
	 * @param nombre	nombre del fichero donde se guardaran los datos
	 * @throws IOException
	 */
	public void guardarMeses(String nombre) throws IOException {
		FileOutputStream fichero = new FileOutputStream(nombre);
		ObjectOutputStream escritor = new ObjectOutputStream(fichero);
		for(Mes mes:this.meses) {
			for(Gestion gestion:mes.getGestiones()) {
				for(Transaccion transaccion:gestion.getElementos()) {
					escritor.writeObject(transaccion);
				}
			}
			
		}
		escritor.close();
		fichero.close();
	}
	
	/**
	 * Carga los datos que hemos guardado en el fichero de persistencia
	 * 
	 * @param nombre	nombre del fichero
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public void cargarMeses(String nombre) throws IOException, ClassNotFoundException {
		FileInputStream fichero;
		try {
			fichero = new FileInputStream(nombre);
		}catch(FileNotFoundException e){
			return;
		}
		
		ObjectInputStream lector = new ObjectInputStream(fichero);
		try {
			while(true) {
				Transaccion transaccion = (Transaccion)lector.readObject();
				this.aniadirTransaccion(transaccion, transaccion.getGestion().getNombre(), transaccion.getGestion().esIngreso());
			}
		}catch(EOFException e) {
			lector.close();
			fichero.close();
		}		
	}
	
	/**
	 * devuelve todas las transacciones del mes actual
	 * 
	 * @return transacciones del mes actual
	 */
	public Vector<String> salidaTodo(){
		Vector<String> ret = new Vector<String>();
		for(Mes mes:this.meses) {
			ret.add(mes.toString());
			for(Gestion gestion:mes.getGestiones()) {
				ret.add("  "+gestion.toString());
				for(Transaccion transaccion:gestion.getElementos()) {
					ret.add("    "+transaccion.toString());
				}
			}
		}
		return ret;
	}
	
	/**
	 * Exporta todos un esquema de toda la estructura de datos a un xml almacenado en la carpeta donde nos encontramos.
	 * El fichero se llamara gestiones.xml
	 */
	public void exportarXML() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		File fichero = new File("gestiones.xml");
		try {
			builder = factory.newDocumentBuilder();
			DOMImplementation dom = builder.getDOMImplementation();
			Document documento = dom.createDocument(null, "xml", null);
			Element raiz = documento.createElement("raiz");
			documento.getDocumentElement().appendChild(raiz);
			Element nodoMeses;
			Element nodoGestion;
			Element nodoTransaccion;
			Element nodoDatos;
			Element nodoRaizGestion;
			Element nodoRaizTransaccion;
			Text dato;
			for(Mes mes:this.meses) {
				nodoMeses = documento.createElement("mes");
				raiz.appendChild(nodoMeses);
				nodoDatos = documento.createElement("nombre");
				nodoMeses.appendChild(nodoDatos);
				dato = documento.createTextNode(mes.getMes().toString());
				nodoDatos.appendChild(dato);
				nodoDatos = documento.createElement("anio");
				nodoMeses.appendChild(nodoDatos);
				dato = documento.createTextNode(String.valueOf(mes.getAnio()));
				nodoDatos.appendChild(dato);
				nodoDatos = documento.createElement("dineroRestante");
				nodoMeses.appendChild(nodoDatos);
				dato = documento.createTextNode(String.valueOf(mes.getTotal()));
				nodoDatos.appendChild(dato);
				nodoDatos = documento.createElement("numeroGestiones");
				nodoMeses.appendChild(nodoDatos);
				dato = documento.createTextNode(String.valueOf(mes.nGestiones));
				nodoDatos.appendChild(dato);
				nodoRaizGestion = documento.createElement("Gestiones");
				nodoMeses.appendChild(nodoRaizGestion);
				for(Gestion gestion:mes.gestiones) {
					nodoGestion = documento.createElement("gestion");
					nodoRaizGestion.appendChild(nodoGestion);
					nodoDatos = documento.createElement("Nombre");
					nodoGestion.appendChild(nodoDatos);
					dato = documento.createTextNode(gestion.getNombre());
					nodoDatos.appendChild(dato);
					nodoDatos = documento.createElement("Dinero");
					nodoGestion.appendChild(nodoDatos);
					dato = documento.createTextNode(String.valueOf(gestion.getSuma()));
					nodoDatos.appendChild(dato);
					nodoDatos = documento.createElement("EsPositivo");
					nodoGestion.appendChild(nodoDatos);
					dato = documento.createTextNode(String.valueOf(gestion.esIngreso()));
					nodoDatos.appendChild(dato);
					nodoRaizTransaccion = documento.createElement("Transacciones");
					nodoGestion.appendChild(nodoRaizTransaccion);
					for(Transaccion transaccion:gestion.getElementos()) {
						nodoTransaccion = documento.createElement("transaccion");
						nodoRaizTransaccion.appendChild(nodoTransaccion);
						nodoDatos = documento.createElement("Nombre");
						nodoTransaccion.appendChild(nodoDatos);
						dato = documento.createTextNode(transaccion.getNombre());
						nodoDatos.appendChild(dato);
						nodoDatos = documento.createElement("Fecha");
						nodoTransaccion.appendChild(nodoDatos);
						dato = documento.createTextNode(transaccion.getDia().toString());
						nodoDatos.appendChild(dato);
						nodoDatos = documento.createElement("Dinero");
						nodoTransaccion.appendChild(nodoDatos);
						dato = documento.createTextNode(String.valueOf(transaccion.getDinero()));
						nodoDatos.appendChild(dato);
					}
				}
			}
			Source src = new DOMSource(documento);
            Result result = new StreamResult(fichero);

            Transformer transformer = null;
            transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(src, result);
			
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Importa el xml cogiendolo de la ruta donde se guarda por defecto
	 */
	public void importarXML() {
		this.importarXML("gestiones.xml");
	}
	
	/**
	 * Importa un xml de la ruta donde le indiquemos
	 * 
	 * @param nombreFichero	Nombre del fichero donde esta el xml a cargar
	 */
	public void importarXML(String nombreFichero) {
		File fichero = new File(nombreFichero);
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document documento = builder.parse(fichero);
			NodeList gestiones = documento.getElementsByTagName("gestion");
			//NodeList transacciones = documento.getElementsByTagName("transaccion");
			NodeList meses = documento.getElementsByTagName("mes");
			for(int h = 0; h < meses.getLength(); h++) {
				Node mesNodo = meses.item(h);
				Element elementoMes = (Element) mesNodo;
				for(int i = 0; i < gestiones.getLength();i++) {
					Node gestionNodo = gestiones.item(i);
					Element elementoGestion = (Element) gestionNodo;
					
					Gestion gestion = this.aniadirGestion(elementoGestion.getElementsByTagName("Nombre").item(0).getChildNodes().item(0).getNodeValue(), 
							Integer.parseInt(elementoMes.getElementsByTagName("anio").item(0).getChildNodes().item(0).getNodeValue()),
							Month.valueOf(elementoMes.getElementsByTagName("nombre").item(0).getChildNodes().item(0).getNodeValue()),
							Boolean.parseBoolean(elementoGestion.getElementsByTagName("EsPositivo").item(0).getChildNodes().item(0).getNodeValue()));
					NodeList transacciones = elementoGestion.getElementsByTagName("transaccion");
					for(int j=0; j < transacciones.getLength(); j++) {
						Node transaccio = transacciones.item(j);
						Element elementoTransaccion = (Element) transaccio;
						//System.out.println(elementoTransaccion.getElementsByTagName("Fecha").item(0).getChildNodes().item(0).getNodeValue());
						String[] fecha = elementoTransaccion.getElementsByTagName("Fecha").item(0).getChildNodes().item(0).getNodeValue().split("-");
						this.aniadirTransaccion(new Transaccion(elementoTransaccion.getElementsByTagName("Nombre").item(0).getChildNodes().item(0).getNodeValue(),
								Float.parseFloat(elementoTransaccion.getElementsByTagName("Dinero").item(0).getChildNodes().item(0).getNodeValue()),
								LocalDate.of(Integer.parseInt(fecha[0]), Integer.parseInt(fecha[1]), Integer.parseInt(fecha[2])),
								gestion),
								gestion.getNombre(), true);
					}
				}
			}
			
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Devuelve el nombre del mes e el que nos encontramos
	 * 
	 * @return Nombre del mes en el que nos encontramos
	 */
	public String getNombre() {
		return this.meses.get(this.mesActual).getMes().toString();
	}
	
}

class Mes implements Serializable{
	private int anio;
	private Month mes;
	float total;
	int nGestiones;
	ArrayList<Gestion> gestiones;
	/**
	 * Constructor por defecto de Mes
	 * 
	 * @param gestiones	lista de gestiones que contiene el mes
	 * @param anio		anio al que pertenece el mes
	 * @param mes		mes que gestiona la clase
	 */
	Mes(ArrayList<Gestion> gestiones, int anio, Month mes){
		this.gestiones = gestiones;
		this.anio = anio;
		this.mes = mes;
		this.nGestiones = this.gestiones.size(); 
	}
	
	/**
	 * Aniade una transaccion al mes
	 * 
	 * @param transaccion	Transaccion a aniadir
	 * @param nombre		nombre de la gestion a la que pertenece
	 * @param isPositivo	Tipo de gestion a la que pertenece
	 */
	void aniadirTransaccion(Transaccion transaccion, String nombre, boolean isPositivo) {
		for(Gestion gestion:this.gestiones) {
			if(gestion.getNombre().equals(nombre)) {
				gestion.aniadirGasto(transaccion);
				return;
			}
		}
		gestiones.add(new Gestion(nombre, isPositivo));
		this.nGestiones++;
		gestiones.get(this.gestiones.size()-1).aniadirGasto(transaccion);
		return;
	}
	
	/**
	 * Devuelve el mes que gestione
	 * 
	 * @return mes que gestiona
	 */
	Month getMes() {
		return this.mes;
	}
	
	/**
	 * Devuelve el anio que gestiona
	 * 
	 * @return anio que gestiona
	 */
	int getAnio() {
		return this.anio;
	}
	/**
	 * devuelve las gestionaes que contiene
	 * 
	 * @return gestiones que contiene
	 */
	ArrayList<Gestion> getGestiones(){
		return this.gestiones;
	}
	
	/**
	 * devuelve el dinero que quedo ese mes (o que debe)
	 * 
	 * @return dinero que queda o debe ese mes
	 */
	float getTotal() {
		this.total = 0;
		for(Gestion gestion:this.gestiones) {
			this.total += gestion.getTotal();
		}
		return this.total;
	}
	
	public String toString() {
		String ret = "";
		ret += this.mes.toString() + "/" + this.anio + ":" + this.getTotal();
		return ret;
	}
	
}
