/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package xmlhandling;

import expressiontree.operators.Operator;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apfloat.Apfloat;
import org.w3c.dom.Document;
import propertyhandler.FileLocations;

/**
 * A expression szöveges kifejezésekből XML-t építő osztály.
 * @author Esta
 */
public class XMLBuilderFromPrefixText extends XMLBuilder {

    /**
     * Az alapértelmezett konstruktor, ami beállítja a kimeneti fájlt is.
     * @param file a kimeneti xml
     */
    public XMLBuilderFromPrefixText(File file) {
        this.file = file;
    }

    /**
     * Az a publikus metódus, ami egy expression szöveges kifejezésből elkészít egy XML dokumentumot.
     * @param portable azt adja meg, hogy legyen-e hordozható a fájl.
     * @throws IOException ha nem sikerülne a standard header hozzáadásakor a másolás.
     * @throws expressiontree.xmlhandling.UnknownOperatorException ha olyan operátort találunk, amely nem ismert.
     * @throws java.io.FileNotFoundException ha a file paraméter által hivatkozott fájl nem írható.
     * @throws javax.xml.parsers.ParserConfigurationException ha gond lenne a parser beállításával.
     * @throws javax.xml.stream.XMLStreamException ha probléma adódna a kiírás során.
     */
    public void convertToXML(boolean portable) throws IOException, UnknownOperatorException, FileNotFoundException, ParserConfigurationException, XMLStreamException, ExpressionLogicalException {
        //tokenizáljuk a expression szöveget a white-space karaktereknél
        LinkedList<String> prefixList = tokenizer();
        //megvizsgáljuk, hogy a kapott fájl írható-e.
        if (file.exists() && !file.canWrite()) {
            throw new FileNotFoundException("CANT_WRITE");
        } else if (file.exists() && file.canWrite() && portable && !this.overwrite) {
            throw new FileNotFoundException("OVERWRITE_XML");
        }
        //létrehozzuk a document-et
        Document doc = XMLBuilder.getDocument();
        XMLStreamWriter out = XMLBuilder.getStream(file);
        //hozzáadjuk az XML-hez a programban alapértelmezett fejlécet
        XMLBuilder.addHeader(doc, out, file, portable, precision);
        //itt kezdődik a kifejezés az expression tag-gel.
        out.writeCharacters("\n");
        //ide kerül a kifejezés lényegi része a rekurzív építő eljárással
        this.recursivePrefixTextToXML(prefixList, out);
        //lezárjuk a tageket, majd az egész Stream-et
        out.writeEndElement();
        out.writeCharacters("\n");
        out.close();
        //Létrehozzuk a szükséges behúzásokat, hogy jobban láthatóak legyenek a 
        //dokumentumban az operátorok operandusai
        XMLBuilderFromPrefixText.consolidate(file);
    }

    /**
     * Az a privát metódus, ami tokenizálja a expression szövegünket.
     * @return a tokenek listájával
     */
    protected LinkedList<String> tokenizer() {
        //készítünk egy listát a tokeneknek
        LinkedList<String> prefixList = new LinkedList<String>();
        //feltördeljük a szöveget, majd a listára fűzzük a töredékeket
        for (String s : expression.replaceAll("\\|", " | ").replaceAll(",", " ").replaceAll("\\{", " ").replaceAll("\\}", " }").replaceAll("\\[", "[ ").replaceAll("\\]", " ] ").replaceAll("\\s+", " ").split("\\s")) {
            prefixList.add(s);
        }
        return prefixList;
    }
    
    /**
     * Az a privát metódus, ami tokenizálja a kapott szöveget.
     * @param string a kapott szöveg
     * @return a tokenek listájával
     */
    protected LinkedList<String> tokenizer(String string) {
        //készítünk egy listát a tokeneknek
        LinkedList<String> prefixList = new LinkedList<String>();
        //feltördeljük a szöveget, majd a listára fűzzük a töredékeket
        for (String s : string.replaceAll("\\|", " | ").replaceAll(",", " ").replaceAll("\\{", " ").replaceAll("\\}", " }").replaceAll("\\[", "[ ").replaceAll("\\]", " ] ").replaceAll("\\s+", " ").split("\\s")) {
            prefixList.add(s);
        }
        return prefixList;
    }

    /**
     * Olyan privát metódus, amely rekurzívan előállítja a dokumentumot, mintha csak fát építene, és kiírja a Stream-re.
     * @param prefixList a tokenek listája
     * @param out a kimeneti Stream
     * @throws xmlhandling.UnknownOperatorException ha nem ismert operátort találtunk.
     * @throws javax.xml.stream.XMLStreamException ha hiba van a kiírás során.
     * @throws java.io.FileNotFoundException ha a file paraméter által hivatkozott fájl nem írható.
     * @throws javax.xml.parsers.ParserConfigurationException ha gond lenne a parser beállításával.
     */
    private void recursivePrefixTextToXML(LinkedList<String> prefixList, XMLStreamWriter out) throws UnknownOperatorException, XMLStreamException, ExpressionLogicalException, ParserConfigurationException, FileNotFoundException {
        if (!prefixList.isEmpty() && prefixList.getFirst().equalsIgnoreCase("[")) {
            //ha mátrixba botlottunk, akkor a mátrix-kezelőnek adjuk tovább a munkát.
            this.prefixMatrixHandler(prefixList, out);
        } else if (!prefixList.isEmpty() && Operator.OTHER.contains(prefixList.getFirst())) {
            //változó paraméterszámú operátoroknál meg kell keresnünk azt is, hogy mennyi paramétert használunk aktuálisan
            out.writeStartElement(Operator.textToTagTranslate(prefixList.getFirst()));
            int operands = 0;
            prefixList.remove(0);
            //addig haladunk előre, és olvassuk be a paramétereket, amíg } jelet nem találunk
            LinkedList<String> prefixListCopy = new LinkedList<String>();
            prefixListCopy.addAll(prefixList);
            //a lista másolatának segítségével megszámoljuk az operandusokat
            while (!prefixListCopy.isEmpty() && !prefixListCopy.getFirst().equalsIgnoreCase("}")) {
                this.recursivePrefixTextToXML(prefixListCopy, XMLBuilder.getStream(new File(FileLocations.getDefaultTempData())));
                operands++;
            }
            //legyártjuk az xml tageket
            if (operands == 0) {
                throw new ExpressionLogicalException("OTHER_HAS_NO_OPERANDS");
            }
            out.writeAttribute("operands", "" + operands);
            out.writeCharacters("\n");
            //beírjuk az operandusokhoz tartozó tageket is
            while (!prefixList.isEmpty() && !prefixList.getFirst().equalsIgnoreCase("}")) {
                this.recursivePrefixTextToXML(prefixList, out);
            }
            prefixList.removeFirst();
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (!prefixList.isEmpty() && Operator.UNARY.contains(prefixList.getFirst())) {
            //egyoperandusú operátoroknál könnyű dolgunk van
            out.writeStartElement(Operator.textToTagTranslate(prefixList.getFirst()));
            prefixList.remove(0);
            //az operandusok száma biztosan 1
            out.writeAttribute("operands", "1");
            out.writeCharacters("\n");
            //csak egy operandust olvasunk be
            this.recursivePrefixTextToXML(prefixList, out);
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (!prefixList.isEmpty() && Operator.BINARY.contains(prefixList.getFirst())) {
            //két operandust kell keresnünk, mert az operátor bináris
            out.writeStartElement(Operator.textToTagTranslate(prefixList.getFirst()));
            prefixList.remove(0);
            //az operandusok száma 2
            out.writeAttribute("operands", "2");
            out.writeCharacters("\n");
            //operandusok beolvasása
            this.recursivePrefixTextToXML(prefixList, out);
            this.recursivePrefixTextToXML(prefixList, out);
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (!prefixList.isEmpty()) {
            try {
                Apfloat a = new Apfloat(prefixList.getFirst());
            } catch (NumberFormatException numberFormatException) {
                //ha nem számot találtunk, és nem is a pí-t akkor ismeretlen operátorunk van.
                if (!prefixList.getFirst().equalsIgnoreCase("pi") && !prefixList.getFirst().equalsIgnoreCase("VAR")) {
                    throw new UnknownOperatorException(prefixList.getFirst());
                }
            }
            //ha számot találtunk, akkor csak legyártjuk a taget, és minden OK
            out.writeStartElement("number");
            //beírjuk a számot
            out.writeCharacters(prefixList.getFirst());
            //lezárjuk a tag-et
            out.writeEndElement();
            out.writeCharacters("\n");
            //eltávolítjuk a listáról a számot, amit már kiírtunk
            prefixList.removeFirst();
        }
    }

    /**
     * A expression szöveges kifejezésekben szereplő mátrixok kezelésére szolgáló metódus.
     * @param prefixList a kifejezés azon része, amelynek az elején a mátrix található
     * @param out a kimeneti Stream
     * @throws xmlhandling.UnknownOperatorException ha ismeretlen operátort találtunk.
     * @throws javax.xml.stream.XMLStreamException ha hiba történt a kiírás közben.
     * @throws java.io.FileNotFoundException ha a file paraméter által hivatkozott fájl nem írható.
     * @throws javax.xml.parsers.ParserConfigurationException ha gond lenne a parser beállításával.
     */
    private void prefixMatrixHandler(LinkedList<String> prefixList, XMLStreamWriter out) throws UnknownOperatorException, XMLStreamException, ExpressionLogicalException, ParserConfigurationException, FileNotFoundException {
        //ha az első elem [, akkor azt töröljük (az első elem [ lesz mindenképpen)
        prefixList.removeFirst();
        //}
        LinkedList<String> matrixList = new LinkedList<String>();
        //kell egy StringBuilder, hogy legyen miben tárolni a mátrix adatait
        StringBuilder m = new StringBuilder();
        //beállítjuk a mélységet és amíg a mátrix tart, minden adatot a listáról a StringBuilderbe rakunk.
        int deep = 0;
        m.append(prefixList.removeFirst());
        //itt zajlik az előző kommentben jelzett másolás
        while (!prefixList.isEmpty() && !(prefixList.getFirst().equalsIgnoreCase("]") && deep == 0)) {
            m.append(" ");
            if (prefixList.getFirst().equalsIgnoreCase("[")) {
                deep++;
            } else if (prefixList.getFirst().equalsIgnoreCase("]")) {
                deep--;
            }
            //hozzáadjuk az adatokat és a már bemásolt adatokat töröljük.
            m.append(prefixList.removeFirst());
        }
        //hozzáadjuk az adatokat elválasztó szóközöket
        m.append(" ");
        m.append(prefixList.getFirst());
        prefixList.removeFirst();
        //feltördeljük szóközöknél a mátrix adatait (az előző részek feleslegesek lehetnek néhány esetben, de van amikor nem)
        for (String s : this.tokenizer(m.toString())) {
            matrixList.add(s);
        }
        LinkedList<String> matrixListCopy = new LinkedList<String>();
        matrixListCopy.addAll(matrixList);
        //sorok és oszlopok számát nullázzuk.
        int rows = 0;
        int cols = 0;
        int lastCols = 0;
        while (!matrixListCopy.isEmpty() && !matrixListCopy.getFirst().equalsIgnoreCase("]")) {
            //számoljuk közben a sorokat és az oszlopokat
            while (!matrixListCopy.isEmpty() && !matrixListCopy.getFirst().equalsIgnoreCase("]") && !matrixListCopy.getFirst().equalsIgnoreCase("|")) {
                //minden elemnél meghívjuk a rekurzív építő metódust
                this.recursivePrefixTextToXML(matrixListCopy, XMLBuilder.getStream(new File(FileLocations.getDefaultTempData())));
                cols++;
            }
            //ha a sorokat törő | karaktert találtuk, akkor eltávolítjuk a lista elejéről
            if (!matrixListCopy.isEmpty() && matrixListCopy.getFirst().equalsIgnoreCase("|")) {
                matrixListCopy.removeFirst();
            }
            if (lastCols > 0 && lastCols == cols) {
                cols = 0;
            } else if (lastCols == 0) {
                lastCols = cols;
                cols = 0;
            } else {
                throw new ExpressionLogicalException("NUMBER_OF_COLS_NOT_MATCH");
            }
            rows++;
        }
        //ha a mátrix üres, akkor azt jelezzük a hívónak.
        if (rows == 0 && lastCols == 0) {
            throw new ExpressionLogicalException("EMPTY_MATRIX");
        //összefűzzük az adatokat, és beszúrjuk a sorok illetve oszlopok számát is.
        }
        out.writeStartElement("matrix");
        out.writeAttribute("rows", "" + rows);
        out.writeAttribute("cols", "" + lastCols);
        out.writeCharacters("\n");
        while (!matrixList.isEmpty() && !matrixList.getFirst().equalsIgnoreCase("]")) {
            //számoljuk közben a sorokat és az oszlopokat
            out.writeStartElement("row");
            out.writeCharacters("\n");
            while (!matrixList.isEmpty() && !matrixList.getFirst().equalsIgnoreCase("]") && !matrixList.getFirst().equalsIgnoreCase("|")) {
                out.writeStartElement("col");
                out.writeCharacters("\n");
                //minden elemnél meghívjuk a rekurzív építő metódust
                this.recursivePrefixTextToXML(matrixList, out);
                out.writeEndElement();
                out.writeCharacters("\n");
            }
            //ha a sorokat törő | karaktert találtuk, akkor eltávolítjuk a lista elejéről
            if (!matrixList.isEmpty() && matrixList.getFirst().equalsIgnoreCase("|")) {
                matrixList.removeFirst();
            }
            out.writeEndElement();
            out.writeCharacters("\n");
        }
        out.writeEndElement();
        out.writeCharacters("\n");
    }

}
