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

import expressiontree.*;
import expressiontree.operators.unary.*;
import expressiontree.operators.binary.*;
import expressiontree.operators.other.*;
import xmlhandling.UnknownOperatorException;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * Az operátorok absztrakt ősosztálya.
 * @author Esta
 */
public abstract class Operator extends ExpressionTreeElement implements Comparable {

    /**
     * A változó paraméterszámmal működő operátorok listája.
     */
    public static final LinkedList<String> OTHER = new LinkedList<String>();
    /**
     * Az egy operandussal működő operátorok listája.
     */
    public static final LinkedList<String> UNARY = new LinkedList<String>();
    /**
     * A két operandussal működő operátorok listája.
     */
    public static final LinkedList<String> BINARY = new LinkedList<String>();
    /**
     * A mátrixokra alkalmazható operátorok listája.
     */
    public static final LinkedList<String> MATRIX = new LinkedList<String>();
    /**
     * A trigonometrikus operátorok listája.
     */
    public static final LinkedList<String> TRIG = new LinkedList<String>();
    /**
     * A nem trigonometrikus egyoperandusú operátorok listája.
     */
    public static final LinkedList<String> NON_TRIG_UNARY = new LinkedList<String>();
    /**
     * Az a statikus inicializáló blokk, amely feltölti a három fenti listát az elemeikkel.
     */


    static {
        MATRIX.add("inv");
        MATRIX.add("det");
        MATRIX.add("tr");
        MATRIX.add("+");
        MATRIX.add("*");
        MATRIX.add("-");
        MATRIX.add("abs");

        TRIG.add("cos");
        TRIG.add("cosh");
        TRIG.add("sinh");
        TRIG.add("tanh");
        TRIG.add("acos");
        TRIG.add("acosh");
        TRIG.add("asinh");
        TRIG.add("atanh");
        TRIG.add("asin");
        TRIG.add("atan");
        TRIG.add("sin");
        TRIG.add("tan");

        OTHER.add("sum");
        OTHER.add("prd");
        OTHER.add("min");
        OTHER.add("max");

        UNARY.add("abs");
        UNARY.add("cos");
        UNARY.add("cbrt");
        UNARY.add("det");
        UNARY.add("exp");
        UNARY.add("!");
        UNARY.add("cosh");
        UNARY.add("sinh");
        UNARY.add("tanh");
        UNARY.add("acos");
        UNARY.add("acosh");
        UNARY.add("asinh");
        UNARY.add("atanh");
        UNARY.add("asin");
        UNARY.add("atan");
        UNARY.add("inv");
        UNARY.add("log");
        UNARY.add("neg");
        UNARY.add("sin");
        UNARY.add("sqrt");
        UNARY.add("tan");
        UNARY.add("tr");

        BINARY.add("+");
        BINARY.add("agm");
        BINARY.add("bic");
        BINARY.add("/");
        BINARY.add("/%");
        BINARY.add("gcd");
        BINARY.add("lcm");
        BINARY.add("%");
        BINARY.add("*");
        BINARY.add("^");
        BINARY.add("root");
        BINARY.add("-");

        for (String s : UNARY) {
            if (!TRIG.contains(s)) {
                NON_TRIG_UNARY.add(s);
            }
        }
    }

    /**
     * Visszaad egy operátor példányt, amit a kapott sztring (egy tag neve) alapján példányosít
     * @param tagName a tag neve, amiből kiderül, hogy mit kell példányosítani.
     * @return egy operátor példány.
     */
    public static Operator getInstanceOfTreeElement(String tagName) throws UnknownOperatorException {
        if (tagName.equalsIgnoreCase("addition") || tagName.equalsIgnoreCase("add")) {
            return new Addition();
        } else if (tagName.equalsIgnoreCase("arithmeticgeometricmean") || tagName.equalsIgnoreCase("agm")) {
            return new ArithmeticGeometricMean();
        } else if (tagName.equalsIgnoreCase("binomialcoefficient") || tagName.equalsIgnoreCase("bic")) {
            return new BinomialCoefficient();
        } else if (tagName.equalsIgnoreCase("divisionwithnoreminder") || tagName.equalsIgnoreCase("div")) {
            return new DivisionWithNoReminder();
        } else if (tagName.equalsIgnoreCase("divisionwithreminder") || tagName.equalsIgnoreCase("divr")) {
            return new DivisionWithReminder();
        } else if (tagName.equalsIgnoreCase("greatestcommondivisor") || tagName.equalsIgnoreCase("gcd")) {
            return new GreatestCommonDivisor();
        } else if (tagName.equalsIgnoreCase("leastcommonmultiple") || tagName.equalsIgnoreCase("lcm")) {
            return new LeastCommonMultiple();
        } else if (tagName.equalsIgnoreCase("modulo") || tagName.equalsIgnoreCase("mod")) {
            return new Modulo();
        } else if (tagName.equalsIgnoreCase("multiplication") || tagName.equalsIgnoreCase("mul")) {
            return new Multiplication();
        } else if (tagName.equalsIgnoreCase("raisetopower") || tagName.equalsIgnoreCase("pow")) {
            return new RaiseToPower();
        } else if (tagName.equalsIgnoreCase("root")) {
            return new Root();
        } else if (tagName.equalsIgnoreCase("subtraction") || tagName.equalsIgnoreCase("sub")) {
            return new Subtraction();
        } else if (tagName.equalsIgnoreCase("maximum") || tagName.equalsIgnoreCase("max")) {
            return new Maximum();
        } else if (tagName.equalsIgnoreCase("minimum") || tagName.equalsIgnoreCase("min")) {
            return new Minimum();
        } else if (tagName.equalsIgnoreCase("product") || tagName.equalsIgnoreCase("prd")) {
            return new Product();
        } else if (tagName.equalsIgnoreCase("sum")) {
            return new Sum();
        } else if (tagName.equalsIgnoreCase("absolutevalue") || tagName.equalsIgnoreCase("abs")) {
            return new AbsoluteValue();
        } else if (tagName.equalsIgnoreCase("norm")) {
            return new AbsoluteValue();
        } else if (tagName.equalsIgnoreCase("cosine") || tagName.equalsIgnoreCase("cos")) {
            return new Cosine();
        } else if (tagName.equalsIgnoreCase("cuberoot") || tagName.equalsIgnoreCase("cbrt")) {
            return new CubeRoot();
        } else if (tagName.equalsIgnoreCase("determinant") || tagName.equalsIgnoreCase("det")) {
            return new Determinant();
        } else if (tagName.equalsIgnoreCase("exponent") || tagName.equalsIgnoreCase("exp")) {
            return new Exponent();
        } else if (tagName.equalsIgnoreCase("factorial") || tagName.equalsIgnoreCase("fact")) {
            return new Factorial();
        } else if (tagName.equalsIgnoreCase("hyperboliccosine") || tagName.equalsIgnoreCase("cosh")) {
            return new HyperbolicCosine();
        } else if (tagName.equalsIgnoreCase("hyperbolicsine") || tagName.equalsIgnoreCase("sinh")) {
            return new HyperbolicSine();
        } else if (tagName.equalsIgnoreCase("hyperbolictangent") || tagName.equalsIgnoreCase("tanh")) {
            return new HyperbolicTangent();
        } else if (tagName.equalsIgnoreCase("inversecosine") || tagName.equalsIgnoreCase("acos")) {
            return new InverseCosine();
        } else if (tagName.equalsIgnoreCase("inversehyperboliccosine") || tagName.equalsIgnoreCase("acosh")) {
            return new InverseHyperbolicCosine();
        } else if (tagName.equalsIgnoreCase("inversehyperbolicsine") || tagName.equalsIgnoreCase("asinh")) {
            return new InverseHyperbolicSine();
        } else if (tagName.equalsIgnoreCase("inversehyperbolictangent") || tagName.equalsIgnoreCase("atanh")) {
            return new InverseHyperbolicTangent();
        } else if (tagName.equalsIgnoreCase("inversesine") || tagName.equalsIgnoreCase("asin")) {
            return new InverseSine();
        } else if (tagName.equalsIgnoreCase("inversetangent") || tagName.equalsIgnoreCase("atan")) {
            return new InverseTangent();
        } else if (tagName.equalsIgnoreCase("invert") || tagName.equalsIgnoreCase("inv")) {
            return new Invert();
        } else if (tagName.equalsIgnoreCase("logarithm") || tagName.equalsIgnoreCase("log")) {
            return new Logarithm();
        } else if (tagName.equalsIgnoreCase("negate") || tagName.equalsIgnoreCase("neg")) {
            return new Negate();
        } else if (tagName.equalsIgnoreCase("sine") || tagName.equalsIgnoreCase("sin")) {
            return new Sine();
        } else if (tagName.equalsIgnoreCase("squareroot") || tagName.equalsIgnoreCase("sqrt")) {
            return new SquareRoot();
        } else if (tagName.equalsIgnoreCase("tangent") || tagName.equalsIgnoreCase("tan")) {
            return new Tangent();
        } else if (tagName.equalsIgnoreCase("transponate") || tagName.equalsIgnoreCase("tr")) {
            return new Transponate();
        } else {
            throw new UnknownOperatorException(tagName);
        }
    }

    /**
     * Olyan privát metódus, ami ha kap egy szöveges utasításban szereplő operátort lefordítja azt a megfelelő xml tag nevére.
     * @param text az utasítás
     * @return az utasítás xml tagba írt neve
     * @throws expressiontree.xmlhandling.UnknownOperatorException ha ismeretlen operátort találtunk.
     */
    public static String textToTagTranslate(String text) throws UnknownOperatorException {
        //kétoperandusúak
        if (text.equalsIgnoreCase("+")) {
            return "addition";
        } else if (text.equalsIgnoreCase("agm")) {
            return "arithmeticgeometricmean";
        } else if (text.equalsIgnoreCase("bic")) {
            return "binomialcoefficient";
        } else if (text.equalsIgnoreCase("/")) {
            return "divisionwithnoreminder";
        } else if (text.equalsIgnoreCase("/%")) {
            return "divisionwithreminder";
        } else if (text.equalsIgnoreCase("gcd")) {
            return "greatestcommondivisor";
        } else if (text.equalsIgnoreCase("lcm")) {
            return "leastcommonmultiple";
        } else if (text.equalsIgnoreCase("%")) {
            return "modulo";
        } else if (text.equalsIgnoreCase("*")) {
            return "multiplication";
        } else if (text.equalsIgnoreCase("^")) {
            return "raisetopower";
        } else if (text.equalsIgnoreCase("root")) {
            return "root";
        } else if (text.equalsIgnoreCase("-")) {
            return "subtraction";
        //változó operandusszámúak
        } else if (text.equalsIgnoreCase("max")) {
            return "maximum";
        } else if (text.equalsIgnoreCase("min")) {
            return "minimum";
        } else if (text.equalsIgnoreCase("prd")) {
            return "product";
        } else if (text.equalsIgnoreCase("sum")) {
            return "sum";
        //egyoperandusúak
        } else if (text.equalsIgnoreCase("abs")) {
            return "absolutevalue";
        } else if (text.equalsIgnoreCase("cos")) {
            return "cosine";
        } else if (text.equalsIgnoreCase("cbrt")) {
            return "cuberoot";
        } else if (text.equalsIgnoreCase("det")) {
            return "determinant";
        } else if (text.equalsIgnoreCase("exp")) {
            return "exponent";
        } else if (text.equalsIgnoreCase("!")) {
            return "factorial";
        } else if (text.equalsIgnoreCase("cosh")) {
            return "hyperboliccosine";
        } else if (text.equalsIgnoreCase("sinh")) {
            return "hyperbolicsine";
        } else if (text.equalsIgnoreCase("tanh")) {
            return "hyperbolictangent";
        } else if (text.equalsIgnoreCase("acos")) {
            return "inversecosine";
        } else if (text.equalsIgnoreCase("acosh")) {
            return "inversehyperboliccosine";
        } else if (text.equalsIgnoreCase("asinh")) {
            return "inversehyperbolicsine";
        } else if (text.equalsIgnoreCase("atanh")) {
            return "inversehyperbolictangent";
        } else if (text.equalsIgnoreCase("asin")) {
            return "inversesine";
        } else if (text.equalsIgnoreCase("atan")) {
            return "inversetangent";
        } else if (text.equalsIgnoreCase("inv")) {
            return "invert";
        } else if (text.equalsIgnoreCase("log")) {
            return "logarithm";
        } else if (text.equalsIgnoreCase("neg")) {
            return "negate";
        } else if (text.equalsIgnoreCase("sin")) {
            return "sine";
        } else if (text.equalsIgnoreCase("sqrt")) {
            return "squareroot";
        } else if (text.equalsIgnoreCase("tan")) {
            return "tangent";
        } else if (text.equalsIgnoreCase("tr")) {
            return "transponate";
        } else {
            //ismeretlen
            throw new UnknownOperatorException(text);
        }
    }

    /**
     * Visszatér egy menübe illeszthető operátor példánnyal, amire a szöveges formájával hivatkoztunk.
     * @param s Az operátor szöveges formája.
     * @return A megfelelő operátor objektum
     * @throws xmlhandling.UnknownOperatorException ha a szöveges forma ismeretlen operátoré.
     */
    public static Operator getMenuItem(String s) throws UnknownOperatorException {
        return Operator.getInstanceOfTreeElement(Operator.textToTagTranslate(s));
    }
    /**
     * Az operandusok kollekciója.
     */
    protected ArrayList<ExpressionTreeElement> operands;

    /**
     * Visszaadja az Operátor gyermekeinek (az operandusoknak) a kollekcióját.
     * @return operands
     */
    public ArrayList<ExpressionTreeElement> getOperands() {
        return operands;
    }

    /**
     * Az Operátor egy gyermekének (egy operandusnak) a beállítására szolgál.
     * @param e a gyermek.
     */
    public void addOperands(ExpressionTreeElement e) {
        this.operands.add(e);
    }

    /**
     * Konstruktor, ami beállítja az operandusok számát is.
     * @param numberOfOperands
     */
    public Operator(int numberOfOperands) {
        super(true);
        this.operands = new ArrayList<ExpressionTreeElement>(numberOfOperands);
    }

    /**
     * Alapértelmezett konstruktor.
     */
    public Operator() {
        super(true);
        this.operands = new ArrayList<ExpressionTreeElement>();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Operator other = (Operator) obj;
        if (this.operands != other.operands && (this.operands == null || !this.operands.equals(other.operands))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 83 * hash + (this.operands != null ? this.operands.hashCode() : 0);
        return hash;
    }

    /**
     * Megadja, hogy mennyi karakterrel kell eltolni az első operandus pozícióját az operátor elejétől számítva.
     * @param b Azt mutatja meg, hogy infix-e az operátor (ha igaz, akkor infix)
     * @return Az eltolás mértéke
     */
    public int operandOffset(boolean b) {
        if (b) {
            return this.toText(true).indexOf("(") + 1;
        } else {
            return this.toText(false).length();
        }
    }

    /**
     * Kiírja a operátor infix vagy prefix szöveges formáját.
     * @param b Azt mutatja meg, hogy infix-e az operátor (ha igaz, akkor infix)
     * @return Az operátor szövegesen. Ha b igaz, zárójeleket is szúr be.
     */
    public abstract String toText(boolean b);

    /**
     * Visszaadja a jvm nyelvi beállításainak megfelelő nevét az adott operátornak.
     * @return Az operátor neve.
     */
    public abstract String getLocalizedName();

    @Override
    public String toString() {
        return this.getLocalizedName();
    }

    public int compareTo(Object o) {
        return this.getLocalizedName().compareTo(((Operator)o).getLocalizedName());
    }
}
