from __future__ import division, print_function
import math
import unittest
from functools import reduce


def __mult(t0, t1):
    return t0.mult(t1)


class Transform(object):
    def __init__(self, m):
        self.m = tuple(m)

    def entry(self, x, y):
        return self.m[y * 3 + x]

    def x(self):
        return self.m[6]

    def y(self):
        return self.m[7]

    def mult(self, o):
        m = [0, 0, 0, 0, 0, 0, 0, 0, 0]
        for i in range(3):
            for j in range(3):
                m[j * 3 + i] = sum([self.entry(i, k) * o.entry(k, j) for k in range(3)])
        return Transform(m)

    def mult_point(self, p):
        t = self.mult(translate(p[0], p[1]))
        toreturn = [t.x(), t.y()]
        if len(p) == 3:
            toreturn.append(0)
        return tuple(toreturn)


class TransformTest(unittest.TestCase):
    def testEntry(self):
        t = Transform((1, 2, 3, 4, 5, 6, 7, 8, 9))
        self.assertEquals(1, t.entry(0, 0))
        self.assertEquals(4, t.entry(0, 1))
        self.assertEquals(7, t.entry(0, 2))
        self.assertEquals(2, t.entry(1, 0))
        self.assertEquals(5, t.entry(1, 1))
        self.assertEquals(8, t.entry(1, 2))
        self.assertEquals(3, t.entry(2, 0))
        self.assertEquals(6, t.entry(2, 1))
        self.assertEquals(9, t.entry(2, 2))

    def testMult(self):
        t0 = translate(1, 2)
        t1 = translate(10, 20)
        t2 = t0.mult(t1)
        self.assertEquals(translate(11, 22).m, t2.m)

    def testTranslatePoint(self):
        p0 = (11, 22)
        t = translate(10, 20)
        p1 = t.mult_point(p0)
        self.assertEquals((21, 42), p1)

    def testRotatePoint(self):
        p0 = (3, 4)
        t = rotate(90)
        p1 = t.mult_point(p0)
        self.assertAlmostEquals(-4, p1[0])
        self.assertAlmostEquals(3, p1[1])


IDENTITY = Transform([1, 0, 0, 0, 1, 0, 0, 0, 1])


def matrix(a, b, c, d, e, f):
    return Transform([a, b, 0, c, d, 0, e, f, 1])


def translate(x, y):
    return matrix(1, 0, 0, 1, x, y)


def scale(x, y=None):
    if y is None:
        y = x
    return matrix(x, 0, 0, y, 0, 0)


def skew_x(a):
    tana = math.tan(a)
    return matrix(1, 0, tana, 1, 0, 0)


def skew_y(a):
    tana = math.tan(a)
    return matrix(1, tana, 0, 1, 0, 0)


def rotate(a, p=None):
    r = math.radians(a)
    sina = math.sin(r)
    cosa = math.cos(r)
    transform = matrix(cosa, sina, -sina, cosa, 0, 0)

    if p is not None:
        x = p[0]
        y = p[1]
        inner = translate(-x, -y)
        outer = translate(x, y)
        transform = outer.mult(transform.mult(inner))

    return transform


def parse(transform_string):
    transform_strings = [s+")" for s in transform_string.split(")") if s.strip()]
    transforms = [eval(t) for t in transform_strings]
    return reduce(__mult, transforms)


class __TestParse(unittest.TestCase):
    def testOneTranslate(self):
        actual = parse("translate(10,20)")
        expected = translate(10, 20)
        self.assertEquals(expected.m, actual.m)

    def testTranslateTranslate(self):
        actual = parse("translate(10,0) translate(0,20)")
        expected = translate(10, 20)
        self.assertEquals(expected.m, actual.m)

if __name__ == "__main__":
    unittest.main()
