from otp.ai.AIBaseGlobal import *
from panda3d.core import *
from direct.distributed.ClockDelta import *
from otp.avatar import DistributedAvatarAI
import SuitTimings
from direct.task import Task
import SuitPlannerBase
import SuitBase
import SuitDialog
import SuitDNA
from libpandadna import *
from direct.directnotify import DirectNotifyGlobal
from toontown.battle import SuitBattleGlobals
from toontown.building import FADoorCodes
import DistributedSuitBaseAI
from toontown.hood import ZoneUtil
from toontown.toon import NPCToons
import random

class DistributedSuitAI(DistributedSuitBaseAI.DistributedSuitBaseAI):
    SUIT_BUILDINGS = simbase.config.GetBool('want-suit-buildings', 1)
    DEBUG_SUIT_POSITIONS = simbase.config.GetBool('debug-suit-positions', 0)
    UPDATE_TIMESTAMP_INTERVAL = 180.0
    myId = 0
    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedSuitAI')

    def __init__(self, air, suitPlanner):
        DistributedSuitBaseAI.DistributedSuitBaseAI.__init__(self, air, suitPlanner)
        self.bldgTrack = None
        self.branchId = None
        if suitPlanner:
            self.branchId = suitPlanner.zoneId
        self.pathEndpointStart = 0
        self.pathEndpointEnd = 0
        self.minPathLen = 0
        self.maxPathLen = 0
        self.pathPositionIndex = 0
        self.pathPositionTimestamp = 0.0
        self.pathState = 0
        self.currentLeg = 0
        self.legType = SuitLeg.TOff
        self.flyInSuit = 0
        self.buildingSuit = 0
        self.attemptingTakeover = 0
        self.buildingDestination = None
        self.buildingDestinationIsCogdo = False

    def delete(self):
        del self.bldgTrack
        del self.branchId
        del self.buildingDestination
        del self.buildingDestinationIsCogdo

        DistributedSuitBaseAI.DistributedSuitBaseAI.delete(self)

    def stopTasks(self):
        taskMgr.remove(self.taskName('flyAwayNow'))
        taskMgr.remove(self.taskName('danceNowFlyAwayLater'))
        taskMgr.remove(self.taskName('move'))

    def pointInMyPath(self, point, elapsedTime):
        if self.pathState != 1:
            return 0
        then = globalClock.getFrameTime() + elapsedTime
        elapsed = then - self.pathStartTime
        if not self.sp:
            pass
        return self.legList.isPointInRange(point, elapsed - self.sp.PATH_COLLISION_BUFFER, elapsed + self.sp.PATH_COLLISION_BUFFER)

    def requestBattle(self, x, y, z, h, p, r):
        toonId = self.air.getAvatarIdFromSender()
        if self.air.doId2do.get(toonId) == None:
            return
        if self.pathState == 3:
            pass
        elif self.pathState != 1:
            if self.notify.getDebug():
                self.notify.debug('requestBattle() - suit %s not on path' % self.getDoId())
            if self.pathState == 2 or self.pathState == 4:
                self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName()))
            self.d_denyBattle(toonId)
            return
        elif self.legType != SuitLeg.TWalk:
            if self.notify.getDebug():
                self.notify.debug('requestBattle() - suit %s not in Bellicose' % self.getDoId())
            self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName()))
            self.d_denyBattle(toonId)
            return
        self.confrontPos = Point3(x, y, z)
        self.confrontHpr = Vec3(h, p, r)
        if self.sp.requestBattle(self.zoneId, self, toonId):
            if self.notify.getDebug():
                self.notify.debug('Suit %s requesting battle in zone %s' % (self.getDoId(), self.zoneId))
        else:
            if self.notify.getDebug():
                self.notify.debug('requestBattle from suit %s - denied by battle manager' % self.getDoId())
            self.b_setBrushOff(SuitDialog.getBrushOffIndex(self.getStyleName()))
            self.d_denyBattle(toonId)
        return

    def getConfrontPosHpr(self):
        return (self.confrontPos, self.confrontHpr)

    def flyAwayNow(self):
        self.b_setPathState(2)
        self.stopPathNow()
        name = self.taskName('flyAwayNow')
        taskMgr.remove(name)
        taskMgr.doMethodLater(SuitTimings.toSky, self.finishFlyAwayNow, name)

    def danceNowFlyAwayLater(self):
        self.b_setPathState(4)
        self.stopPathNow()
        name = self.taskName('danceNowFlyAwayLater')
        taskMgr.remove(name)
        taskMgr.doMethodLater(SuitTimings.victoryDance + SuitTimings.toSky, self.finishFlyAwayNow, name)

    def finishFlyAwayNow(self, task):
        self.notify.debug('Suit %s finishFlyAwayNow' % self.doId)
        self.requestRemoval()
        return Task.done

    def d_setSPDoId(self, doId):
        self.sendUpdate('setSPDoId', [doId])

    def getSPDoId(self):
        if self.sp:
            return self.sp.getDoId()
        else:
            return 0

    def releaseControl(self):
        self.b_setPathState(0)

    def b_setPathEndpoints(self, start, end, minPathLen, maxPathLen):
        self.setPathEndpoints(start, end, minPathLen, maxPathLen)
        self.d_setPathEndpoints(start, end, minPathLen, maxPathLen)

    def d_setPathEndpoints(self, start, end, minPathLen, maxPathLen):
        self.sendUpdate('setPathEndpoints', [start,
         end,
         minPathLen,
         maxPathLen])

    def setPathEndpoints(self, start, end, minPathLen, maxPathLen):
        self.pathEndpointStart = start
        self.pathEndpointEnd = end
        self.minPathLen = minPathLen
        self.maxPathLen = maxPathLen

    def getPathEndpoints(self):
        return (self.pathEndpointStart,
         self.pathEndpointEnd,
         self.minPathLen,
         self.maxPathLen)

    def b_setPathPosition(self, index, timestamp):
        self.setPathPosition(index, timestamp)
        self.d_setPathPosition(index, timestamp)

    def d_setPathPosition(self, index, timestamp):
        self.notify.debug('Suit %s reaches point %s at time %0.2f' % (self.getDoId(), index, timestamp))
        self.sendUpdate('setPathPosition', [index, globalClockDelta.localToNetworkTime(timestamp)])

    def setPathPosition(self, index, timestamp):
        self.pathPositionIndex = index
        self.pathPositionTimestamp = timestamp

    def getPathPosition(self):
        return (self.pathPositionIndex, globalClockDelta.localToNetworkTime(self.pathPositionTimestamp))

    def b_setPathState(self, state):
        self.setPathState(state)
        self.d_setPathState(state)

    def d_setPathState(self, state):
        self.sendUpdate('setPathState', [state])

    def setPathState(self, state):
        if self.pathState != state:
            self.pathState = state
            if state == 0:
                self.stopPathNow()
            elif state == 1:
                self.moveToNextLeg(None)
            elif state == 2:
                self.stopPathNow()
            elif state == 3:
                pass
            elif state == 4:
                self.stopPathNow()
            else:
                self.notify.error('Invalid state: ' + str(state))
        return

    def getPathState(self):
        return self.pathState

    def d_debugSuitPosition(self, elapsed, currentLeg, x, y, timestamp):
        timestamp = globalClockDelta.localToNetworkTime(timestamp)
        self.sendUpdate('debugSuitPosition', [elapsed,
         currentLeg,
         x,
         y,
         timestamp])

    def initializePath(self):
        self.makeLegList()
        if self.notify.getDebug():
            self.notify.debug('Leg list:')
            print self.legList
        idx1 = self.startPoint.getIndex()
        idx2 = self.endPoint.getIndex()
        self.pathStartTime = globalClock.getFrameTime()
        self.setPathEndpoints(idx1, idx2, self.minPathLen, self.maxPathLen)
        self.setPathPosition(0, self.pathStartTime)
        self.pathState = 1
        self.currentLeg = 0
        self.zoneId = self.legList.getZoneId(0)
        self.legType = self.legList.getType(0)
        if self.notify.getDebug():
            self.notify.debug('creating suit in zone %s' % self.zoneId)

    def resync(self):
        self.b_setPathPosition(self.currentLeg, self.pathStartTime + self.legList.getStartTime(self.currentLeg))

    def moveToNextLeg(self, task):
        now = globalClock.getFrameTime()
        elapsed = now - self.pathStartTime
        nextLeg = self.legList.getLegIndexAtTime(elapsed, self.currentLeg)
        numLegs = self.legList.getNumLegs()
        if self.currentLeg != nextLeg:
            if nextLeg >= numLegs:
                self.flyAwayNow()
                return Task.done
            self.currentLeg = nextLeg
            self.__beginLegType(self.legList.getType(nextLeg))
            zoneId = self.legList.getZoneId(nextLeg)
            if zoneId:
                self.__enterZone(zoneId)
            self.notify.debug('Suit %s reached leg %s of %s in zone %s.' % (self.getDoId(),
             nextLeg,
             numLegs - 1,
             self.zoneId))
            if self.DEBUG_SUIT_POSITIONS:
                leg = self.legList.getLeg(nextLeg)
                pos = leg.getPosAtTime(elapsed - leg.getStartTime())
                self.d_debugSuitPosition(elapsed, nextLeg, pos[0], pos[1], now)
        if now - self.pathPositionTimestamp > self.UPDATE_TIMESTAMP_INTERVAL:
            self.resync()
        if self.pathState != 1:
            return Task.done
        nextLeg += 1
        while nextLeg + 1 < numLegs and self.legList.getZoneId(nextLeg) == ZoneUtil.getCanonicalZoneId(self.zoneId) and self.legList.getType(nextLeg) == self.legType:
            nextLeg += 1

        if nextLeg < numLegs:
            nextTime = self.legList.getStartTime(nextLeg)
            delay = nextTime - elapsed
            taskMgr.remove(self.taskName('move'))
            taskMgr.doMethodLater(delay, self.moveToNextLeg, self.taskName('move'))
        else:
            if simbase.config.GetBool('want-cogbuildings', True):
                self.startTakeOver()
            self.requestRemoval()
        return Task.done

    def stopPathNow(self):
        taskMgr.remove(self.taskName('move'))

    def __enterZone(self, zoneId):
        if zoneId != self.zoneId:
            self.sp.zoneChange(self, self.zoneId, zoneId)
            # Originally, we would call self.air.sendSetZoneMsg(). I think the
            # following is a worthy replacement, however:
            self.b_setLocation(simbase.air.districtId, zoneId)
            self.zoneId = zoneId
            if self.pathState == 1:
                self.sp.checkForBattle(zoneId, self)

    def __beginLegType(self, legType):
        self.legType = legType
        if legType == SuitLeg.TWalkFromStreet:
            self.checkBuildingState()
        elif legType == SuitLeg.TToToonBuilding:
            self.openToonDoor()
        elif legType == SuitLeg.TToSuitBuilding:
            self.openSuitDoor()
        elif legType == SuitLeg.TToCogHQ:
            self.openCogHQDoor(1)
        elif legType == SuitLeg.TFromCogHQ:
            self.openCogHQDoor(0)

    def resume(self):
        self.notify.debug('Suit %s resume' % self.doId)
        if self.currHP <= 0:
            self.notify.debug('Suit %s dead after resume' % self.doId)
            self.requestRemoval()
        else:
            self.danceNowFlyAwayLater()

    def prepareToJoinBattle(self):
        self.b_setPathState(0)

    def interruptMove(self):
        SuitBase.SuitBase.interruptMove(self)

    def checkBuildingState(self):
        blockNumber = self.buildingDestination
        if blockNumber == None:
            return
        building = self.sp.buildingMgr.getBuilding(blockNumber)
        if self.attemptingTakeover:
            if not building.isToonBlock():
                self.flyAwayNow()
                return
            if not hasattr(building, 'door'):
                self.flyAwayNow()
                return
            building.door.setDoorLock(FADoorCodes.SUIT_APPROACHING)
        elif not building.isSuitBlock():
            self.flyAwayNow()
        return

    def openToonDoor(self):
        blockNumber = self.buildingDestination
        building = self.sp.buildingMgr.getBuilding(blockNumber)
        if not building.isToonBlock():
            self.flyAwayNow()
            return
        if not hasattr(building, 'door'):
            self.flyAwayNow()
            return
        building.door.requestSuitEnter(self.getDoId())

    def openSuitDoor(self):
        blockNumber = self.buildingDestination
        building = self.sp.buildingMgr.getBuilding(blockNumber)
        if not building.isSuitBlock():
            self.flyAwayNow()
            return

    def openCogHQDoor(self, enter):
        blockNumber = self.legList.getBlockNumber(self.currentLeg)
        try:
            door = self.sp.cogHQDoors[blockNumber]
        except:
            self.notify.error('No CogHQ door %s in zone %s' % (blockNumber, self.sp.zoneId))
            return

        if enter:
            door.requestSuitEnter(self.getDoId())
        else:
            door.requestSuitExit(self.getDoId())

    def startTakeOver(self):
        if not self.SUIT_BUILDINGS:
            return
        blockNumber = self.buildingDestination
        if self.sp.buildingMgr is None:
            return
        if not self.sp.buildingMgr.isSuitBlock(blockNumber):
            self.notify.debug('Suit %s taking over building %s in %s' % (self.getDoId(), blockNumber, self.zoneId))
            difficulty = self.getActualLevel() - 1

            dept = SuitDNA.getSuitDept(self.dna.name)
            if self.buildingDestinationIsCogdo:
                self.sp.cogdoTakeOver(blockNumber, difficulty, self.buildingHeight, dept)
            else:
                self.sp.suitTakeOver(blockNumber, dept, difficulty, self.buildingHeight)
