package org.python.modules;

import org.python.core.Py;
import org.python.core.PyComplex;
import org.python.core.PyFloat;
import org.python.core.PyInstance;
import org.python.core.PyObject;
import org.python.modules.math;

public class cmath {
    public static PyFloat pi = new PyFloat(Math.PI);
    public static PyFloat e = new PyFloat(Math.E);

    private static PyComplex one = new PyComplex(1.0, 0.0);
    private static PyComplex half = new PyComplex(0.5, 0.0);
    private static PyComplex i = new PyComplex(0.0, 1.0);
    private static PyComplex half_i = new PyComplex(0.0, 0.5);

    private static PyComplex c_prodi(PyComplex x) {
        return (new PyComplex(-x.imag, x.real));
    }

    private static double hypot(double x, double y) {
        return (Math.sqrt(x * x + y * y));
    }

    private static PyComplex complexFromPyObject(PyObject obj) {
        // If op is already of type PyComplex_Type, return its value
        if (obj instanceof PyComplex) {
            return (PyComplex)obj;
        }

        // If not, use op's __complex__ method, if it exists
        PyObject newObj = null;
        if (obj instanceof PyInstance) {
            // this can go away in python 3000
            if (obj.__findattr__("__complex__") != null) {
                newObj = obj.invoke("__complex__");
            }
            // else try __float__
        } else {
            PyObject complexFunc = obj.getType().lookup("__complex__");
            if (complexFunc != null) {
                newObj = complexFunc.__call__(obj);
            }
        }

        if (newObj != null) {
            if (!(newObj instanceof PyComplex)) {
                throw Py.TypeError("__complex__ should return a complex object");
            }
            return (PyComplex)newObj;
        }

        // If neither of the above works, interpret op as a float giving the real part of
        // the result, and fill in the imaginary part as 0
        return new PyComplex(obj.asDouble(), 0);
    }
    
    public static PyObject acos(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        return (c_prodi(log(x.__add__(i
                .__mul__(sqrt(one.__sub__(x.__mul__(x))))))).__neg__());
    }

    public static PyComplex acosh(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = null;

        PyComplex a = sqrt(x.__sub__(one));
        PyComplex b = sqrt(x.__add__(one));
        PyComplex c = sqrt(half);
        r = log(c.__mul__(b.__add__(a)));

        return ((PyComplex) r.__add__(r));
    }

    public static PyComplex asin(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = null;

        PyComplex squared = (PyComplex) x.__mul__(x);
        PyComplex sq1_minus_xsq = sqrt(one.__sub__(squared));

        r = (PyComplex) c_prodi(log(sq1_minus_xsq.__add__(c_prodi(x))))
                .__neg__();
        return (r);
    }

    public static PyComplex asinh(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = null;

        PyComplex a = sqrt(x.__add__(i));
        PyComplex b = sqrt(x.__sub__(i));
        PyComplex z = sqrt(half);
        r = log(z.__mul__(a.__add__(b)));

        return ((PyComplex) r.__add__(r));
    }

    public static PyComplex atan(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = (PyComplex) half_i.__mul__(log(i.__add__(x).__div__(
                i.__sub__(x))));

        return (r);
    }

    public static PyComplex atanh(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = (PyComplex) half.__mul__(log(one.__add__(x).__div__(
                one.__sub__(x))));
        return (r);
    }

    public static PyComplex cos(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(Math.cos(x.real) * math.cosh(x.imag), -Math
                .sin(x.real)
                * math.sinh(x.imag));
        return (r);
    }

    public static PyComplex cosh(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(Math.cos(x.imag) * math.cosh(x.real), Math
                .sin(x.imag)
                * math.sinh(x.real));
        return (r);
    }

    public static PyComplex exp(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(0.0, 0.0);
        double l = Math.exp(x.real);
        r.real = l * Math.cos(x.imag);
        r.imag = l * Math.sin(x.imag);
        return (r);
    }

    public static PyComplex log(PyObject in) {
        PyComplex r = new PyComplex(0.0, 0.0);
        PyComplex x = complexFromPyObject(in);
        r.imag = Math.atan2(x.imag, x.real);
        r.real = Math.log(hypot(x.real, x.imag));
        return (r);
    }

    public static PyComplex log10(PyObject in) {
        PyComplex r = new PyComplex(0.0, 0.0);
        PyComplex x = complexFromPyObject(in);
        double l = hypot(x.real, x.imag);
        r.imag = Math.atan2(x.imag, x.real) / Math.log(10.0);
        r.real = math.log10(new PyFloat(l));
        return (r);
    }
                    
    public static PyComplex log(PyObject in, PyObject base) {
        return log(complexFromPyObject(in), base.asDouble());
    }
    
    public static PyComplex log(PyComplex x, double base) {
        PyComplex r = new PyComplex(0.0, 0.0);
        double l = hypot(x.real, x.imag);
        double log_base = Math.log(base);
        r.imag = Math.atan2(x.imag, x.real) / log_base;
        r.real = math.log(new PyFloat(l)) / log_base;
        return (r);
    }

    public static PyComplex sin(PyObject in) {
        PyComplex r = new PyComplex(0.0, 0.0);
        PyComplex x = complexFromPyObject(in);
        r.real = Math.sin(x.real) * math.cosh(x.imag);
        r.imag = Math.cos(x.real) * math.sinh(x.imag);
        return (r);
    }

    public static PyComplex sinh(PyObject in) {
        PyComplex r = new PyComplex(0.0, 0.0);
        PyComplex x = complexFromPyObject(in);
        r.real = Math.cos(x.imag) * math.sinh(x.real);
        r.imag = Math.sin(x.imag) * math.cosh(x.real);
        return (r);
    }

    public static PyComplex sqrt(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(0.0, 0.0);

        if ((x.real != 0.0) || (x.imag != 0.0)) {
            double s = Math
                    .sqrt(0.5 * (Math.abs(x.real) + hypot(x.real, x.imag)));
            double d = 0.5 * x.imag / s;

            if (x.real > 0) {
                r.real = s;
                r.imag = d;
            } else if (x.imag >= 0) {
                r.real = d;
                r.imag = s;
            } else {
                r.real = -d;
                r.imag = -s;
            }
        }
        return (r);
    }

    public static PyComplex tan(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(0.0, 0.0);

        double sr = Math.sin(x.real);
        double cr = Math.cos(x.real);
        double shi = math.sinh(x.imag);
        double chi = math.cosh(x.imag);
        double rs = sr * chi;
        double is = cr * shi;
        double rc = cr * chi;
        double ic = -sr * shi;
        double d = rc * rc + ic * ic;
        r.real = ((rs * rc) + (is * ic)) / d;
        r.imag = ((is * rc) - (rs * ic)) / d;

        return (r);
    }

    public static PyComplex tanh(PyObject in) {
        PyComplex x = complexFromPyObject(in);
        PyComplex r = new PyComplex(0.0, 0.0);

        double si = Math.sin(x.imag);
        double ci = Math.cos(x.imag);
        double shr = math.sinh(x.real);
        double chr = math.cosh(x.real);
        double rs = ci * shr;
        double is = si * chr;
        double rc = ci * chr;
        double ic = si * shr;
        double d = rc * rc + ic * ic;
        r.real = ((rs * rc) + (is * ic)) / d;
        r.imag = ((is * rc) - (rs * ic)) / d;

        return (r);
    }
}
