'''functions for 2D affine transformations'''
__all__ = (
    'nullTransform',
    'translate',
    'scale',
    'rotate',
    'skewX',
    'skewY',
    'mmult',
    'combineTransforms',
    'inverse',
    'zTransformPoint',
    'transformPoint',
    'transformPoints',
    'zTransformPoints',
    )
from math import cos, sin, tan, radians

# constructors for matrices:
def nullTransform():
    return (1, 0, 0, 1, 0, 0)

def translate(dx, dy=0):
    return (1, 0, 0, 1, dx, dy)

def scale(sx, sy=1):
    return (sx, 0, 0, sy, 0, 0)

def rotate(angle, cx=0, cy=0):
    a = radians(angle)
    sina = sin(a)
    cosa = cos(a)
    return (cosa, sina, -sina, cosa, cx, cy)

def skewX(angle):
    return (1, 0, tan(radians(angle)), 1, 0, 0)

def skewY(angle):
    return (1, tan(radians(angle)), 0, 1, 0, 0)

def skew(ax, ay=0):
    if ay:
        return mmult(skewX(ax),skewY(ay))
    else:
        return skewX(ax)

def mmult(A, B):
    "A postmultiplied by B"
    # I checked this RGB
    # [a0 a2 a4]    [b0 b2 b4]
    # [a1 a3 a5] *  [b1 b3 b5]
    # [      1 ]    [      1 ]
    #
    return (A[0]*B[0] + A[2]*B[1],
            A[1]*B[0] + A[3]*B[1],
            A[0]*B[2] + A[2]*B[3],
            A[1]*B[2] + A[3]*B[3],
            A[0]*B[4] + A[2]*B[5] + A[4],
            A[1]*B[4] + A[3]*B[5] + A[5])

def combineTransforms(*T):
    '''
    given transform matrices in the order they should be applied generate
    a combined transform.

    combineTransforms(T0,T1,T2) == mmult(T2,mmult(T1,T0))
                                == T2*T1*T0
    so that T0 is applied first, then T1 and finally T2.
    '''
    nT = len(T)
    return (
            mmult(T[1],T[0]) if nT==2 
            else mmult(combineTransforms(*T[2:]), mmult(T[1],T[0])) if nT>2
            else T[0] if nT==1
            else nullTransform()
            )

def inverse(A):
    "For A affine 2D represented as 6vec return 6vec version of A**(-1)"
    # I checked this RGB
    det = float(A[0]*A[3] - A[2]*A[1])
    R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det]
    return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]])

def zTransformPoint(A,v):
    "Apply the homogenous part of atransformation a to vector v --> A*v"
    return (A[0]*v[0]+A[2]*v[1],A[1]*v[0]+A[3]*v[1])

def transformPoint(A,v):
    "Apply transformation a to vector v --> A*v"
    return (A[0]*v[0]+A[2]*v[1]+A[4],A[1]*v[0]+A[3]*v[1]+A[5])

def transformPoints(matrix, V):
    r = [transformPoint(matrix,v) for v in V]
    if isinstance(V,tuple): r = tuple(r)
    return r

def zTransformPoints(matrix, V):
    return list(map(lambda x,matrix=matrix: zTransformPoint(matrix,x), V))
