fus 2.4.1
import "./main" all

# `Point`'s non-static methods are not allowed to be written here. This is to
# avoid circular calling.
# Also, 2d vector related things are not written here. They may be in `Point`.

equals'export: (a, b) ->
    a: Point.from(a)
    b: Point.from(b)
    a.x = b.x and a.y = b.y

approxEquals'export: (a, b) ->
    a: Point.from(a)
    b: Point.from(b)
    a.x..approxEquals(b.x) and a.y..approxEquals(b.y)

opposite'export: p ->
    p: Point.from(p)
    Point(-p.x, -p.y)

reciprocal'export: p ->
    p: Point.from(p)
    n: p.x * p.x + p.y * p.y
    Point(p.x / n, -p.y / n)

conjugate'export: p ->
    p: Point.from(p)
    Point(p.x, -p.y)

abs'export: p ->
    p: Point.from(p)

    # If on x-axis or y-axis, then it doesn't calculate square root.
    # This is not for performance, but for preventing approximation.
    # Though all modern browsers already don't generate approximations,
    # we make it even much safer.
    if p.x = 0
        Math.abs(p.y)
    else if p.y = 0
        Math.abs(p.x)
    else
        Math.sqrt(p.x * p.x + p.y * p.y)

phase'export: p -> # We use `phase` instead of `argument` to avoid ambiguity.
    p: Point.from(p)
    Math.atan2(p.y, p.x)

phaseInDegrees'export: p ->
    p: Point.from(p)

    # avoid approximation
    if p.x = 0 and p.y = 0
        0
    else if p.x = 0 and p.y > 0
        90
    else if p.x = 0 and p.y < 0
        -90
    else if p.x > 0 and p.y = 0
        0
    else if p.x < 0 and p.y = 0
        180

    else
        d: Math..radiansToDegrees(cmath.phase(p))
        if d ≤ -180
            180
        else
            d

add'export: (a, b) ->
    a: Point.from(a)
    b: Point.from(b)
    Point(a.x + b.x, a.y + b.y)

subtract'export: (a, b) -> cmath.add(a, cmath.opposite(b))

multiply'export: (a, b) ->
    a: Point.from(a)
    b: Point.from(b)
    Point(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x)

divide'export: (a, b) ->
    a: Point.from(a)
    b: Point.from(b)
    z: b.x * b.x + b.y * b.y
    Point((a.x * b.x + a.y * b.y) / z, (a.y * b.x - a.x * b.y) / z)

distance'export: (a, b) -> cmath.abs(cmath.subtract(a, b))

exp'export: p ->
    p: Point.from(p)
    Point.fromPolar(Math.exp(p.x), p.y)

log'export: p -> Point(Math.log(cmath.abs(p)), cmath.phase(p))

pow'export: (a, b) -> cmath.exp(cmath.multiply(cmath.log(a), b))

sqrt'export: p ->
    p: Point.from(p)
    r: cmath.abs(p)
    Point(Math.sqrt((r + p.x) / 2), Math.sign(p.y) * Math.sqrt((r - p.x) / 2))

cos'export: p ->
    cmath.divide(
        cmath.add(
            cmath.exp(
                cmath.multiply(p, Point(0, 1))
            )
            cmath.exp(
                cmath.multiply(
                    cmath.opposite(p), Point(0, 1)
                )
            )
        )
        2
    )

sin'export: p ->
    cmath.divide(
        cmath.subtract(
            cmath.exp(
                cmath.multiply(p, Point(0, 1))
            )
            cmath.exp(
                cmath.multiply(
                    cmath.opposite(p), Point(0, 1)
                )
            )
        )
        Point(0, 2)
    )

tan'export: p -> cmath.divide(cmath.sin(p), cmath.cos(p))

acos'export: p ->
    cmath.opposite(
        cmath.multiply(
            cmath.log(
                cmath.add(
                    p
                    cmath.multiply(
                        cmath.sqrt(
                            cmath.add(
                                cmath.opposite(
                                    cmath.multiply(p, p)
                                )
                                1
                            )
                        )
                        Point(0, 1)
                    )
                )
            )
            Point(0, 1)
        )
    )

asin'export: p ->
    cmath.opposite(
        cmath.multiply(
            cmath.log(
                cmath.add(
                    cmath.multiply(p, Point(0, 1))
                    cmath.sqrt(
                        cmath.add(
                            cmath.opposite(
                                cmath.multiply(p, p)
                            )
                            1
                        )
                    )
                )
            )
            Point(0, 1)
        )
    )

atan'export: p ->
    cmath.multiply(
        cmath.subtract(
            cmath.log(
                cmath.subtract(
                    1
                    cmath.multiply(p, Point(0, 1))
                )
            )
            cmath.log(
                cmath.add(
                    1
                    cmath.multiply(p, Point(0, 1))
                )
            )
        )
        Point(0, 0.5)
    )
