import copy

import DistributedElevatorIntAI
from ElevatorConstants import *
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObjectAI
from direct.distributed.ClockDelta import *
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from direct.task import Timer
from otp.ai.AIBaseGlobal import *
from toontown.battle import BattleBase
from toontown.battle import DistributedBattleBldgAI
from toontown.toonbase.ToontownBattleGlobals import *


class DistributedSuitInteriorAI(DistributedObjectAI.DistributedObjectAI):
    def __init__(self, air, elevator):
        self.air = air
        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
        (self.extZoneId, self.zoneId) = elevator.bldg.getExteriorAndInteriorZoneId()
        self.numFloors = elevator.bldg.planner.numFloors
        self.avatarExitEvents = []
        self.toons = []
        self.toonSkillPtsGained = {}
        self.toonExp = {}
        self.toonOrigQuests = {}
        self.toonItems = {}
        self.toonOrigMerits = {}
        self.toonMerits = {}
        self.toonParts = {}
        self.helpfulToons = []
        self.currentFloor = 0
        self.topFloor = self.numFloors - 1
        self.bldg = elevator.bldg
        self.elevator = elevator
        self.suits = []
        self.activeSuits = []
        self.reserveSuits = []
        self.joinedReserves = []
        self.suitsKilled = []
        self.suitsKilledPerFloor = []
        self.battle = None
        self.timer = Timer.Timer()
        self.responses = {}
        self.ignoreResponses = 0
        self.ignoreElevatorDone = 0
        self.ignoreReserveJoinDone = 0
        self.toonIds = copy.copy(elevator.seats)
        for toonId in self.toonIds:
            if toonId is not None:
                self.__addToon(toonId)
        self.savedByMap = {}
        self.fsm = ClassicFSM.ClassicFSM(
            'DistributedSuitInteriorAI',
            [
                State.State('WaitForAllToonsInside',
                            self.enterWaitForAllToonsInside, self.exitWaitForAllToonsInside,
                            ['Elevator']),
                State.State('Elevator',
                            self.enterElevator, self.exitElevator,
                            ['Battle']),
                State.State('Battle',
                            self.enterBattle, self.exitBattle,
                            ['ReservesJoining', 'BattleDone']),
                State.State('ReservesJoining',
                            self.enterReservesJoining, self.exitReservesJoining,
                            ['Battle']),
                State.State('BattleDone',
                            self.enterBattleDone, self.exitBattleDone,
                            ['Resting', 'Reward']),
                State.State('Resting',
                            self.enterResting, self.exitResting,
                            ['Elevator']),
                State.State('Reward', self.enterReward, self.exitReward,
                            ['Off']),
                State.State('Off',
                            self.enterOff, self.exitOff,
                            ['WaitForAllToonsInside'])
            ], 'Off', 'Off', onUndefTransition=ClassicFSM.ClassicFSM.ALLOW)
        self.fsm.enterInitialState()

    def delete(self):
        self.ignoreAll()
        self.toons = []
        self.toonIds = []
        self.fsm.requestFinalState()
        del self.fsm
        del self.bldg
        del self.elevator
        self.timer.stop()
        del self.timer
        self.__cleanupFloorBattle()
        taskName = self.taskName('deleteInterior')
        taskMgr.remove(taskName)
        DistributedObjectAI.DistributedObjectAI.delete(self)

    def __handleUnexpectedExit(self, toonId):
        self.notify.warning('toon: %d exited unexpectedly' % toonId)
        self.__removeToon(toonId)
        if len(self.toons) == 0:
            self.timer.stop()
            if self.fsm.getCurrentState().getName() == 'Resting':
                return
            elif self.battle is None:
                self.bldg.deleteSuitInterior()

    def __addToon(self, toonId):
        if toonId not in self.air.doId2do:
            self.notify.warning('addToon() - no toon for doId: %d' % toonId)
            return
        event = self.air.getAvatarExitEvent(toonId)
        self.avatarExitEvents.append(event)
        self.accept(event, self.__handleUnexpectedExit, extraArgs=[toonId])
        self.toons.append(toonId)
        self.responses[toonId] = 0

    def __removeToon(self, toonId):
        if toonId in self.toons:
            self.toons.remove(toonId)
        if toonId in self.toonIds:
            self.toonIds[self.toonIds.index(toonId)] = None
        if toonId in self.responses:
            del self.responses[toonId]
        event = self.air.getAvatarExitEvent(toonId)
        if self.avatarExitEvents.count(event):
            self.avatarExitEvents.remove(event)
        self.ignore(event)

    def __resetResponses(self):
        self.responses = {}
        for toon in self.toons:
            self.responses[toon] = 0
        self.ignoreResponses = 0

    def __allToonsResponded(self):
        for toon in self.toons:
            if self.responses[toon] == 0:
                return 0
        self.ignoreResponses = 1
        return 1

    def getZoneId(self):
        return self.zoneId

    def getExtZoneId(self):
        return self.extZoneId

    def getDistBldgDoId(self):
        return self.bldg.getDoId()

    def getNumFloors(self):
        return self.numFloors

    def d_setToons(self):
        self.sendUpdate('setToons', self.getToons())

    def getToons(self):
        sendIds = []
        for toonId in self.toonIds:
            if toonId is None:
                sendIds.append(0)
            else:
                sendIds.append(toonId)
        return [sendIds, 0]

    def d_setSuits(self):
        self.sendUpdate('setSuits', self.getSuits())

    def getSuits(self):
        suitIds = []
        for suit in self.activeSuits:
            suitIds.append(suit.doId)
        reserveIds = []
        values = []
        for info in self.reserveSuits:
            reserveIds.append(info[0].doId)
            values.append(info[1])
        return [suitIds, reserveIds, values]

    def b_setState(self, state):
        self.d_setState(state)
        self.setState(state)

    def d_setState(self, state):
        stime = globalClock.getRealTime() + BattleBase.SERVER_BUFFER_TIME
        self.sendUpdate('setState', [
            state, globalClockDelta.localToNetworkTime(stime)])

    def setState(self, state):
        self.fsm.request(state)

    def getState(self):
        return [self.fsm.getCurrentState().getName(),
                globalClockDelta.getRealNetworkTime()]

    def setAvatarJoined(self):
        avId = self.air.getAvatarIdFromSender()
        if self.toons.count(avId) == 0:
            self.air.writeServerEvent('suspicious', avId, 'DistributedSuitInteriorAI.setAvatarJoined from toon not in %s.' % self.toons)
            self.notify.warning('setAvatarJoined() - av: %d not in list' % avId)
            return
        avatar = self.air.doId2do.get(avId)
        if avatar is not None:
            self.savedByMap[avId] = (avatar.getName(), avatar.dna.asTuple())
        self.responses[avId] += 1
        if self.__allToonsResponded():
            self.fsm.request('Elevator')

    def elevatorDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return
        elif self.fsm.getCurrentState().getName() != 'Elevator':
            self.notify.warning('elevatorDone() - in state: %s' % self.fsm.getCurrentState().getName())
            return
        elif self.toons.count(toonId) == 0:
            self.notify.warning('elevatorDone() - toon not in toon list: %d' % toonId)
            return
        self.responses[toonId] += 1
        if self.__allToonsResponded() and self.ignoreElevatorDone == 0:
            self.b_setState('Battle')

    def reserveJoinDone(self):
        toonId = self.air.getAvatarIdFromSender()
        if self.ignoreResponses == 1:
            return
        elif self.fsm.getCurrentState().getName() != 'ReservesJoining':
            self.notify.warning('reserveJoinDone() - in state: %s' % self.fsm.getCurrentState().getName())
            return
        elif self.toons.count(toonId) == 0:
            self.notify.warning('reserveJoinDone() - toon not in list: %d' % toonId)
            return
        self.responses[toonId] += 1
        if self.__allToonsResponded() and self.ignoreReserveJoinDone == 0:
            self.b_setState('Battle')

    def enterOff(self):
        pass

    def exitOff(self):
        pass

    def enterWaitForAllToonsInside(self):
        self.__resetResponses()

    def exitWaitForAllToonsInside(self):
        self.__resetResponses()

    def enterElevator(self):
        suitHandles = self.bldg.planner.genFloorSuits(self.currentFloor)
        self.suits = suitHandles['activeSuits']
        self.activeSuits = []
        for suit in self.suits:
            self.activeSuits.append(suit)
        self.reserveSuits = suitHandles['reserveSuits']
        self.d_setToons()
        self.d_setSuits()
        self.__resetResponses()
        self.d_setState('Elevator')
        self.timer.startCallback(BattleBase.ELEVATOR_T + ElevatorData[ELEVATOR_NORMAL]['openTime'] + BattleBase.SERVER_BUFFER_TIME, self.__serverElevatorDone)

    def __serverElevatorDone(self):
        self.ignoreElevatorDone = 1
        self.b_setState('Battle')

    def exitElevator(self):
        self.timer.stop()
        self.__resetResponses()

    def __createFloorBattle(self):
        if self.currentFloor == self.topFloor:
            bossBattle = 1
        else:
            bossBattle = 0
        self.battle = DistributedBattleBldgAI.DistributedBattleBldgAI(self.air, self.zoneId, self.__handleRoundDone, self.__handleBattleDone, bossBattle = bossBattle)
        self.battle.suitsKilled = self.suitsKilled
        self.battle.suitsKilledPerFloor = self.suitsKilledPerFloor
        self.battle.battleCalc.toonSkillPtsGained = self.toonSkillPtsGained
        self.battle.toonExp = self.toonExp
        self.battle.toonOrigQuests = self.toonOrigQuests
        self.battle.toonItems = self.toonItems
        self.battle.toonOrigMerits = self.toonOrigMerits
        self.battle.toonMerits = self.toonMerits
        self.battle.toonParts = self.toonParts
        self.battle.helpfulToons = self.helpfulToons
        self.battle.setInitialMembers(self.toons, self.suits)
        self.battle.generateWithRequired(self.zoneId)
        mult = getCreditMultiplier(self.currentFloor)
        if self.air.suitInvasionManager.getInvading():
            mult *= getInvasionMultiplier()
        self.battle.battleCalc.setSkillCreditMultiplier(mult)

    def __cleanupFloorBattle(self):
        for suit in self.suits:
            self.notify.debug('cleaning up floor suit: %d' % suit.doId)
            if suit.isDeleted():
                self.notify.debug('whoops, suit %d is deleted.' % suit.doId)
                continue
            suit.requestDelete()
        self.suits = []
        self.reserveSuits = []
        self.activeSuits = []
        if self.battle != None:
            self.battle.requestDelete()
        self.battle = None

    def __handleRoundDone(self, toonIds, totalHp, deadSuits):
        totalMaxHp = 0
        for suit in self.suits:
            totalMaxHp += suit.maxHP
        for suit in deadSuits:
            self.activeSuits.remove(suit)
        if len(self.reserveSuits) > 0 and len(self.activeSuits) < 4:
            self.joinedReserves = []
            hpPercent = 100 - (totalHp / totalMaxHp) * 100.0
            for info in self.reserveSuits:
                if info[1] <= hpPercent and len(self.activeSuits) < 4:
                    self.suits.append(info[0])
                    self.activeSuits.append(info[0])
                    self.joinedReserves.append(info)
                    continue
            for info in self.joinedReserves:
                self.reserveSuits.remove(info)
            if len(self.joinedReserves) > 0:
                self.fsm.request('ReservesJoining')
                self.d_setSuits()
                return
        if len(self.activeSuits) == 0:
            self.fsm.request('BattleDone', [toonIds])
        else:
            self.battle.resume()

    def __handleBattleDone(self, zoneId, toonIds):
        if len(toonIds) == 0:
            taskName = self.taskName('deleteInterior')
            taskMgr.doMethodLater(10, self.__doDeleteInterior, taskName)
        elif self.currentFloor == self.topFloor:
            self.setState('Reward')
        else:
            self.b_setState('Resting')

    def __doDeleteInterior(self, task):
        self.bldg.deleteSuitInterior()

    def enterBattle(self):
        if self.battle is None:
            self.__createFloorBattle()
            self.elevator.d_setFloor(self.currentFloor)

    def exitBattle(self):
        pass

    def enterReservesJoining(self):
        self.__resetResponses()
        self.timer.startCallback(ElevatorData[ELEVATOR_NORMAL]['openTime'] + SUIT_HOLD_ELEVATOR_TIME + BattleBase.SERVER_BUFFER_TIME, self.__serverReserveJoinDone)

    def __serverReserveJoinDone(self):
        self.ignoreReserveJoinDone = 1
        self.b_setState('Battle')

    def exitReservesJoining(self):
        self.timer.stop()
        self.__resetResponses()
        for info in self.joinedReserves:
            self.battle.suitRequestJoin(info[0])
        self.battle.resume()
        self.joinedReserves = []

    def enterBattleDone(self, toonIds):
        if len(toonIds) != len(self.toons):
            deadToons = []
            for toon in self.toons:
                if toonIds.count(toon) == 0:
                    deadToons.append(toon)
                    continue
            for toon in deadToons:
                self.__removeToon(toon)
        self.d_setToons()
        if len(self.toons) == 0:
            self.bldg.deleteSuitInterior()
        elif self.currentFloor == self.topFloor:
            self.battle.resume(self.currentFloor, topFloor = 1)
        else:
            self.battle.resume(self.currentFloor, topFloor = 0)

    def exitBattleDone(self):
        self.__cleanupFloorBattle()

    def __handleEnterElevator(self):
        self.fsm.request('Elevator')

    def enterResting(self):
        self.intElevator = DistributedElevatorIntAI.DistributedElevatorIntAI(self.air, self, self.toons)
        self.intElevator.generateWithRequired(self.zoneId)

    def handleAllAboard(self, seats):
        if not hasattr(self, 'fsm'):
            return
        numOfEmptySeats = seats.count(None)
        if numOfEmptySeats == 4:
            self.bldg.deleteSuitInterior()
            return
        elif numOfEmptySeats >= 0 and numOfEmptySeats <= 3:
            pass
        else:
            self.error('Bad number of empty seats: %s' % numOfEmptySeats)
        for toon in self.toons:
            if seats.count(toon) == 0:
                self.__removeToon(toon)
                continue
        self.toonIds = copy.copy(seats)
        self.toons = []
        for toonId in self.toonIds:
            if toonId != None:
                self.toons.append(toonId)
                continue
        self.d_setToons()
        self.currentFloor += 1
        self.fsm.request('Elevator')

    def exitResting(self):
        self.intElevator.requestDelete()
        del self.intElevator

    def enterReward(self):
        victors = self.toonIds[:]
        savedBy = []
        for v in victors:
            tuple = self.savedByMap.get(v)
            if tuple:
                savedBy.append([v, tuple[0], tuple[1]])
                continue
        self.bldg.fsm.request('waitForVictors', [victors, savedBy])
        self.d_setState('Reward')

    def exitReward(self):
        pass