#
# a python vector class
#
# loosely based on code found at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52272 from 
#
# A. Pletzer 5 Jan 00/11 April 2002
#

from Krank import *

class vector(list):
    """
        A list based vector class
    """        
    #-------------------------------------------------------------------------------------------
    def __add__(self, other):
        return vector((self[0]+other[0], self[1]+other[1]))

    def __sub__(self, other):
        return vector((self[0]-other[0], self[1]-other[1]))
    
    def __mul__(self, other):
        if other.__class__ == vector:
            return vector((self[0]*other[0], self[1]*other[1]))
        else:
            return vector((self[0]*other, self[1]*other))

    def __truediv__(self, other):
        if other.__class__ == vector:
            return vector((self[0]/other[0], self[1]/other[1]))
        else:
            return vector((self[0]/other, self[1]/other))

    def __floordiv__(self, other):
        if other.__class__ == vector:
            return vector((self[0]//other[0], self[1]//other[1]))
        else:
            return vector((self[0]//other, self[1]//other))

    def __rmul__(self, other):
        return (self*other)
        
    def __neg__(self):
        return vector((-self[0], -self[1]))
    
    def __iadd__(self, other):
        self[0] += other[0]
        self[1] += other[1]
        return self

    def __isub__(self, other):
        self[0] -= other[0]
        self[1] -= other[1]
        return self
        
    def __imul__(self, other):
        self.__init__(self*other)
        return self

    def __idiv__(self, other):
        self.__init__(self/other)
        return self
    
    def __str__(self):
        return "<%0.2f %0.2f>" % (self[0], self[1])

#    def size(self): return len(self)

    #-------------------------------------------------------------------------------------------
    def __getattr__(self, name):
        if name == 'x':
            return self[0]
        elif name == 'y':
            return self[1]

    #-------------------------------------------------------------------------------------------
    def __setattr__(self, name, value):
        if name == 'x':
            self[0] = value
        elif name == 'y':
            self[1] = value
        else:
            list.__setattr__(self, name, value)
            
    #-------------------------------------------------------------------------------------------
    def norm(self):
        l = self.length()
        if l:
            return self/l
        return vector((0,0))

    #-------------------------------------------------------------------------------------------
    def dot(self, other):
        return self[0]*other[0] + self[1]*other[1]
    
    #-------------------------------------------------------------------------------------------
    def length(self):
        dot = abs(self.dot(self))
        if dot < sys.maxsize:
            return math.sqrt(dot)
        return 0

    #-------------------------------------------------------------------------------------------
    def square(self):
        return abs(self.dot(self))
        
    #-------------------------------------------------------------------------------------------
    def angle(self):
        x, y = self
        if x > 0:
            if y >= 0:
                return math.atan(y / x)
            else:
                return math.atan(y / x) + 2*math.pi
        elif x == 0:
            if y > 0:
                return math.pi/2
            elif y == 0:
                return 0
            else:
                return 1.5*math.pi
        else:
            return math.atan(y / x) + math.pi

    #-------------------------------------------------------------------------------------------            
    def fade(self, other, f=0.5):
        angle = fadeAngle(self.angle(), other.angle(), f)
        length = self.length() * (1-f) + other.length() * f
        return vector.withAngle(angle, length)

    #-------------------------------------------------------------------------------------------
    def to(self, other):
        return other-self

    #-------------------------------------------------------------------------------------------
    def withAngle(self, angle, length=1):
        return vector((math.cos(angle), math.sin(angle)))*length
    
    withAngle = classmethod(withAngle)

    #-------------------------------------------------------------------------------------------
    def interpolate(self, other, f=0.5):
        return self+self.to(other)*fade(0, 1, f)
        
    #-------------------------------------------------------------------------------------------
    def zero(self):
        self[0] = 0
        self[1] = 0
        
    #-------------------------------------------------------------------------------------------
    def clamp(self, minVec, maxVec):
        return vector((clamp(self[0], minVec.x, maxVec.x), clamp(self[1], minVec.y, maxVec.y)))
        
    #-------------------------------------------------------------------------------------------
    def inRect(self, rect):
        (x, y), (w, h) = rect
        if self[0] < x: return False
        if self[1] < y: return False
        if self[0] > x+w: return False
        if self[1] > y+h: return False
        return True
        
    #-------------------------------------------------------------------------------------------
    def out(self):
        print(self)
        
#-----------------------------------------------------------------------------------------------
def pos (x, y):
    return vector((x, y))

#-----------------------------------------------------------------------------------------------
def isVector(x):
    """
    Determines if the argument is a vector class object.
    """
    return hasattr(x,'__class__') and x.__class__ is vector

#-----------------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------------

class affine (list):

    #-------------------------------------------------------------------------------------------
    def __init__(self, init=None):
        list.__init__(self)
        if init:
            self.extend(init)
        else:
            self.extend([1.0, 0, 0, 1.0, 0.0, 0.0])

    #-------------------------------------------------------------------------------------------
    def apply (self):
        t = NSAffineTransform.transform()
        t.setTransformStruct_(self)
        t.set()

    #-------------------------------------------------------------------------------------------
    def scale (self, sx, sy):
        self.__init__([sx*self[0], sx*self[1], sy*self[2], sy * self[3], self[4], self[5] ])
        return self
        
    #-------------------------------------------------------------------------------------------
    def scaling (self, sx, sy):
        return affine([sx, 0, 0, sy, 0, 0])

    scaling = classmethod(scaling)

    #-------------------------------------------------------------------------------------------
    def rotate(self, theta):
        co = math.cos(theta)
        si = math.sin(theta)
        self.__init__([ self[0] * co + self[2] * si,
                        self[1] * co + self[3] * si,
                       -self[0] * si + self[2] * co,
                       -self[1] * si + self[3] * co,
                        self[4],
                        self[5]])
        return self
        
    #-------------------------------------------------------------------------------------------
    def rotation(self, theta):
        co = math.cos(theta)
        si = math.sin(theta)
        return affine([co, si, -si, co, 0, 0])
        
    rotation = classmethod(rotation)

    #-------------------------------------------------------------------------------------------
    def translate(self, tx, ty):
        self.__init__([ self[0], self[1], self[2], self[3],
                        self[0]*tx + self[2]*ty + self[4],
                        self[1]*tx + self[3]*ty + self[5]])
        return self
        
    #-------------------------------------------------------------------------------------------
    def translation(self, tx, ty):
        return affine([1, 0, 0, 1, tx, ty])
        
    translation = classmethod(translation)

    #-------------------------------------------------------------------------------------------
    def __mul__(self, other):
      return affine ([  self[0]*other[0]+self[2]*other[1],
                        self[1]*other[0]+self[3]*other[1],
                        self[0]*other[2]+self[2]*other[3],
                        self[1]*other[2]+self[3]*other[3],
                        self[0]*other[4]+self[2]*other[5]+self[4],
                        self[1]*other[4]+self[3]*other[5]+self[5]])
      
    #-------------------------------------------------------------------------------------------
    def __imul__(self, other):
        return self*other 

    #-------------------------------------------------------------------------------------------
    def __rmul__(self, other):
        return other.__mul__(self)

    #-------------------------------------------------------------------------------------------
    def transform (self, vec):
        x,y = vec
        a,b,c,d,e,f = self
        return vector([self[0]*x + self[2]*y+self[4], self[1]*x+self[3]*y+self[5]])

    #-------------------------------------------------------------------------------------------
    def scaleRotateVector(self, v):
        # scale a vector (translations are not done)
        x, y = v
        a,b,c,d,e,f = self
        return vector([a*x + c*y, b*x+d*y])

#-----------------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------------

def clamp (value, low, high):
    return max(low, min(high, value))

def fade (s, t, f=0.5):
    return s * (1-f) + t * f

#-----------------------------------------------------------------------------------------------

def fadeAngle(a, b, f=0.5):
    diff = abs(a-b)
    if diff > math.pi:
        if a < b: 
            a += math.pi*2
        else: 
            b += math.pi*2
        angle = a * (1-f) + b * f
        if angle < 0:
            angle += math.pi*2
        elif angle > math.pi*2:
            angle -= math.pi*2 
        return angle
    else:
        return a * (1-f) + b * f

    
