from pandac.PandaModules import Point3, VBase4
from direct.fsm.FSM import FSM
from direct.interval.IntervalGlobal import Sequence, Parallel, ActorInterval, Func, Wait, ParticleInterval, Track, LerpColorScaleInterval, LerpScaleInterval, LerpHprInterval
from direct.task.Task import Task
from toontown.battle import BattleParticles
from toontown.battle import MovieUtil
from toontown.minigame.MazeSuit import MazeSuit
from CogdoMazeGameObjects import CogdoMazeSplattable
import CogdoMazeGameGlobals as Globals
import random

class CogdoMazeSuit(MazeSuit, FSM, CogdoMazeSplattable):
    GagHitEventName = 'CogdoMazeSuit_GagHit'
    DeathEventName = 'CogdoMazeSuit_Death'
    ThinkEventName = 'CogdoMazeSuit_Think'

    def __init__(self, serialNum, maze, randomNumGen, difficulty, startTile, cogdoSuitType, walkAnimName = None):
        data = Globals.SuitData[cogdoSuitType]
        MazeSuit.__init__(self, serialNum, maze, randomNumGen, data['cellWalkPeriod'], difficulty, data['dnaName'], startTile=startTile, walkSameDirectionProb=Globals.SuitWalkSameDirectionProb, walkTurnAroundProb=Globals.SuitWalkTurnAroundProb, uniqueRandomNumGen=False, walkAnimName=walkAnimName)
        FSM.__init__(self, 'CogdoMazeSuit')
        CogdoMazeSplattable.__init__(self, self.suit, '%s-%i' % (Globals.SuitCollisionName, self.serialNum), 1.5)
        if 'scale' in data:
            self.suit.setScale(data['scale'])
        self.hp = data['hp']
        self.type = cogdoSuitType
        self.memos = data['memos']
        self.deathSuit = self.suit.getLoseActor()
        self.deathSuit.pose('lose', 0)
        BattleParticles.loadParticles()
        self._initSfx()

    def _initSfx(self):
        audioMgr = base.cogdoGameAudioMgr
        self._deathSoundIval = Sequence(audioMgr.createSfxIval('cogSpin', duration=1.6, startTime=0.6, volume=0.8, source=self.deathSuit), audioMgr.createSfxIval('cogDeath', volume=0.32, source=self.deathSuit))

    def _destroySfx(self):
        if self._deathSoundIval.isPlaying():
            self._deathSoundIval.finish()
        del self._deathSoundIval

    def destroy(self):
        BattleParticles.unloadParticles()
        self.ignoreAll()
        self._destroySfx()
        CogdoMazeSplattable.destroy(self)
        MazeSuit.destroy(self)

    def handleEnterSphere(self, collEntry):
        messenger.send(self.COLLISION_EVENT_NAME, [self.type, self.serialNum])

    def gameStart(self, gameStartTime):
        MazeSuit.gameStart(self, gameStartTime)
        self.accept(Globals.GagCollisionName + '-into-' + self.gagCollisionName, self.handleGagHit)
        messenger.send(self.ThinkEventName, [self, self.TX, self.TY])

    def initCollisions(self):
        MazeSuit.initCollisions(self)
        self.collNodePath.setScale(0.75)
        self.accept(self.uniqueName('again' + self.COLL_SPHERE_NAME), self.handleEnterSphere)

    def think(self, curTic, curT, unwalkables):
        MazeSuit.think(self, curTic, curT, unwalkables)
        messenger.send(self.ThinkEventName, [self, self.TX, self.TY])

    def handleGagHit(self, collEntry):
        gagNodePath = collEntry.getFromNodePath().getParent()
        messenger.send(self.GagHitEventName, [self.type, self.serialNum, gagNodePath])

    def _getSuitAnimationIval(self, animName, startFrame = 0, duration = 1, partName = None, nextState = None):
        totalFrames = self.suit.getNumFrames(animName)
        frames = totalFrames - 1 - startFrame
        frameRate = self.suit.getFrameRate(animName)
        newRate = frames / duration
        playRate = newRate / frameRate
        ival = Sequence(ActorInterval(self.suit, animName, startTime=startFrame / newRate, endTime=totalFrames / newRate, playRate=playRate, partName=partName))
        if nextState is not None:

            def done():
                self.request(nextState)

            ival.append(Func(done))
        return ival

    def hitByGag(self):
        self.hp = self.hp - 1
        self.doSplat()
        if self.hp <= 0:
            self.explode()

    def explode(self):
        self.doDeathTrack()
        messenger.send(self.DeathEventName, [self.type, self.serialNum])

    def doDeathTrack(self):

        def removeDeathSuit(suit, deathSuit):
            if not deathSuit.isEmpty():
                deathSuit.detachNode()
                suit.cleanupLoseActor()

        self.deathSuit.reparentTo(self.suit.getParent())
        self.deathSuit.setScale(self.suit.getScale())
        self.deathSuit.setPos(render, self.suit.getPos(render))
        self.deathSuit.setHpr(render, self.suit.getHpr(render))
        self.suit.hide()
        self.collNodePath.reparentTo(self.deathSuit)
        gearPoint = Point3(0, 0, self.suit.height / 2.0 + 2.0)
        smallGears = BattleParticles.createParticleEffect(file='gearExplosionSmall')
        singleGear = BattleParticles.createParticleEffect('GearExplosion', numParticles=1)
        smallGearExplosion = BattleParticles.createParticleEffect('GearExplosion', numParticles=10)
        bigGearExplosion = BattleParticles.createParticleEffect('BigGearExplosion', numParticles=30)
        smallGears.setPos(gearPoint)
        singleGear.setPos(gearPoint)
        smallGearExplosion.setPos(gearPoint)
        bigGearExplosion.setPos(gearPoint)
        smallGears.setDepthWrite(False)
        singleGear.setDepthWrite(False)
        smallGearExplosion.setDepthWrite(False)
        bigGearExplosion.setDepthWrite(False)
        suitTrack = Sequence(Func(self.collNodePath.stash), ActorInterval(self.deathSuit, 'lose', startFrame=80, endFrame=140), Func(removeDeathSuit, self.suit, self.deathSuit, name='remove-death-suit'))
        explosionTrack = Sequence(Wait(1.5), MovieUtil.createKapowExplosionTrack(self.deathSuit, explosionPoint=gearPoint))
        gears1Track = Sequence(ParticleInterval(smallGears, self.deathSuit, worldRelative=0, duration=4.3, cleanup=True), name='gears1Track')
        gears2MTrack = Track((0.0, explosionTrack), (0.7, ParticleInterval(singleGear, self.deathSuit, worldRelative=0, duration=5.7, cleanup=True)), (5.2, ParticleInterval(smallGearExplosion, self.deathSuit, worldRelative=0, duration=1.2, cleanup=True)), (5.4, ParticleInterval(bigGearExplosion, self.deathSuit, worldRelative=0, duration=1.0, cleanup=True)), name='gears2MTrack')

        def removeParticle(particle):
            if particle and hasattr(particle, 'renderParent'):
                particle.cleanup()
                del particle

        removeParticles = Sequence(Func(removeParticle, smallGears), Func(removeParticle, singleGear), Func(removeParticle, smallGearExplosion), Func(removeParticle, bigGearExplosion))
        self.deathTrack = Sequence(Parallel(suitTrack, gears2MTrack, gears1Track, self._deathSoundIval), removeParticles)
        self.deathTrack.start()


class CogdoMazeSlowMinionSuit(CogdoMazeSuit):

    def __init__(self, serialNum, maze, randomNumGen, difficulty, startTile = None):
        CogdoMazeSuit.__init__(self, serialNum, maze, randomNumGen, difficulty, startTile, Globals.SuitTypes.SlowMinion)
        self.defaultTransitions = {'Off': ['Normal'],
         'Normal': ['Attack', 'Off'],
         'Attack': ['Normal']}

    def gameStart(self, gameStartTime):
        CogdoMazeSuit.gameStart(self, gameStartTime)
        self.request('Normal')

    def enterNormal(self):
        self.startWalkAnim()

    def exitNormal(self):
        pass

    def enterAttack(self, elapsedTime):
        self._attackIval = self._getSuitAnimationIval('finger-wag', duration=2.0, nextState='Normal')
        self._attackIval.start(elapsedTime)

    def filterAttack(self, request, args):
        if request == 'Attack':
            return None
        else:
            return self.defaultFilter(request, args)
        return None

    def exitAttack(self):
        self._attackIval.pause()
        del self._attackIval


class CogdoMazeFastMinionSuit(CogdoMazeSuit):

    def __init__(self, serialNum, maze, randomNumGen, difficulty, startTile = None):
        CogdoMazeSuit.__init__(self, serialNum, maze, randomNumGen, difficulty, startTile, Globals.SuitTypes.FastMinion)


class CogdoMazeBossSuit(CogdoMazeSuit):
    BlinkTaskName = 'CogdoMazeBossBlinkTask'
    ShakeTaskName = 'CogdoMazeBossShakeTask'
    StartWalkTaskName = 'CogdoMazeBossStartWalkTask'
    ShakeEventName = 'CogdoMazeSuitShake'

    def __init__(self, serialNum, maze, randomNumGen, difficulty, startTile = None):
        CogdoMazeSuit.__init__(self, serialNum, maze, randomNumGen, difficulty, startTile, Globals.SuitTypes.Boss, walkAnimName='stomp')
        self.dropTimer = 0
        self._walkSpeed = float(self.maze.cellWidth) / self.cellWalkDuration * 0.5

    def _initSfx(self):
        CogdoMazeSuit._initSfx(self)
        audioMgr = base.cogdoGameAudioMgr
        self._stompSfxIval = audioMgr.createSfxIval('cogStomp', source=self.suit, cutoff=Globals.BossStompSfxCutoff, volume=0.3)
        self._hitSfx = audioMgr.createSfx('bossCogAngry', self.suit)

    def _destroySfx(self):
        del self._hitSfx
        if self._stompSfxIval.isPlaying():
            self._stompSfxIval.finish()
        del self._stompSfxIval
        CogdoMazeSuit._destroySfx(self)

    def spin(self):
        part = self.suit
        time = Globals.BossSpinTime
        degrees = 360 * Globals.BossSpinCount
        spinIval = LerpHprInterval(part, time, (self.suit.getH() + degrees, 0, 0), blendType='easeOut')
        spinIval.start()

    def hitByGag(self):
        if self.hp >= 2:
            self._hitSfx.play()
            self.spin()
            self.suit.setColorScale(Globals.BlinkColor)
            self.__startBlinkTask()
        elif self.hp == 1:
            self.__stopBlinkTask()
        CogdoMazeSuit.hitByGag(self)

    def gameStart(self, gameStartTime):
        CogdoMazeSuit.gameStart(self, gameStartTime)

    def startWalkAnim(self):
        self.suit.loop(self._walkAnimName, fromFrame=43, toFrame=81)
        self.suit.setPlayRate(self._walkSpeed * Globals.BossCogStompAnimationPlayrateFactor, self._walkAnimName)
        self.__startShakeTask()

    def destroy(self):
        CogdoMazeSuit.destroy(self)
        self.__stopShakeTask()
        self.__stopBlinkTask()

    def pickRandomValidSpot(self, r = 5):
        validSpots = []
        for x in xrange(self.TX - r, self.TX + r):
            for y in xrange(self.TY - r, self.TY + r):
                if self.maze.isWalkable(x, y):
                    validSpots.append([x, y])

        return self.rng.choice(validSpots)

    def __startShakeTask(self):
        self.__stopShakeTask()
        taskMgr.doMethodLater(Globals.BossShakeTime, self.__shake, self.uniqueName(CogdoMazeBossSuit.ShakeTaskName))
        self.bossShakeLastTime = 0

    def __stopShakeTask(self):
        taskMgr.remove(self.uniqueName(CogdoMazeBossSuit.ShakeTaskName))

    def __shake(self, task):
        if task.time - self.bossShakeLastTime > Globals.BossShakeTime:
            self.suit.setPlayRate(self._walkSpeed * Globals.BossCogStompAnimationPlayrateFactor, self._walkAnimName)
            self._stompSfxIval.start()
            messenger.send(self.ShakeEventName, [self, Globals.BossShakeStrength])
            self.bossShakeLastTime = task.time
        return task.cont

    def __startBlinkTask(self):
        self.__stopBlinkTask()
        taskMgr.doMethodLater(Globals.BlinkFrequency, self.__blink, CogdoMazeBossSuit.BlinkTaskName)

    def __stopBlinkTask(self):
        taskMgr.remove(CogdoMazeBossSuit.BlinkTaskName)

    def __blink(self, task):
        blink = Sequence(LerpColorScaleInterval(self.suit, Globals.BlinkSpeed, VBase4(1.0, 1.0, 1.0, 1.0)), LerpColorScaleInterval(self.suit, Globals.BlinkSpeed, Globals.BlinkColor))
        blink.start()
        return Task.again