/*
 * 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;

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

    /**
     * Az alapértelmezett konstruktor, ami beállítja a kimeneti fájlt is.
     * @param file a kimeneti xml
     */
    public XMLBuilderFromInfixText(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 prefix szöveget a white-space karaktereknél
        LinkedList<String> infixList = tokenizer();
        //megvizsgáljuk, hogy a kapott fájl írható-e.
        if (file.exists() && !file.canWrite()) {
            throw new FileNotFoundException("CANT_WRITE_XML");
        } 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.recursiveInfixTextToXML(infixList, out);
        //lezárjuk a tageket, majd az egész Stream-et
        if (!infixList.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (String s : infixList) {
                sb.append(s);
            }
            throw new UnknownOperatorException(sb.toString());
        }
        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
        XMLBuilderFromInfixText.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> infixList = 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("\\[", " [ ").replaceAll("\\]", " ] ").replaceAll("\\s+", " ").split("\\s")) {
            infixList.add(s);
        }
        if (infixList.getFirst().equalsIgnoreCase("")) {
            infixList.removeFirst();
        }
        if (infixList.getLast().equalsIgnoreCase("")) {
            infixList.removeLast();
        }
        return infixList;
    }

    /**
     * A nyitó és záró zárójelek számát ellenőrző metódus.
     * @param infixList a kifejezés része, vagy egésze.
     * @return nem, ha a zárójelek száma megfelelő
     */
    private boolean bracketsAreNotPaired(LinkedList<String> infixList) {
        int br0 = 0;
        int br1 = 0;
        int br2 = 0;
        for (String s : infixList) {
            if (s.equalsIgnoreCase("(")) {
                br0++;
            } else if (s.equalsIgnoreCase(")")) {
                br0--;
            } else if (s.equalsIgnoreCase("[")) {
                br1++;
            } else if (s.equalsIgnoreCase("]")) {
                br1--;
            } else if (s.equalsIgnoreCase("sum")) {
                br2++;
            } else if (s.equalsIgnoreCase("prd")) {
                br2++;
            } else if (s.equalsIgnoreCase("max")) {
                br2++;
            } else if (s.equalsIgnoreCase("min")) {
                br2++;
            } else if (s.equalsIgnoreCase("}")) {
                br2--;
            }
        }
        return (br0 != 0 || br1 != 0 || br2 != 0);
    }

    /**
     * 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 infixList 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 xmlhandling.ExpressionLogicalException ha nem megfelelőek a zárójelek.
     * @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 recursiveInfixTextToXML(LinkedList<String> infixList, XMLStreamWriter out) throws UnknownOperatorException, XMLStreamException, ExpressionLogicalException, ParserConfigurationException, FileNotFoundException {
        //töröljük a külső zárójelet
        if (this.bracketsAreNotPaired(infixList)) {
            throw new ExpressionLogicalException("BRACKETS_ARE_NOT_PAIRED");
        }
        if (infixList.getFirst().equalsIgnoreCase("(") && infixList.getLast().equalsIgnoreCase(")")) {
            infixList.removeFirst(); //nyitó
            infixList.removeLast(); //záró
        } else {
            new Exception();
            throw new ExpressionLogicalException("NO_OPENING_AND/OR_CLOSING_BRACKET");
        }
        int index = this.getMainOperator(infixList);
        if (index == -1) {
            throw new ExpressionLogicalException("EMPTY_LIST");
        }
        String op = infixList.get(index);
        if (op.equalsIgnoreCase("[")) {
            //a mátrix-kezelőnek adjuk tovább a munkát.
            this.infixMatrixHandler(infixList, out);
        } else if (Operator.OTHER.contains(op)) {
            //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(op));
            int operands = 0;
            //eltávolítjuk az operátort
            infixList.removeFirst();
            //lemásoljuk a listát
            LinkedList<String> infixListCopy = new LinkedList<String>();
            infixListCopy.addAll(infixList);
            //a lista másolatának segítségével megszámoljuk az operandusokat
            //addig haladunk előre, és olvassuk be a paramétereket
            //amíg a { jelet nem találjuk
            while (!infixListCopy.isEmpty()) {
                if (infixListCopy.get(0).equalsIgnoreCase("}")) {
                    break;
                }
                this.recursiveInfixTextToXML(this.getNextOperand(infixListCopy), 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 (!infixList.isEmpty() && !infixList.getFirst().equalsIgnoreCase("}")) {
                this.recursiveInfixTextToXML(this.getNextOperand(infixList), out);
            }
            //eltávolítjuk a "}" jelet
            if (!infixList.isEmpty()) {
                infixList.removeFirst();
            } else {
                throw new ExpressionLogicalException("NO_}_FOUND");
            }
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (Operator.UNARY.contains(op)) {
            //egyoperandusú operátoroknál könnyű dolgunk van
            out.writeStartElement(Operator.textToTagTranslate(op));
            //eltávolítjuk az operátort
            infixList.remove(index);
            //az operandusok száma biztosan 1
            out.writeAttribute("operands", "1");
            out.writeCharacters("\n");
            //csak egy operandust olvasunk be
            this.recursiveInfixTextToXML(infixList, out);
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (Operator.BINARY.contains(op)) {
            //két operandust kell keresnünk, mert az operátor bináris
            out.writeStartElement(Operator.textToTagTranslate(op));
            //a listát két részlistára bontjuk
            LinkedList<String> left = new LinkedList<String>();
            LinkedList<String> right = new LinkedList<String>();
            int size = infixList.size();
            for (int i = 0; i < size; i++) {
                //a bal oldalt a bal oldalhoz
                if (i < (index)) {
                    left.add(infixList.removeFirst());
                //jobb oldalt a jobbhoz
                } else if (i > index) {
                    right.add(infixList.removeFirst());
                //töröljük a fő műveleti jelet
                } else if (i == index) {
                    infixList.removeFirst();
                }
            }
            //az operandusok száma 2
            out.writeAttribute("operands", "2");
            out.writeCharacters("\n");
            //operandusok beolvasása
            this.recursiveInfixTextToXML(left, out);
            this.recursiveInfixTextToXML(right, out);
            //lezárjuk az operátorhoz tartozó taget
            out.writeEndElement();
            out.writeCharacters("\n");
        } else if (!infixList.isEmpty()) {
            if (op.equalsIgnoreCase(")")) {
                //System.out.println(infixList);
                this.recursiveInfixTextToXML(infixList, out);
            } else {
                try {
                    Apfloat a = new Apfloat(op);
                } catch (NumberFormatException numberFormatException) {
                    //ha nem számot találtunk, és nem is a pí-t akkor ismeretlen operátorunk van.
                    if (!(op.equalsIgnoreCase("pi") || op.equalsIgnoreCase("VAR"))) {
                        throw new UnknownOperatorException(op);
                    }
                }
                //ha számot találtunk, akkor csak legyártjuk a taget, és minden OK
                out.writeStartElement("number");
                //beírjuk a számot
                out.writeCharacters(op);
                //kitöröljük a számot.
                infixList.removeFirst();
                //lezárjuk a tag-et
                out.writeEndElement();
                out.writeCharacters("\n");
            }
        }
    }

    /**
     * A expression szöveges kifejezésekben szereplő mátrixok kezelésére szolgáló metódus.
     * @param infixList 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 xmlhandling.ExpressionLogicalException ha gond van a zárójelekkel.
     * @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 infixMatrixHandler(LinkedList<String> infixList, XMLStreamWriter out) throws UnknownOperatorException, XMLStreamException, ExpressionLogicalException, ParserConfigurationException, FileNotFoundException {
        //ha az első elem [, akkor azt töröljük (az első elem [ lesz mindenképpen)
        if (infixList.getFirst().equalsIgnoreCase("[") && infixList.getLast().equalsIgnoreCase("]")) {
            infixList.removeFirst(); //nyitó
            infixList.removeLast(); //záró
        } else {
            new Exception();
            throw new ExpressionLogicalException("NO_OPENING_AND/OR_CLOSING_BRACKET");
        }
        //}
        LinkedList<String> matrixListCopy = new LinkedList<String>();
        matrixListCopy.addAll(infixList);
        //sorok és oszlopok számát nullázzuk.
        int rows = 0;
        int cols = 0;
        int lastCols = 0;
        while (!matrixListCopy.isEmpty()) {
            if (matrixListCopy.get(0).equalsIgnoreCase("]")) {
                break;
            //számoljuk közben a sorokat és az oszlopokat
            }
            while (!matrixListCopy.isEmpty()) {
                if (matrixListCopy.get(0).equalsIgnoreCase("]")) {
                    break;
                }
                if (matrixListCopy.get(0).equalsIgnoreCase("|")) {
                    break;
                //minden elemnél meghívjuk a rekurzív építő metódust
                }
                this.recursiveInfixTextToXML(this.getNextOperand(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();
            }
            rows++;
            if (lastCols > 0 && lastCols == cols) {
                cols = 0;
            } else if (lastCols == 0) {
                lastCols = cols;
                cols = 0;
            } else {
                throw new ExpressionLogicalException("NUMBER_OF_COLS_NOT_MATCH");
            }
        }
        //ha a mátrix üres, akkor azt jeleznünk kell
        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 (!infixList.isEmpty()) {
            if (infixList.get(0).equalsIgnoreCase("]")) {
                break;
            //számoljuk közben a sorokat és az oszlopokat
            }
            out.writeStartElement("row");
            out.writeCharacters("\n");
            while (!infixList.isEmpty()) {
                if (infixList.get(0).equalsIgnoreCase("|")) {
                    break;
                }
                if (infixList.get(0).equalsIgnoreCase("]")) {
                    break;
                }
                out.writeStartElement("col");
                out.writeCharacters("\n");
                //minden elemnél meghívjuk a rekurzív építő metódust
                this.recursiveInfixTextToXML(this.getNextOperand(infixList), out);
                out.writeEndElement();
                out.writeCharacters("\n");
            }
            //ha a sorokat törő | karaktert találtuk, akkor eltávolítjuk a lista elejéről
            if (!infixList.isEmpty() && infixList.getFirst().equalsIgnoreCase("|")) {
                infixList.removeFirst();
            }
            out.writeEndElement();
            out.writeCharacters("\n");
        }
        out.writeEndElement();
        out.writeCharacters("\n");
    }

    /**
     * Megkeresi egy tokenizált expression kifejezésben a fő műveleti jelet, és visszatér az indexével.
     * @param infixList a tokenizált
     * @return main a fő műveleti jel indexe
     */
    private int getMainOperator(LinkedList<String> infixList) {
        int main = -1;
        int deep = 0;
        //ha a lista mérete 1, akkor csak szám lehet
        if (infixList.size() == 1) {
            return 0;
        }
        //végigmegyünk a listán
        for (String s : infixList) {
            main++;
            //megvizsgáljuk, hogy mit találtunk
            if (s.equalsIgnoreCase("(")) {
                //ha ( növeljük a mélységet
                deep++;
            } else if (s.equalsIgnoreCase(")")) {
                //ha ) csökkentjük a mélységet
                deep--;
            } else if (Operator.UNARY.contains(s) && deep == 0) {
                //ha unáris operátor, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (Operator.BINARY.contains(s) && deep == 0) {
                //ha bináris operátor, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (Operator.OTHER.contains(s) && deep == 0) {
                //ha egyéb operátor, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (s.equalsIgnoreCase("[") && deep == 0) {
                //ha mátrix, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (s.equalsIgnoreCase("pi") && deep == 0) {
                //ha pí, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (s.equalsIgnoreCase("VAR") && deep == 0) {
                //ha VAR, és kívül vagyunk, akkor megtaláltuk, amit kerestünk
                return main;
            } else if (deep == 0) {
                //ha mást találtunk, akkor az egy ismeretlen operátor
                return main;
            }
        }
        return main;
    }

    /**
     * Megkeresi és levágja egy tokenizált expression kifejezés elején a következő operandust.
     * @param infixList a tokenizált lista
     * @return operand az első operandus adatainak listája
     */
    private LinkedList<String> getNextOperand(LinkedList<String> infixList) {
        LinkedList<String> operand = new LinkedList<String>();
        int deep = 0;
        //megkeressük a következő operandust
        while (!infixList.isEmpty()) {
            //levágunk egy elemet
            operand.add(infixList.removeFirst());
            if (operand.getLast().equalsIgnoreCase("(")) {
                deep++;
            // ( hatására a mélység nő
            } else if (operand.getLast().equalsIgnoreCase(")")) {
                deep--;
            // ) hatására a mélység csökken
            }
            if (deep == 0) {
                //ha a mélység 0, akkor kész
                return operand;
            }
        }
        return null;
    }
}