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

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.Scanner;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Document;
import propertyhandler.Properties;

/**
 * A szövegből XML-t építő osztályok őse.
 * @author Esta
 */
public abstract class XMLBuilder {

    /**
     * A kifejezés, amiből xml-t kell gyártani.
     */
    protected String expression;
    /**
     * A kifejezés pontossága.
     */
    protected long precision;
    /**
     * A kimeneti fájl.
     */
    protected File file;
    /**
     * Azt jelzi, hogy felülírhatjuk-e a fájlokat. Alapból false.
     */
    protected boolean overwrite = false;

    /**
     * A fájlok felülírhatóságát beállító metódus.
     * @param overwrite a beállítás új értéke.
     */
    public void setOverwrite(boolean overwrite) {
        this.overwrite = overwrite;
    }

    /**
     * A kifejezést beállító metódus.
     * @param expression a kifejezés új értéke.
     */
    public void setExpression(String expression) {
        this.expression = expression;
    }

    /**
     * A pontosságot beállító metódus.
     * @param precision a pontosság új értéke.
     */
    public void setPrecision(long precision) {
        this.precision = precision;
    }

    /**
     * Beállítja a kimeneti fájl értékét.
     * @param file a kimeneti fájl.
     */
    public void setFile(File file) {
        this.file = file;
    }

    /**
     * A kifejezés tokenizálását elvégző metódus.
     * @return list a tokenek listájával.
     */
    protected abstract LinkedList<String> tokenizer();

    /**
     * Elvégzi az xml-be konvertálást.
     * @param portable azt mutatja, hogy legyen-e hordozható az eredményként kapott fájl.
     * @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.
     */
    public abstract void convertToXML(boolean portable) throws IOException, UnknownOperatorException, FileNotFoundException, ParserConfigurationException, XMLStreamException, ExpressionLogicalException;

    /**
     * Formázza az xml dokumentum szövegét, azaz beszúrja a sorok elejére a megfelelő behúzásokat.
     * @param file a behúzások nélküli xml dokumentum
     * @throws java.io.FileNotFoundException ha a fájl nem található.
     */
    public static void consolidate(File file) throws FileNotFoundException {
        //soronként tördeljük a szöveget, és az adatokat egy tömbben tároljuk.
        StringBuilder sb = new StringBuilder();
        //létrehozzuk az új sorok listáját
        LinkedList<String> newLines = new LinkedList<String>();
        //a mélység kezdetben 0
        int deep = 0;
        //végigmegyünk az összes soron
        Scanner sc = new Scanner(file);
        LinkedList<String> lines = new LinkedList<String>();
        while (sc.hasNextLine()) {
            lines.add(sc.nextLine());
        }
        sc.close();
        for (String s : lines) {
            if (s.equalsIgnoreCase("")) {
                //üres sornál csak hozzáadjuk a sort az új sorokhoz
                newLines.add("\n");
            } else if (s.startsWith("</")) {
                //záró tag-nél a behúzás csökken 3 karakterrel
                sb.deleteCharAt(0);
                sb.deleteCharAt(0);
                sb.deleteCharAt(0);
                newLines.add(sb.toString() + s + "\n");
                deep--;
            } else if (s.startsWith("<number>")) {
                //ha számot találtunk, akkor a behúzás mértéke nem változik, mert a végén ott a záró tag is.
                newLines.add(sb.toString() + s + "\n");
            } else if (s.startsWith("<") && !s.startsWith("<!--") && !s.startsWith("<?")) {
                //nyitó tag esetén a behúzás 3-mal nő
                newLines.add(sb.toString() + s + "\n");
                deep++;
                sb.append("   ");
            } else {
                //egyébként csak átmásoljuk az adatokat
                newLines.add(s + "\n");
            }
        }
        //összefűzzük a sorokat
        sb = new StringBuilder();
        for (String s : newLines) {
            sb.append(s);
        }
        //visszatérünk a kész szöveggel
        PrintWriter pw = new PrintWriter(file);
        pw.print(sb.toString());
        pw.close();
    }

    /**
     * Visszaad egy XMLStreamWritert, amivel a kapott fájlba írhatunk.
     * @param file a fájl, amibe írni akarunk
     * @return out az Stream
     * @throws javax.xml.parsers.ParserConfigurationException ha nem sikerült a parser konfiguráció.
     * @throws javax.xml.stream.XMLStreamException ha hiba van a Streammel.
     * @throws java.io.FileNotFoundException ha nem található a fájl.
     */
    protected static XMLStreamWriter getStream(File file) throws ParserConfigurationException, XMLStreamException, FileNotFoundException {
        //létrehozzuk a document-et
        Document doc = XMLBuilder.getDocument();
        //beállítjuk a dokumentum XML verzióját
        doc.setXmlVersion("1.0");
        XMLOutputFactory outfact = javax.xml.stream.XMLOutputFactory.newInstance();
        //nyitunk egy új Stream-et
        XMLStreamWriter out = outfact.createXMLStreamWriter(new FileOutputStream(file), doc.getXmlEncoding());
        return out;
    }

    /**
     * Visszaadja egy dokumentumot, ami szükséges lesz a kiíráskor.
     * @return  doc a DOM Document.
     * @throws javax.xml.parsers.ParserConfigurationException ha nem sikerült a parser beállítása.
     */
    protected static Document getDocument() throws ParserConfigurationException {
        //létrehozzuk a document-et
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        //engedélyezzük a validálást
        builderFactory.setValidating(true);
        DocumentBuilder builder = builderFactory.newDocumentBuilder();
        Document doc = builder.newDocument();
        //beállítjuk a dokumentum XML verzióját
        doc.setXmlVersion("1.0");
        return doc;
    }

    /**
     * Az a metódus, ami elvégzi a dokumentumhoz a header hozzáadását.
     * @param doc a dokumentum.
     * @param out a kimeneti Stream.
     * @param file Az XML file.
     * @param portable Azt adja meg, hogy legyen-e hordozható a file.
     * @throws javax.xml.stream.XMLStreamException ha a kiírás során hiba lenne.
     * @throws java.io.FileNotFoundException Ha nem sikerült az alapértelmezett XSD file-t másolni.
     */
    protected static void addHeader(Document doc, XMLStreamWriter out, File file, boolean portable, long precision) throws XMLStreamException, FileNotFoundException, IOException {
        //hozzáadjuk az xml headert
        out.writeStartDocument(Charset.defaultCharset() + "", doc.getXmlVersion());
        out.writeCharacters("\n");
        //szokásos komment
        out.writeCharacters("\n\n");
        out.writeComment("\n" +
                "Author     : " + Properties.getUserName() + "\n" +
                "Description: XMLCalculator expression document.\n" +
                "Saved with XMLCalculator.\n");
        out.writeCharacters("\n\n");
        //ha nem hordozható, akkor a default xsl-t használjuk.
        if (!portable) {
            out.writeStartElement("expression");
            out.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            out.writeAttribute("\nxmlns","http://localhost/");
            out.writeAttribute("\nxsi:schemaLocation", "http://localhost/ \n"+ propertyhandler.FileLocations.getDefaultTempXSD());
            out.writeAttribute("\nprecision", "" + precision);
        //ha hordozható, akkor a file nevével egyező nevű filet használunk, csak a kiterjesztés lesz más
        } else {
            String xsd = XMLBuilder.createXSD(file);
            out.writeStartElement("expression");
            out.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            out.writeAttribute("\nxmlns","http://localhost/");
            out.writeAttribute("\nxsi:schemaLocation", "http://localhost/ \n" + xsd);
            out.writeAttribute("\nprecision", "" + precision);
        }
        
    }

    /**
     * Lemásolja a default XSD-t, ha szükséges, hogy a kapott xml fájllal együtt hordozhassuk.
     * @param file az xml fájl.
     * @return xsd az XSD dokumentum helye.
     * @throws java.io.FileNotFoundException Ha hiba van a másoláskor.
     * @throws java.io.IOException Ha hiba van az új fájl léterhozásakor.
     */
    public static String createXSD(File file) throws FileNotFoundException, IOException {
        String xsd;
        String fileName;
        //XML-t kaptunk?
        if (file.getName().toLowerCase().endsWith(".xml")) {
            //lecseréljük a kiterjesztést
            fileName = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 4);
            fileName = fileName.concat(".xsd");
            File xsdFile = new File(fileName);
            xsd = xsdFile.getName();
            xsdFile.createNewFile();
            File defaultXSD = new File(propertyhandler.FileLocations.getDefaultXSD());
            if (!defaultXSD.canRead()) {
                throw new FileNotFoundException("DEFAULT_XSD_CANT_READ");
            }
            if (!xsdFile.canWrite()) {
                throw new FileNotFoundException("PORTABLE_XSD_CANT_WRITE");
            }
            //másoljuk a default XSD-t
            Scanner sc = new Scanner(defaultXSD);
            PrintWriter pw = new PrintWriter(xsdFile);
            while (sc.hasNextLine()) {
                pw.println(sc.nextLine());
            }
            pw.close();
            sc.close();
            return xsd;
        } else {
            return "ERROR: Only .xml files are enabled.";
        }
    }

    /**
     * Lemásolja a default XSL-t, ha szükséges, hogy a kapott xml fájllal együtt hordozhassuk.
     * @param file az xml fájl.
     * @param matrix azt jelzi, hogy mátrix van-e az XML-ben eltárolva.
     * @return xsl az XSL dokumentum helye.
     * @throws java.io.FileNotFoundException Ha hiba van a másoláskor.
     * @throws java.io.IOException Ha hiba van az új fájl léterhozásakor.
     */
    public static String createXSL(File file, boolean matrix) throws FileNotFoundException, IOException {
        String xsl;
        String fileName;
        File defaultXSL;
        //XML-t kaptunk?
        if (file.getName().endsWith(".xml") || file.getName().endsWith(".XML")) {
            //lecseréljük a kiterjesztést
            fileName = file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - 4);
            fileName = fileName.concat(".xsl");
            File xslFile = new File(fileName);
            xslFile.createNewFile();
            xsl = xslFile.getName();
            if (matrix) {
                defaultXSL = new File(propertyhandler.FileLocations.getDefaultMatrixXSL());
            } else {
                defaultXSL = new File(propertyhandler.FileLocations.getDefaultNumberXSL());
            }
            if (!defaultXSL.canRead()) {
                throw new FileNotFoundException("DEFAULT_XSL_CANT_READ");
            }
            if (!xslFile.canWrite()) {
                throw new FileNotFoundException("PORTABLE_XSL_CANT_WRITE");
            }
            //másoljuk a default XSL-t
            Scanner sc = new Scanner(defaultXSL);
            PrintWriter pw = new PrintWriter(xslFile);
            while (sc.hasNextLine()) {
                pw.println(sc.nextLine());
            }
            pw.close();
            return xsl;
        } else {
            return "ERROR: Only .XML or .xml files are enabled.";
        }
    }
}
