import math
from ..nvector import NVector


def _sign(x):
    if x < 0:
        return -1
    return 1


class TransformMatrix:
    scalar = float

    def __init__(self):
        """ Creates an Identity matrix """
        self.to_identity()

    def clone(self):
        m = TransformMatrix()
        m._mat = list(self._mat)
        return m

        return self

    def __getitem__(self, key):
        row, col = key
        return self._mat[row*4+col]

    def __setitem__(self, key, value):
        row, col = key
        self._mat[row*4+col] = self.scalar(value)

    @property
    def a(self):
        return self[0, 0]

    @a.setter
    def a(self, v):
        self[0, 0] = self.scalar(v)

    @property
    def b(self):
        return self[0, 1]

    @b.setter
    def b(self, v):
        self[0, 1] = self.scalar(v)

    @property
    def c(self):
        return self[1, 0]

    @c.setter
    def c(self, v):
        self[1, 0] = self.scalar(v)

    @property
    def d(self):
        return self[1, 1]

    @d.setter
    def d(self, v):
        self[1, 1] = self.scalar(v)

    @property
    def tx(self):
        return self[3, 0]

    @tx.setter
    def tx(self, v):
        self[3, 0] = self.scalar(v)

    @property
    def ty(self):
        return self[3, 1]

    @ty.setter
    def ty(self, v):
        self[3, 1] = self.scalar(v)

    def __str__(self):
        return str(self._mat)

    def scale(self, x, y=None):
        if y is None:
            y = x

        m = TransformMatrix()
        m.a = x
        m.d = y
        self *= m
        return self

    def translate(self, x, y=None):
        if y is None:
            x, y = x
        m = TransformMatrix()
        m.tx = x
        m.ty = y
        self *= m
        return self

    def skew(self, x_rad, y_rad):
        m = TransformMatrix()
        m.c = math.tan(x_rad)
        m.b = math.tan(y_rad)
        self *= m
        return self

    def skew_from_axis(self, skew, axis):
        self.rotate(axis)
        m = TransformMatrix()
        m.c = math.tan(skew)
        self *= m
        self.rotate(-axis)
        return self

    def row(self, i):
        return NVector(self[i, 0], self[i, 1], self[i, 2], self[i, 3])

    def column(self, i):
        return NVector(self[0, i], self[1, i], self[2, i], self[3, i])

    def to_identity(self):
        self._mat = [
            1., 0., 0., 0.,
            0., 1., 0., 0.,
            0., 0., 1., 0.,
            0., 0., 0., 1.,
        ]

    def apply(self, vector):
        vector3 = NVector(vector.x, vector.y, 0, 1)
        return NVector(
            self.column(0).dot(vector3),
            self.column(1).dot(vector3),
        )

    @classmethod
    def rotation(cls, radians):
        m = cls()
        m.a = math.cos(radians)
        m.b = -math.sin(radians)
        m.c = math.sin(radians)
        m.d = math.cos(radians)

        return m

    def __mul__(self, other):
        m = TransformMatrix()
        for row in range(4):
            for col in range(4):
                m[row, col] = self.row(row).dot(other.column(col))
        return m

    def __imul__(self, other):
        m = self * other
        self._mat = m._mat
        return self

    def rotate(self, radians):
        self *= TransformMatrix.rotation(radians)
        return self

    def extract_transform(self):
        a = self.a
        b = self.b
        c = self.c
        d = self.d
        tx = self.tx
        ty = self.ty

        dest_trans = {
            "translation": NVector(tx, ty),
            "angle": 0,
            "scale": NVector(1, 1),
            "skew_axis": 0,
            "skew_angle": 0,
        }

        delta = a * d - b * c
        if a != 0 or b != 0:
            r = math.hypot(a, b)
            dest_trans["angle"] = - _sign(b) * math.acos(a/r)
            sx = r
            sy = delta / r
            dest_trans["skew_axis"] = 0
        else:
            r = math.hypot(c, d)
            dest_trans["angle"] = math.pi / 2 + _sign(d) * math.acos(c / r)
            sx = delta / r
            sy = r
            dest_trans["skew_axis"] = math.pi / 2

        dest_trans["scale"] = NVector(sx, sy)

        skew = math.atan2((a * c + b * d), r * r)
        dest_trans["skew_angle"] = skew

        return dest_trans

    def to_css_2d(self):
        return "matrix(%s, %s, %s, %s, %s, %s)" % (
            self.a, self.b, self.c, self.d, self.tx, self.ty
        )
