package math.complex;

import math.matrix.Matrix;
import math.utils.IType;
import math.utils.MathUtil;

public class Complex implements IType<Complex> {
    private double re;
    private double im;
    private double r;
    private double fi;

    public Complex(Complex complex) {
        this.re = complex.re;
        this.im = complex.im;
        this.r = complex.r;
        this.fi = complex.fi;
    }

    public Complex(double re_r, double im_fi, CType typ) {
        switch (typ) {
            case Normal:
                this.re = re_r;
                this.im = im_fi;
                this.r = mod();
                this.fi = arg();
                break;
            case Trigo:
                this.r = re_r;
                this.fi = im_fi;
                this.re = r * MathUtil.cosDeg(fi);
                this.im = r * MathUtil.sinDeg(fi);
                break;
            default:
                break;
        }
    }

    //Sprzeżenie liczby zespolonej
    public Complex conj() {
        return new Complex(re, -im, CType.Normal);
    }

    //Modul liczby zespolonej
    public double mod() {
        return Math.sqrt(re * re + im * im);
    }

    public double arg() {
        return Math.toDegrees(Math.atan2(im, re));
    }

    @Override
    public Complex zero() {
        return new Complex(0, 0, CType.Normal);
    }

    @Override
    public Complex one() {
        return new Complex(1, 0, CType.Normal);
    }

    @Override
    public Complex add(Complex x, Complex y) {
        double a = x.re + y.re;
        double b = x.im + y.im;
        return new Complex(a, b, CType.Normal);
    }

    public Complex add(Complex other) {
        double a = this.re + other.re;
        double b = this.im + other.im;
        return new Complex(a, b, CType.Normal);
    }

    @Override
    public Complex sub(Complex x, Complex y) {
        double a = x.re - y.re;
        double b = x.im - y.im;
        return new Complex(a, b, CType.Normal);
    }

    public Complex sub(Complex other) {
        double a = this.re - other.re;
        double b = this.im - other.im;
        return new Complex(a, b, CType.Normal);
    }

    @Override
    public Complex mult(Complex x, Complex y) {
        double a = x.r * y.r;
        double b = x.fi + y.fi;
        return new Complex(a, b, CType.Trigo);
    }

    public Complex mult(Complex other) {
        double a = this.r * other.r;
        double b = this.fi + other.fi;
        return new Complex(a, b, CType.Trigo);
    }

    @Override
    public Complex div(Complex x, Complex y) {
        return new Complex(x.r / y.r, x.fi - y.fi, CType.Trigo);
    }

    public Complex div(Complex other) {
        return new Complex(this.r / other.r, this.fi - other.fi, CType.Trigo);
    }

    @Override
    public Complex neg(Complex x) {
        return new Complex(-1 * x.re, -1 * im, CType.Normal);
    }

    @Override
    public Complex recip(Complex x) {
        return new Complex(1.0 / x.r, -1.0 * x.fi, CType.Trigo);
    }

    public Complex recip() {
        return new Complex(1.0 / this.r, -1.0 * this.fi, CType.Trigo);
    }

    @Override
    public Complex pow(Complex x, int exp) {
        return new Complex(Math.pow(x.r, exp), x.fi * exp, CType.Trigo);
    }

    public Complex pow(int exp) {
        return new Complex(Math.pow(this.r, exp), this.fi * exp, CType.Trigo);
    }

    public Complex[] roots(Complex x, int n) {
        Complex[] tabl = new Complex[n];
        for (int i = 0; i < n; i++) {
            tabl[i] = new Complex(Math.pow(x.r, 1.0 / n),
                    (x.fi + 2 * i * 180.0) / n, CType.Trigo);
        }
        return tabl;
    }

    public Complex[] roots(int n) {
        Complex[] tabl = new Complex[n];
        for (int i = 0; i < n; i++) {
            tabl[i] = new Complex(Math.pow(this.r, 1.0 / n),
                    (this.fi + 2 * i * 180.0) / n, CType.Trigo);
        }
        return tabl;
    }

    @Override
    public boolean equals(Complex x, Complex y) {
        return (x.re == y.re) && (x.im == y.im);
    }

    @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 Complex)) {
            return false;
        }
        Complex a = (Complex) other;
        return (this.re == a.re) && (this.im == a.im);
    }

    @Override
    public int hashCode() {
        return 17 * Double.valueOf(this.re).hashCode() + 19
                * Double.valueOf(this.im).hashCode();
    }

    public String toString(CType ctype) {
        String format = null;
        switch (ctype) {
            case Normal:
                if (im >= 0) {
                    format = "[" + re + "+" + im + "i" + "]";
                } else {
                    format = "[" + re + "" + im + "i" + "]";
                }
                break;
            case Trigo:
                StringBuilder sb = new StringBuilder();
                sb.append("[");
                sb.append(MathUtil.roundToDecimal(r, 3));
                sb.append("(");
                sb.append("cos");
                double t = MathUtil.roundToDecimal(fi, 3);
                boolean znak = false;
                if (t < 0.0) {
                    znak = true;
                }
                double t1 = Math.abs(t);
                //System.out.println(t);
                while (t1 >= 360.0) {
                    t1 -= 360.0;
                }
                if (znak) {
                    t1 *= -1.0;
                }
                sb.append(MathUtil.getDecim(t1));
                sb.append("\u00B0");
                sb.append(MathUtil.getFract(t1));
                sb.append(" ");
                sb.append("+");
                sb.append(" ");
                sb.append("i");
                sb.append("\u00B7");
                sb.append("sin");
                sb.append(MathUtil.getDecim(t1));
                sb.append("\u00B0");
                sb.append(MathUtil.getFract(t1));
                sb.append(")");
                sb.append("]");
                format = sb.toString();
                break;
            case Exp:
                format = MathUtil.roundToDecimal(r, 3) +
                        "\u00B7" +
                        "e^(i" +
                        "\u00B7" +
                        MathUtil.roundToDecimal(fi, 3) +
                        ")";
                break;
        }
        return format;
    }

    public Matrix asMatrix(Complex complex) {
        double[] tabl = new double[4];
        tabl[0] = complex.re;
        tabl[1] = -1 * complex.im;
        tabl[2] = complex.im;
        tabl[3] = complex.re;
        return new Matrix(tabl, 2);
    }

    public double getRe() {
        return re;
    }

    public double getIm() {
        return im;
    }

    public double getR() {
        return r;
    }

    public double getFi() {
        return fi;
    }
}
