package math.polynomials;

import math.utils.ArrayUtil;

import java.util.Arrays;
import java.util.Comparator;

public class Poly implements Cloneable, IFunct {
    private Jdm[] poly;
    private MaxFirstComp max = new MaxFirstComp();

    public Poly() {
        poly = new Jdm[0];
    }

    public Poly(Jdm[] poly) {
        this.poly = PolyUtil.clone(poly);
        this.reduce(max);
    }

    @SuppressWarnings("CopyConstructorMissesField")
    public Poly(Poly poly) {
        this.poly = PolyUtil.clone(poly.getPoly());
    }

    public Poly(Jdm jdm) {
        poly = new Jdm[1];
        poly[0] = jdm.clone();
    }

    //---------------------- overrides ---------------------------------------
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < poly.length; i++) {
            if (poly[i].getCoeff() > 0) {
                if (i > 0) {
                    sb.append("+");
                }
                sb.append(poly[i]);
            }
            if (poly[i].getCoeff() < 0) {
                sb.append(poly[i]);
            }
        }
        return sb.toString();
    }

    @SuppressWarnings("MethodDoesntCallSuperMethod")
    @Override
    public Poly clone() {
        Jdm[] arr = PolyUtil.clone(this.poly);
        return new Poly(arr);
    }

    @Override
    public int hashCode() {
        return 17 * Arrays.hashCode(poly);
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (getClass() != other.getClass()) {
            return false;
        }
        if (!(other instanceof Poly)) {
            return false;
        }
        Poly a = (Poly) other;
        if (poly.length != a.getPoly().length) {
            return false;
        }
        for (int i = 0; i < poly.length; i++) {
            if (!poly[i].equals(a.poly[i])) {
                return false;
            }
        }
        return true;
    }

    @Override
    public double getValue(double... x) {
        double sum = 0.0;
        for (Jdm jdm : poly) {
            sum += jdm.getValue(x[0]);
        }
        return sum;
    }

    //---------------------- setters and getters -----------------------------
    public void setPoly(Jdm[] poly) {
        this.poly = PolyUtil.clone(poly);
        this.reduce(max);
    }

    public Jdm[] getPoly() {
        return PolyUtil.clone(poly);
    }

    public MaxFirstComp getMax() {
        return max;
    }

    public void setMax(MaxFirstComp max) {
        this.max = max;
    }

    //---------------------- instance methods --------------------------------
    public void reduce(Comparator<Jdm> order) {
        sort(order);
        for (int i = 0; i < poly.length - 1; i++) {
            if (poly[i].isSimilarTo(poly[i + 1])) {
                double x = poly[i].getCoeff() + poly[i + 1].getCoeff();
                if (x != 0) {
                    poly[i].setCoeff(x);
                    poly = ArrayUtil.shortenArray(poly, i + 1);
                    i--;
                } else {
                    poly = ArrayUtil.shortenArray(poly, i, i + 1);
                }
            }
        }
    }

    public void sort(Comparator<Jdm> order) {
        Arrays.sort(poly, order);
    }

    public int size() {
        return poly.length;
    }

    public int degree() {
        return this.poly[0].degree();
    }

    public boolean isFullPoly() {
        this.reduce(new MaxFirstComp());
        //noinspection RedundantIfStatement
        if (this.degree() + 1 == poly.length) {
            return true;
        }
        return false;
    }

    public void add(Jdm jdm) {
        poly = ArrayUtil.extendArray(poly, 1);
        poly[poly.length - 1] = jdm.clone();
        this.reduce(max);
    }

    public void add(Poly poly) {
        int len1 = this.size();
        int len2 = poly.size();
        this.poly = ArrayUtil.extendArray(this.poly, poly.size());
        for (int i = len1, j = 0; i < len1 + len2; i++, j++) {
            this.poly[i] = poly.getPoly()[j].clone();
        }
        this.reduce(max);
    }

    public void sub(Jdm jdm) {
        poly = ArrayUtil.extendArray(poly, 1);
        poly[poly.length - 1] = PolyUtil.mult(jdm.clone(), -1);
        this.reduce(max);
    }

    public void sub(Poly poly) {
        int len1 = this.size();
        int len2 = poly.size();
        this.poly = ArrayUtil.extendArray(this.poly, poly.size());
        for (int i = len1, j = 0; i < len1 + len2; i++, j++) {
            this.poly[i] = PolyUtil.mult(poly.getPoly()[j].clone(), -1);
        }
        this.reduce(max);
    }

    public void mult(int m) {
        for (Jdm jdm : poly) {
            double a = jdm.getCoeff();
            jdm.setCoeff(a * m);
        }
    }

    public void mult(Jdm jdm) {
        for (Jdm jdm1 : this.poly) {
            try {
                jdm1.mult(jdm);
            } catch (PolyException e) {
                e.printStackTrace();
            }
        }
    }

    public void mult(Poly poly) {
        Jdm[] arr1 = poly.getPoly();
        int len1 = arr1.length;
        Jdm[] arr2 = this.poly;
        int len2 = arr2.length;
        Jdm[] arr3 = new Jdm[len1 * len2];
        int k = 0;
        for (Jdm jdm1 : arr1) {
            for (Jdm jdm : arr2) {
                arr3[k] = PolyUtil.mult(jdm1, jdm);
                k++;
            }
        }
        this.poly = arr3;
        this.reduce(max);
    }

    public double[] getCoeffs() {
        int len = poly.length;
        double[] c = new double[len];
        for (int i = 0; i < len; i++) {
            c[i] = poly[i].getCoeff();
        }
        return c;
    }

    public double[] getCoeffsHorner(double value) {
        double[] coeffs = getCoeffs();
        int len = coeffs.length - 1;
        double sum = 0.0;
        for (int i = 0; i < len; i++) {
            sum = coeffs[i] * value + coeffs[i + 1];
            coeffs[i + 1] = sum;
            sum = 0;
        }
        return coeffs;
    }

    public Poly derivative() {
        Jdm[] arr = poly.clone();
        for (int i = 0; i < arr.length; i++) {
            Jdm jdm = arr[i].clone();
            Jdm jdm1 = jdm.derivative();
            arr[i] = jdm1;
        }
        return new Poly(arr);
    }
}
