import os, sys, math, re, logging

sys.path.append(os.path.abspath('../tool'))
import defensive
import obj

import behavior
import collisionDetection

test = defensive.Testing()
log = defensive.Logging('bubble')


@obj.immutable
class Bubble(object):
    def __init__(self,name,\
                 (x,y),r,\
                 (dirx,diry),
                 (vx,vy),\
                 child,\
                 link,\
                 collisionDetection,\
                 tag):
        self.name = name
        self.x,self.y= x,y
        self.r = r
        self.dx,self.dy = (dirx,diry)
        self._vx,self._vy = (vx,vy)
        self._child = tuple(child)
        self.link = tuple(link)
        self.collisionDetection = collisionDetection
        self.tag = tuple(tag)
	    
    def __repr__(self):
        return 'Bubble['+self.name+']'
    
    def __str__(self):
        return 'Bubble('+self.name+',at'+str((self.x,self.y))+\
                                   ',look'+str((self.dx,self.dy))+\
                                   ',go'+str((self._vx,self._vy))+')'

    @test.eq((1,1))
    @log.debug
    def getNextPos(self):
        """
        return predicted x,y position
        """
        vx,vy = self.getForceVector()
        return (self.x+vx,self.y+vy)

    @log.debug  
    def getChild(self):
        """
        return translated child
        """
        for child in self._child:
            yield child.translate(self.x, self.y)

    @log.debug
    def getRadian(self):
        """
        return direction angle in radian
        """
        dirx,diry = self.dx,self.dy
        return math.atan2(dirx,diry)

    @log.debug
    def getDelta(self,other):
        """
        return delta of respective (x,y) position
        """
        deltaX = self.x - other.x
        deltaY = self.y - other.y
        return deltaX,deltaY
    
    @log.debug
    def getDistSQ(self,other):
        """
        return square of distance between two bubble
        """
        deltaX,deltaY = self.getDelta(other)
        return deltaX**2 + deltaY**2
    
    @log.debug
    def intersect(self,other):
        """
        return true if two bubble intersect
        """
        minDist = self.r + other.r
        minDistSQ = minDist**2
        return minDistSQ >= self.getDistSQ(other)
    
    @log.debug
    def inside(self,other):
        """
        return true if self is completely inside the other
        """
        minDist = other.r - self.r
        minDistSQ = minDist**2
        return minDistSQ >= self.getDistSQ(other)

    @log.debug
    def getForceVector(self):
        """
        return forcevector and pseudo normalise it
        """
        vx,vy = self._vx,self._vy
        if vx > 1:
            vx = 1
        if vx < -1:
            vx = -1
        if vy > 1:
            vy = 1
        if vy < -1:
            vy = -1
        return vx,vy   

    @log.debug
    @obj.immutator
    def stop(copy):
        """
        return modified bubble for forceVector as (0,0)
        """
        copy._vx,copy._vy = (0,0)
        return copy
    
    @log.debug
    @obj.immutator
    def translate(copy,x,y):
        """
        return modified bubble with new position
        """
        copy.x,copy.y=(copy.x+x,copy.y+y)
        return copy
    
    @log.debug
    @obj.immutator
    def rotate(copy,rad):
        """
        return modified bubble with new direction
        """
        s = math.sin(rad)
        c = math.cos(rad)
        x,y = copy.dx,copy.dy
        dirx = x*c - y*s
        diry = x*s +y*c
        dist = math.hypot(dirx,diry)
        dirx /= dist
        diry /= dist
        copy.dx,copy.dy = (dirx,diry)
        return copy
       
    @log.debug   
    @obj.immutator            
    def setDirection(copy,dirx,diry):
        """
        return modified bubble with new direction
        """
        copy.dx,copy.dy = (dirx,diry)
        return copy
    
    @log.debug
    @obj.immutator
    def push(copy,x,y):
        """
        return modified bubble with updated forceVector
        """
        vx,vy = copy.getForceVector()
        vx = vx+x
        vy = vy+y
        copy._vx, copy._vy = (vx,vy)
        return copy

    @log.debug
    @obj.immutator
    def restrain(copy,scale):
        """
        return modified bubble with updated forceVector
        """
        vx,vy = copy.getForceVector()
        vx = vx*scale
        vy = vy*scale
        copy._vx,copy._vy = (vx,vy)
        return copy

    @log.debug
    @obj.immutator
    def giggle(copy):
        """
        return modified bubble with applied force vector on position
        """
        vx, vy = copy.getForceVector()
        copy.x += vx
        copy.y+=vy
        return copy

    @log.debug
    @obj.immutator
    def updateChild(copy,child):
        """
        return modified bubble with new child list
        """
        copy._child = tuple(child)
        return copy
    
    @log.debug
    @obj.immutator
    def updateLink(copy,newlink):
        """
        return modified bubble with new link list
        """
        copy.link = tuple(newlink)
        return copy

    @log.debug
    @obj.immutator
    def addTag(copy,newTag):
        """
        return modified bubble with one tag added
        """
        copy.tag = copy.tag + (newTag,)
        return copy
      
    @log.debug  
    @obj.immutator
    def rmTag(copy,rmTag):
        """
        return modified bubble with one tag removed
        """
        copy.tag = tuple(list(copy.tag).remove(rmTag))
        return copy                   
      
    @log.debug        
    def recursiveCollisionDetection(self,bubble,collisionDetectionSuite):
        """
        return the child the bubble is in
        """
        collisionDetection = collisionDetectionSuite[self.collisionDetection]
        if collisionDetection(bubble,self):
            for child in self.getChild():
                childCollisionDetection = collisionDetectionSuite[child.collisionDetection]
                if childCollisionDetection(bubble,child):
                    return child.recursiveCollisionDetection(bubble,collisionDetectionSuite)
        return self

    @log.debug
    @obj.immutator
    def against(copy,context,behaviorSuite,collisionDetectionSuite):
        """
        return modified bubble after all behaviors have been executed
        """
        inbubble = context.recursiveCollisionDetection(copy,collisionDetectionSuite)
        newSelf = copy
        for behaviorName in newSelf.tag:
            if behaviorName in behaviorSuite:
                for behavior in behaviorSuite[behaviorName]:
                    newSelf = behavior(newSelf,inbubble)
        newLink=[]
        for link in newSelf.link:
            newLink.append(link.against(context,behaviorSuite,collisionDetectionSuite))
        return newSelf.updateLink(newLink)

    @log.debug
    @obj.immutator
    def transform(copy,name,data):
        """
        transformation rule:
        for property in [position,direction,forcevector,radius]:
            ++n add , --n sub, n +n -n set, _ keep
        for property in [child, link, behavior, tag]:
            &symbol add, !symbol remove, ! clear
        name and collisionDetection are simple set
        not declaring any is keeping
        """
        copy.name = name if name else copy.name
        copy.collisionDetection = data['collisionDetection'] \
                                  if 'collisionDetection' in data.keys() \
                                  else copy.collisionDetection
        copy.x = data['x'](copy.x)       if 'x'    in data else copy.x
        copy.y = data['y'](copy.y)       if 'y'    in data else copy.y
        copy._vx = data['vx'](copy._vx)     if 'vx'   in data else copy._vx
        copy._vy = data['vy'](copy._vy)     if 'vy'   in data else copy._vy
        copy.dx = data['dirx'](copy.dx) if 'dirx' in data else copy.dx
        copy.dy = data['diry'](copy.dy) if 'diry' in data else copy.dy
        copy.r = data['r'](copy.r) if 'r' in data else copy.r
        copy.tag = tuple(data['tag'](copy.tag)) if 'tag' in data else copy.tag
        copy._child = tuple(data['child'](copy._child)) if 'child' in data else copy._child
        copy.link = tuple(data['link'](copy.link)) if 'link' in data else copy.link
        print copy.name, [i for i in copy.tag]
        return copy

def new():
    """
    static builder for a brand new bubble object
    """
    return Bubble('new',\
                  (0,0),\
                  1,\
                  (0,1),\
                  (0,0),\
                  [],\
                  [],\
                  lambda a,b:True,\
                  [])

def buildNumericTranformation(value):
    parse = re.match("((-{0,2}|\+{0,2}|)([0-9]+(\.[0-9]+)?))|_",value)
    if parse:
        op = parse.group(2)
        val = float(parse.group(3))
        funk = (lambda a : a+val) if op=='++' else\
               (lambda a : a-val) if op=='--' else\
               (lambda a : val) if op=='+' or op=='' else\
               (lambda a : -val) if op=='-' else\
               (lambda a : a)
        return funk
    return lambda a:a

def buildTagTransformation(values):
    toAdd = []
    toRemove = []
    clear = False
    for t in values:
        parse = re.match("((-{0,2}|\+{0,2}|)([0-9]+(\.[0-9]+)?))|_",t)
        print t, parse
        if parse:
            val = parse.group(3)
            if parse.group(0)=='!':
                clear = True
            if parse.group(2)!='!':
                toAdd.append(val)
            else:
                toRemove.append(val)
        #little hack
        toAdd.append(t)
    print 'values', values
    print 'to remove', toRemove, 'to add', toAdd
    if clear:
        return lambda a: toAdd
    return lambda a: set(toAdd + [d for d in a if d not in toRemove])

def buildSubTransformation(tmp):
    def trans(originalLst):
        if len(tmp.keys()):
            originalDict = dict(zip((a.name for a in originalLst),originalLst))
            ret = []
            for nameb,transformationb in tmp.items():
                sub = originalDict[nameb] if nameb in originalDict.keys() else new()
                ret.append( sub.transform(nameb,transformationb) )
            return ret
        else:
            return originalLst
    return trans

def buildTransformation(db):
    cluster = {}
    for name,data in db.items():
        transformator = {}
        if 'collisionDetection' in data:
            transformator['collisionDetection'] = data['collisionDetection']
        for k in ('x','y','dirx','diry','vx','vy','r'):
            if k in data:
                transformator[k] = buildNumericTranformation(data[k])
        if 'tag' in data:
            transformator['tag'] = buildTagTransformation(data['tag'])
        for n in ('child','link'):
            if n in data:
                transformator[n] = buildSubTransformation(buildTransformation(data[n]))
        cluster[name] = transformator
    return cluster

#some function
@log.debug
def bubbleFromStruct(db):
    cluster = []
    """
    return one bubble list from given json data
    """
    for name,transformator in buildTransformation(db).items():
        cluster.append(new().transform(name,transformator))     
    return cluster

if __name__ == '__main__':
    import logging
    import logging.handlers
    ch = logging.handlers.SocketHandler('localhost',
                                        logging.handlers.DEFAULT_TCP_LOGGING_PORT)

    #ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    log.logger.addHandler(ch)
    
    bubble = Bubble('test',\
                 (0,0),1,\
                 (0,1),
                 (1,1),\
                 (),\
                 (),\
                 lambda a,b:True,\
                 (),\
                 ())
    test.runTest(bubble)
