from panda3d.core import *
from direct.task.TaskManagerGlobal import *
from direct.distributed.ClockDelta import *
from direct.interval.IntervalGlobal import *
from toontown.suit import GoonGlobals
from direct.task.Task import Task
from toontown.toonbase import ToontownGlobals
from otp.otpbase import OTPGlobals
from toontown.coghq import DistributedCashbotBossObjectAI
from direct.showbase import PythonUtil
from toontown.suit import DistributedGoonAI
import math, random

class DistributedCashbotBossGoonAI(DistributedGoonAI.DistributedGoonAI, DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI):
    legLength = 10
    directionTable = [
     (0, 15), (10, 10), (-10, 10), (20, 8), (-20, 8), (40, 5), (-40, 5), (60, 4), (-60, 4), (80, 3), (-80, 3), (120, 2), (-120, 2), (180, 1)]
    offMask = BitMask32(0)
    onMask = CollisionNode.getDefaultCollideMask()

    def __init__(self, air, boss):
        DistributedGoonAI.DistributedGoonAI.__init__(self, air, 0)
        DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.__init__(self, air, boss)
        cn = CollisionNode('tubeNode')
        self.tube = CollisionTube(0, 0, 0, 0, 0, 0, 2)
        cn.addSolid(self.tube)
        self.tubeNode = cn
        self.tubeNodePath = self.attachNewNode(self.tubeNode)
        self.feelers = []
        cn = CollisionNode('feelerNode')
        self.feelerLength = self.legLength * 1.5
        feelerStart = 1
        for heading, weight in self.directionTable:
            rad = deg2Rad(heading)
            x = -math.sin(rad)
            y = math.cos(rad)
            seg = CollisionSegment(x * feelerStart, y * feelerStart, 0, x * self.feelerLength, y * self.feelerLength, 0)
            cn.addSolid(seg)
            self.feelers.append(seg)

        cn.setIntoCollideMask(self.offMask)
        self.feelerNodePath = self.attachNewNode(cn)
        self.isWalking = 0
        self.cTrav = CollisionTraverser('goon')
        self.cQueue = CollisionHandlerQueue()
        self.cTrav.addCollider(self.feelerNodePath, self.cQueue)

    def requestBattle(self, pauseTime):
        avId = self.air.getAvatarIdFromSender()
        avatar = self.air.doId2do.get(avId)
        if avatar:
            self.boss.damageToon(avatar, self.strength)
        DistributedGoonAI.DistributedGoonAI.requestBattle(self, pauseTime)

    def sendMovie(self, type, avId=0, pauseTime=0):
        if type == GoonGlobals.GOON_MOVIE_WALK:
            self.demand('Walk')
        else:
            if type == GoonGlobals.GOON_MOVIE_BATTLE:
                self.demand('Battle')
            else:
                if type == GoonGlobals.GOON_MOVIE_STUNNED:
                    self.demand('Stunned')
                else:
                    if type == GoonGlobals.GOON_MOVIE_RECOVERY:
                        self.demand('Recovery')
                    else:
                        self.notify.warning('Ignoring movie type %s' % type)

    def __chooseTarget(self, extraDelay=0):
        direction = self.__chooseDirection()
        if direction == None:
            self.target = None
            self.arrivalTime = None
            self.b_destroyGoon()
            return
        heading, dist = direction
        dist = min(dist, self.legLength)
        targetH = PythonUtil.reduceAngle(self.getH() + heading)
        origH = self.getH()
        h = PythonUtil.fitDestAngle2Src(origH, targetH)
        delta = abs(h - origH)
        turnTime = delta / (self.velocity * 5)
        walkTime = dist / self.velocity
        self.setH(targetH)
        self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0))
        self.departureTime = globalClock.getFrameTime()
        self.arrivalTime = self.departureTime + turnTime + walkTime + extraDelay
        self.d_setTarget(self.target[0], self.target[1], h, globalClockDelta.localToNetworkTime(self.arrivalTime))
        return

    def __chooseDirection(self):
        self.tubeNode.setIntoCollideMask(self.offMask)
        self.cTrav.traverse(self.boss.scene)
        self.tubeNode.setIntoCollideMask(self.onMask)
        entries = {}
        self.cQueue.sortEntries()
        for i in range(self.cQueue.getNumEntries() - 1, -1, -1):
            entry = self.cQueue.getEntry(i)
            dist = Vec3(entry.getSurfacePoint(self)).length()
            if dist < 1.2:
                dist = 0
            entries[entry.getFrom()] = dist

        netScore = 0
        scoreTable = []
        for i in range(len(self.directionTable)):
            heading, weight = self.directionTable[i]
            seg = self.feelers[i]
            dist = entries.get(seg, self.feelerLength)
            score = dist * weight
            netScore += score
            scoreTable.append(score)

        if netScore == 0:
            self.notify.info('Could not find a path for %s' % self.doId)
            return None
        s = random.uniform(0, netScore)
        for i in range(len(self.directionTable)):
            s -= scoreTable[i]
            if s <= 0:
                heading, weight = self.directionTable[i]
                seg = self.feelers[i]
                dist = entries.get(seg, self.feelerLength)
                return (
                 heading, dist)

        self.notify.warning('Fell off end of weighted table.')
        return (
         0, self.legLength)

    def __startWalk(self):
        if self.arrivalTime == None:
            return
        now = globalClock.getFrameTime()
        availableTime = self.arrivalTime - now
        if availableTime > 0:
            point = self.getRelativePoint(self.boss.scene, self.target)
            self.tube.setPointB(point)
            self.node().resetPrevTransform()
            taskMgr.doMethodLater(availableTime, self.__reachedTarget, self.uniqueName('reachedTarget'))
            self.isWalking = 1
        else:
            self.__reachedTarget(None)
        return

    def __stopWalk(self, pauseTime=None):
        if self.isWalking:
            taskMgr.remove(self.uniqueName('reachedTarget'))
            if pauseTime == None:
                now = globalClock.getFrameTime()
                t = (now - self.departureTime) / (self.arrivalTime - self.departureTime)
            else:
                t = pauseTime / (self.arrivalTime - self.departureTime)
            t = min(t, 1.0)
            pos = self.getPos()
            self.setPos(pos + (self.target - pos) * t)
            self.tube.setPointB(0, 0, 0)
            self.isWalking = 0
        return

    def __reachedTarget(self, task):
        self.__stopWalk()
        self.__chooseTarget()
        self.__startWalk()

    def __recoverWalk(self, task):
        self.demand('Walk')
        return Task.done

    def doFree(self, task):
        DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.doFree(self, task)
        self.demand('Walk')
        return Task.done

    def requestStunned(self, pauseTime):
        avId = self.air.getAvatarIdFromSender()
        if avId not in self.boss.involvedToons:
            return
        if self.state == 'Stunned' or self.state == 'Grabbed':
            return
        toon = self.air.doId2do.get(avId)
        if toon:
            toonDistance = self.getPos(toon).length()
            if toonDistance > self.attackRadius * 2:
                self.air.writeServerEvent('suspicious', avId, 'Stunned a goon, but outside of attack radius. Possible multihack.')
                taskMgr.doMethodLater(0, self.__recoverWalk, self.uniqueName('recoverWalk'))
                return
        self.__stopWalk(pauseTime)
        self.boss.makeTreasure(self)
        DistributedGoonAI.DistributedGoonAI.requestStunned(self, pauseTime)

    def hitBoss(self, impact):
        avId = self.air.getAvatarIdFromSender()
        self.validate(avId, impact <= 1.0, 'invalid hitBoss impact %s' % impact)
        if avId not in self.boss.involvedToons:
            return
        if self.state == 'Dropped' or self.state == 'Grabbed':
            if not self.boss.heldObject:
                damage = int(impact * 25 * self.scale)
                self.boss.recordHit(max(damage, 2))
        self.b_destroyGoon()

    def d_setTarget(self, x, y, h, arrivalTime):
        self.sendUpdate('setTarget', [x, y, h, arrivalTime])

    def d_destroyGoon(self):
        self.sendUpdate('destroyGoon')

    def b_destroyGoon(self):
        self.d_destroyGoon()
        self.destroyGoon()

    def destroyGoon(self):
        self.demand('Off')

    def enterOff(self):
        self.tubeNodePath.stash()
        self.feelerNodePath.stash()

    def exitOff(self):
        self.tubeNodePath.unstash()
        self.feelerNodePath.unstash()

    def enterGrabbed(self, avId, craneId):
        DistributedCashbotBossObjectAI.DistributedCashbotBossObjectAI.enterGrabbed(self, avId, craneId)
        taskMgr.remove(self.taskName('recovery'))
        taskMgr.remove(self.taskName('resumeWalk'))

    def enterWalk(self):
        self.avId = 0
        self.craneId = 0
        self.__chooseTarget()
        self.__startWalk()
        self.d_setObjectState('W', 0, 0)

    def exitWalk(self):
        self.__stopWalk()

    def enterEmergeA(self):
        self.avId = 0
        self.craneId = 0
        h = 0
        dist = 15
        pos = self.boss.getPos()
        walkTime = dist / self.velocity
        self.setPosHpr(pos[0], pos[1], pos[2], h, 0, 0)
        self.d_setPosHpr(pos[0], pos[1], pos[2], h, 0, 0)
        self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0))
        self.departureTime = globalClock.getFrameTime()
        self.arrivalTime = self.departureTime + walkTime
        self.d_setTarget(self.target[0], self.target[1], h, globalClockDelta.localToNetworkTime(self.arrivalTime))
        self.__startWalk()
        self.d_setObjectState('a', 0, 0)
        taskMgr.doMethodLater(walkTime, self.__recoverWalk, self.uniqueName('recoverWalk'))

    def exitEmergeA(self):
        self.__stopWalk()
        taskMgr.remove(self.uniqueName('recoverWalk'))

    def enterEmergeB(self):
        self.avId = 0
        self.craneId = 0
        h = 180
        dist = 15
        pos = self.boss.getPos()
        walkTime = dist / self.velocity
        self.setPosHpr(pos[0], pos[1], pos[2], h, 0, 0)
        self.d_setPosHpr(pos[0], pos[1], pos[2], h, 0, 0)
        self.target = self.boss.scene.getRelativePoint(self, Point3(0, dist, 0))
        self.departureTime = globalClock.getFrameTime()
        self.arrivalTime = self.departureTime + walkTime
        self.d_setTarget(self.target[0], self.target[1], h, globalClockDelta.localToNetworkTime(self.arrivalTime))
        self.__startWalk()
        self.d_setObjectState('b', 0, 0)
        taskMgr.doMethodLater(walkTime, self.__recoverWalk, self.uniqueName('recoverWalk'))

    def exitEmergeB(self):
        self.__stopWalk()
        taskMgr.remove(self.uniqueName('recoverWalk'))

    def enterBattle(self):
        self.d_setObjectState('B', 0, 0)

    def exitBattle(self):
        taskMgr.remove(self.taskName('resumeWalk'))

    def enterStunned(self):
        self.d_setObjectState('S', 0, 0)

    def exitStunned(self):
        taskMgr.remove(self.taskName('recovery'))

    def enterRecovery(self):
        self.d_setObjectState('R', 0, 0)
        taskMgr.doMethodLater(2.0, self.__recoverWalk, self.uniqueName('recoverWalk'))

    def exitRecovery(self):
        self.__stopWalk()
        taskMgr.remove(self.uniqueName('recoverWalk'))