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

import expressiontree.ExpressionTreeElement;
import expressiontree.operators.IncompatibleNumberFormatException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
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.apfloat.Apfloat;
import org.w3c.dom.Document;
import propertyhandler.Properties;

/**
 * A mátrixok ősosztálya.
 * @author Esta
 */
public class Matrix extends ExpressionTreeElement {

    /**
     * A mátrix sorainak a száma.
     */
    protected int rows;
    /**
     * A mátrix oszlopainak a száma.
     */
    protected int cols;
    /**
     * A mátrix értékei.
     */
    protected Apfloat[][] values;

    /**
     * A mátrix oszlopainak számával visszatérő metódus.
     * @return cols
     */
    public int getCols() {
        return cols;
    }

    /**
     * A mátrix sorainak számával visszatérő metódus.
     * @return rows
     */
    public int getRows() {
        return rows;
    }

    /**
     * A mátrix értékeivel tér vissza.
     * @return values
     */
    public Apfloat[][] getMatrixValues() {
        return values;
    }

    /**
     * Beállítja a mátrix új értékeit.
     * @param values az új mátrix.
     */
    public void setValues(Apfloat[][] values) {
        this.values = values;
    }

    /**
     * Beállítja a mátrix egy koordinátájának az értékét.
     * @param value az érték.
     * @param row a sor száma.
     * @param col az oszlop száma.
     */
    public void setCoordinate(Apfloat value, int row, int col) {
        if (col <= this.cols && col > 0 && row <= this.rows && row > 0) {
            this.values[row - 1][col - 1] = value;
        } else {
            throw new IndexOutOfBoundsException("ERROR_OUT_OF_BOUNDS");
        }
    }

    /**
     * Az alapértelmezett konstruktor, ami beállítja a mátrix méretét is.
     * @param rows sorok száma.
     * @param cols oszlopok száma.
     */
    public Matrix(int rows, int cols) {
        this(rows, cols, ExpressionTreeElement.DEFALUT_PRECISION);
    }

    /**
     * Az alapértelmezett konstruktor, ami beállítja a mátrix méretét is.
     * @param rows sorok száma.
     * @param cols oszlopok száma.
     * @param precision a pontosság.
     */
    public Matrix(int rows, int cols, long precision) {
        super(false);
        this.rows = rows;
        this.cols = cols;
        this.values = new Apfloat[rows][cols];
        this.precision = precision;
    }

    @Override
    public ExpressionTreeElement getValue() throws IncompatibleNumberFormatException {
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof Matrix) {
            Matrix m = (Matrix) o;
            if (this.rows != m.rows || this.cols != m.cols) {
                return false;
            }
            for (int i = 0; i < m.rows; i++) {
                if (!Arrays.equals(this.values[i], m.values[i])) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 41 * hash + this.rows;
        hash = 41 * hash + this.cols;
        hash = 41 * hash + (this.values != null ? this.values.hashCode() : 0);
        return hash;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        long[] colsize = new long[this.cols];
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                if (this.values[i][j].toString(true).length() > colsize[j]) {
                    colsize[j] = this.values[i][j].toString(true).length();
                }
            }
        }
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                for (long k = 0; k < colsize[j] - this.values[i][j].toString(true).length() + 1; k++) {
                    sb.append(" ");
                }
                sb.append(this.values[i][j].toString(true));
                if (j + 1 != this.cols) {
                    sb.append(" ");
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * Olyan toString metódus, ami "szépen" írja ki a mátrixot, ha a pretty true.
     * @param pretty a szépséget befolyásoló paraméter.
     * @return egy szép mátrix
     */
    public String toString(boolean pretty) {
        if (!pretty) {
            return this.toString();
        }
        StringBuilder sb = new StringBuilder();
        long[] colsize = new long[this.cols];
        long[] dotplace = new long[this.cols];
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                if (this.values[i][j].toString(true).length() > colsize[j]) {
                    colsize[j] = this.values[i][j].toString(true).length();
                }
                if (!(this.values[i][j].toString(true).contains("."))) {
                    if (this.values[i][j].toString(true).length() > dotplace[j]) {
                        dotplace[j] = this.values[i][j].toString(true).length();
                    }
                } else if (this.values[i][j].toString(true).indexOf('.') > dotplace[j]) {
                    dotplace[j] = this.values[i][j].toString(true).indexOf('.');
                }
            }
        }
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                long l;
                if (!(this.values[i][j].toString(true).contains("."))) {
                    l = dotplace[j] - this.values[i][j].toString(true).length() + 1;
                } else {
                    l = dotplace[j] - this.values[i][j].toString(true).indexOf('.') + 1;
                }
                for (long k = 0; k < l; k++) {
                    sb.append(" ");
                }
                sb.append(this.values[i][j].toString(true));
                if (j + 1 != this.cols) {
                    sb.append(" ");
                }
                if (!(this.values[i][j].toString(true).contains("."))) {
                    l = colsize[j] - dotplace[j];
                } else {
                    l = colsize[j] - dotplace[j] - this.values[i][j].toString(true).length() + this.values[i][j].toString(true).indexOf('.');
                }
                for (long k = 0; k < l; k++) {
                    sb.append(" ");
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * A mátrixot XML dokumentummá konvertálva adja vissza.
     * @return az XML szövege.
     * @deprecated a saveToXML metódust kell használni helyette, mert ez még StringBuilderrel van megírva.
     */
    public String toXML() {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\"?>\n");
        sb.append("\n<expression>\n");
        sb.append("   <matrix rows=\"");
        sb.append(this.getRows());
        sb.append("\" cols=\"");
        sb.append(this.getCols());
        sb.append("\">\n");
        for (int i = 0; i < this.getRows(); i++) {
            sb.append("      <row>\n");
            for (int j = 0; j < this.getCols(); j++) {
                sb.append("         <col>\n");
                sb.append("            <number>");
                sb.append(this.getMatrixValues()[i][j].toString(true));
                sb.append("</number>\n");
                sb.append("         </col>\n");
            }
            sb.append("      </row>\n");
        }
        sb.append("   </matrix>\n");
        sb.append("</expression>\n");
        return sb.toString();
    }

    /**
     * Egy kapott File-ba lementi a Matrix tartalmát XML-ben.
     * @param file egy XML fájl
     * @throws java.io.FileNotFoundException, ha a File nem írható.
     * @throws java.io.IOException, ha a célfájl nem létezik, vagy nem hozható létre.
     * @throws javax.xml.parsers.ParserConfigurationException Ha nem sikerült a parsert beállítani.
     * @throws javax.xml.stream.XMLStreamException Ha hiba volt a kiíráskor.
     */
    public void saveToXML(File file) throws FileNotFoundException, IOException, ParserConfigurationException, XMLStreamException {
        file.createNewFile();
        if (!file.canWrite()) {
            throw new FileNotFoundException("CANT_WRITE");
        }
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setValidating(true);
        DocumentBuilder builder = builderFactory.newDocumentBuilder();
        Document doc = builder.newDocument();
        doc.setXmlVersion("1.0");
        XMLOutputFactory outfact = javax.xml.stream.XMLOutputFactory.newInstance();
        XMLStreamWriter out = outfact.createXMLStreamWriter(new FileOutputStream(file), doc.getXmlEncoding());
        out.writeStartDocument(doc.getXmlEncoding(), doc.getXmlVersion());
        //hozzáadjuk a style-sheet-et
        out.writeCharacters("\n");
        out.writeStartElement("?xml-stylesheet type=\"text/xsl\" href=\"" + xmlhandling.XMLBuilder.createXSL(file, true) + "\"?");
        out.writeCharacters("\n");
        out.writeCharacters("\n");
        //comment a tetejére
        out.writeComment("\n" +
                "Author     : " + Properties.getUserName() + "\n" +
                "Description: XMLCalculator output document.\n" +
                "Saved with XMLCalculator.\n");
        out.writeCharacters("\n");
        //hozzáadjuk a xsd-t
        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"+ xmlhandling.XMLBuilder.createXSD(file));
        out.writeAttribute("\nprecision", "" + this.precision);
        out.writeCharacters("\n   ");
        out.writeStartElement("matrix");
        out.writeAttribute("rows", "" + this.getRows());
        out.writeAttribute("cols", "" + this.getCols());
        for (int i = 0; i < this.getRows(); i++) {
            out.writeCharacters("\n      ");
            out.writeStartElement("row");
            for (int j = 0; j < this.getCols(); j++) {
                out.writeCharacters("\n         ");
                out.writeStartElement("col");
                out.writeCharacters("\n            ");
                out.writeStartElement("number");
                out.writeCharacters(this.getMatrixValues()[i][j].toString(true));
                out.writeEndElement();
                out.writeCharacters("\n         ");
                out.writeEndElement();
            }
            out.writeCharacters("\n      ");
            out.writeEndElement();
        }
        out.writeCharacters("\n   ");
        out.writeEndElement();
        out.writeCharacters("\n");
        out.writeEndElement();
        out.writeCharacters("\n");
        out.close();
    }
}
