import random
from direct.showbase.DirectObject import DirectObject
from direct.interval.IntervalGlobal import LerpFunc, ActorInterval, LerpPosInterval
from direct.interval.MetaInterval import Sequence
from direct.directutil import Mopath
from toontown.toonbase import ToonPythonUtil as PythonUtil
from panda3d.core import *
from panda3d.direct import *
from panda3d.physics import *
from toontown.toonbase import ToontownGlobals
from toontown.suit import Suit
from toontown.suit import SuitDNA
from toontown.battle import BattleProps
from toontown.cogdominium import CogdoUtil
from toontown.cogdominium import CogdoFlyingGameGlobals as Globals
from toontown.cogdominium.CogdoFlyingUtil import swapAvatarShadowPlacer
from direct.particles import ParticleEffect
from direct.particles import Particles
from direct.particles import ForceGroup

class CogdoFlyingObstacleFactory:

    def __init__(self):
        self._index = -1
        self._whirlwindModel = CogdoUtil.loadFlyingModel('whirlwind').find('**/whirlwind')
        self._fanModel = CogdoUtil.loadFlyingModel('streamer').find('**/streamer')

    def destroy(self):
        self._whirlwindModel.removeNode()
        del self._whirlwindModel
        self._fanModel.removeNode()
        del self._fanModel
        if Globals.Level.AddParticlesToStreamers:
            if hasattr(self, 'f'):
                self.f.cleanup()
                del self.f

    def createFan(self):
        self._index += 1
        return CogdoFlyingFan(self._index, self._fanModel)

    def createFlyingMinion(self, motionPath = None):
        self._index += 1
        return CogdoFlyingMinionFlying(self._index, motionPath=motionPath)

    def createWalkingMinion(self, motionPath = None):
        self._index += 1
        return CogdoFlyingMinionWalking(self._index, motionPath=motionPath)

    def createWhirlwind(self, motionPath = None):
        self._index += 1
        return CogdoFlyingWhirlwind(self._index, self._whirlwindModel, motionPath=motionPath)

    def createStreamerParticles(self, color1, color2, amp):
        self.f = ParticleEffect.ParticleEffect('streamer_particles')
        p0 = Particles.Particles('particles-1')
        p0.setFactory('PointParticleFactory')
        p0.setRenderer('SparkleParticleRenderer')
        p0.setEmitter('RingEmitter')
        p0.setPoolSize(80)
        p0.setBirthRate(0.05)
        p0.setLitterSize(100)
        p0.setLitterSpread(0)
        p0.factory.setLifespanBase(3.0)
        p0.factory.setLifespanSpread(0.5)
        p0.factory.setMassBase(1.0)
        p0.factory.setMassSpread(0.0)
        p0.factory.setTerminalVelocityBase(5.0)
        p0.factory.setTerminalVelocitySpread(1.0)
        p0.renderer.setAlphaMode(BaseParticleRenderer.PRALPHAOUT)
        p0.renderer.setUserAlpha(1.0)
        p0.renderer.setCenterColor(color1)
        p0.renderer.setEdgeColor(color2)
        p0.renderer.setBirthRadius(0.3)
        p0.renderer.setDeathRadius(0.3)
        p0.renderer.setLifeScale(SparkleParticleRenderer.SPNOSCALE)
        p0.emitter.setEmissionType(BaseParticleEmitter.ETRADIATE)
        p0.emitter.setAmplitude(0)
        p0.emitter.setAmplitudeSpread(0)
        f0 = ForceGroup.ForceGroup('Gravity')
        force0 = LinearVectorForce(Vec3(0.0, 0.0, 10.0), 1.0, 0)
        force0.setVectorMasks(1, 1, 1)
        force0.setActive(1)
        f0.addForce(force0)
        self.f.addForceGroup(f0)
        p0.emitter.setRadius(5.0)
        self.f.addParticles(p0)
        self.f.setPos(0, 0, 0)
        self.f.setHpr(0, 0, 0)
        return self.f

class CogdoFlyingObstacle(DirectObject):
    EnterEventName = 'CogdoFlyingObstacle_Enter'
    ExitEventName = 'CogdoFlyingObstacle_Exit'
    MotionTypes = PythonUtil.Enum(('BackForth', 'Loop'))

    def __init__(self, type, index, model, collSolid, motionPath = None, motionPattern = None, blendMotion = True, instanceModel = True):
        self.type = type
        self.index = index
        name = 'CogdoFlyingObstacle-%s-%i' % (self.type, self.index)
        if instanceModel:
            self.model = NodePath(name)
            model.instanceTo(self.model)
        else:
            self.model = model
            self.model.setName(name)
        self.currentT = 0.0
        self.direction = 1.0
        self.collNode = None
        self._initCollisions(name, collSolid)
        self.motionPath = motionPath
        self.motionPattern = motionPattern
        self.motionSequence = None
        if blendMotion:
            blendType = 'easeInOut'
        else:
            blendType = 'noBlend'
        if motionPath is not None:

            def moveObstacle(value):
                self.motionPath.goTo(self.model, value)

            self.motionPath = Mopath.Mopath(name='obstacle-%i' % self.index)
            self.motionPath.loadNodePath(motionPath)
            dur = self.motionPath.getMaxT()
            self.motionSequence = Sequence(name='%s.obstacle-%i-motionSequence' % (self.__class__.__name__, self.index))
            movePart1 = LerpFunc(moveObstacle, fromData=0.0, toData=self.motionPath.getMaxT(), duration=dur, blendType=blendType)
            self.motionSequence.append(movePart1)
            if self.motionPattern == CogdoFlyingObstacle.MotionTypes.BackForth:
                movePart2 = LerpFunc(moveObstacle, fromData=self.motionPath.getMaxT(), toData=0.0, duration=dur, blendType=blendType)
                self.motionSequence.append(movePart2)

    def _initCollisions(self, name, collSolid):
        self.collName = name
        self.collSolid = collSolid
        self.collSolid.setTangible(0)
        self.collNode = CollisionNode(self.collName)
        self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)
        self.collNode.addSolid(self.collSolid)
        self.collNodePath = self.model.attachNewNode(self.collNode)
        self.collNodePath.hide()
        self.accept('enter' + self.collName, self._handleEnterCollision)
        self.accept('exit' + self.collName, self._handleExitCollision)

    def disable(self):
        if self.collNode is not None:
            self.collNode.setIntoCollideMask(BitMask32(0))

    def enable(self):
        if self.collNode is not None:
            self.collNode.setIntoCollideMask(ToontownGlobals.WallBitmask)

    def startMoving(self, elapsedTime = 0.0):
        if self.motionSequence is not None:
            self.motionSequence.loop()
            self.motionSequence.setT(elapsedTime % self.motionSequence.getDuration())

    def stopMoving(self):
        if self.motionSequence is not None:
            self.motionSequence.pause()

    def destroy(self):
        self.ignoreAll()
        if self.motionSequence is not None:
            self.motionSequence.clearToInitial()
            del self.motionSequence
        
        del self.collSolid
        self.collNodePath.removeNode()
        del self.collNodePath
        del self.collNode
        self.model.removeNode()
        del self.model
        del self.motionPath

    def update(self, dt):
        pass

    def hide(self):
        self.ignoreAll()
        self.model.hide()
        self.collNode.setIntoCollideMask(BitMask32(0))

    def _handleEnterCollision(self, collEntry):
        messenger.send(CogdoFlyingObstacle.EnterEventName, [self, collEntry])

    def _handleExitCollision(self, collEntry):
        messenger.send(CogdoFlyingObstacle.ExitEventName, [self, collEntry])

from pandac.PandaModules import TransformState

class CogdoFlyingWhirlwind(CogdoFlyingObstacle):

    def __init__(self, index, model, motionPath = None):
        collSolid = CollisionTube(0, 0, 0, 0, 0, Globals.Gameplay.WhirlwindCollisionTubeHeight, Globals.Gameplay.WhirlwindCollisionTubeRadius)
        CogdoFlyingObstacle.__init__(self, Globals.Level.ObstacleTypes.Whirlwind, index, model, collSolid, motionPath=motionPath, motionPattern=CogdoFlyingObstacle.MotionTypes.BackForth)
        self.t = 0.0
        self._initModel()

    def _initModel(self):
        self.model.setDepthWrite(False)
        self._texStage = self.model.findTextureStage('*')
        self._soundIval = base.cogdoGameAudioMgr.createSfxIval('whirlwind', source=self.model)
        self.model.setBin('transparent', self.index)

    def startMoving(self, elapsedTime):
        CogdoFlyingObstacle.startMoving(self, elapsedTime)
        self.t = 0.0
        self._soundIval.loop()

    def update(self, dt):
        self.t += dt
        trans = TransformState.makePos((self.t, -self.t, 0))
        self.model.setTexTransform(self._texStage, trans)
        trans = TransformState.makePos((self.t * 2.0, -self.t * 2.0, 0))

    def stopMoving(self):
        CogdoFlyingObstacle.stopMoving(self)
        self._soundIval.pause()

    def destroy(self):
        self._soundIval.clearToInitial()
        del self._soundIval
        CogdoFlyingObstacle.destroy(self)


class CogdoFlyingMinion(CogdoFlyingObstacle):

    def __init__(self, index, collSolid, motionPath = None):
        self.prop = None
        self.suit = Suit.Suit()
        d = SuitDNA.SuitDNA()
        invSuit = base.cr.newsManager.getInvading()
        if invSuit:
            d.newSuit(invSuit)
        else:
            d.newSuit(random.choice(Globals.Gameplay.MinionDnaName))
        
        self.suit.setDNA(d)
        self.suit.setScale(Globals.Gameplay.MinionScale)
        self.suit.nametag3d.stash()
        self.suit.nametag.destroy()
        swapAvatarShadowPlacer(self.suit, 'minion-%sShadowPlacer' % index)
        self.mopathNodePath = NodePath('mopathNodePath')
        self.suit.reparentTo(self.mopathNodePath)
        CogdoFlyingObstacle.__init__(self, Globals.Level.ObstacleTypes.Minion, index, self.mopathNodePath, collSolid, motionPath=motionPath, motionPattern=CogdoFlyingObstacle.MotionTypes.Loop, blendMotion=False, instanceModel=False)
        self.lastPos = None
        self.suit.loop('neutral')

    def attachPropeller(self):
        if self.prop is None:
            self.prop = BattleProps.globalPropPool.getProp('propeller')
            head = self.suit.find('**/joint_head')
            self.prop.reparentTo(head)

    def detachPropeller(self):
        if self.prop:
            self.prop.cleanup()
            self.prop.removeNode()
            self.prop = None

    def startMoving(self, elapsedTime):
        CogdoFlyingObstacle.startMoving(self, elapsedTime)

    def stopMoving(self):
        CogdoFlyingObstacle.stopMoving(self)

    def update(self, dt):
        CogdoFlyingObstacle.update(self, dt)
        self.currPos = self.mopathNodePath.getPos()
        if self.lastPos != None:
            vec = self.currPos - self.lastPos
            self.mopathNodePath.lookAt(self.currPos + vec)
        
        self.mopathNodePath.setP(0)
        self.lastPos = self.mopathNodePath.getPos()

    def destroy(self):
        self.mopathNodePath.removeNode()
        del self.mopathNodePath
        self.suit.cleanup()
        self.suit.removeNode()
        self.suit.delete()
        CogdoFlyingObstacle.destroy(self)

class CogdoFlyingMinionFlying(CogdoFlyingMinion):

    def __init__(self, index, motionPath = None):
        radius = Globals.Gameplay.FlyingMinionCollisionRadius
        offset = Globals.Gameplay.FlyingMinionCollisionHeightOffset
        collSolid = CollisionSphere(0, 0, offset, radius)
        CogdoFlyingMinion.__init__(self, index, collSolid, motionPath)
        self.attachPropeller()
        self.propTrack = Sequence(ActorInterval(self.prop, 'propeller', startFrame=0, endFrame=14))
        dur = Globals.Gameplay.FlyingMinionFloatTime
        offset = Globals.Gameplay.FlyingMinionFloatOffset
        suitPos = self.suit.getPos()
        upperPos = suitPos + Point3(0.0, 0.0, offset / 2.0)
        lowerPos = suitPos + Point3(0.0, 0.0, -offset / 2.0)
        self.floatSequence = Sequence(LerpPosInterval(self.suit, dur / 4.0, startPos=suitPos, pos=upperPos, blendType='easeInOut'), LerpPosInterval(self.suit, 
            dur / 2.0, startPos=upperPos, pos=lowerPos, blendType='easeInOut'), LerpPosInterval(self.suit, dur / 4.0, startPos=lowerPos, pos=suitPos, 
            blendType='easeInOut'), name='%s.floatSequence%i' % (self.__class__.__name__, self.index))

    def startMoving(self, elapsedTime):
        CogdoFlyingMinion.startMoving(self, elapsedTime)
        self.floatSequence.loop(elapsedTime)
        self.propTrack.loop(elapsedTime)
        self.suit.pose('landing', 0)

    def stopMoving(self):
        CogdoFlyingMinion.stopMoving(self)
        self.floatSequence.clearToInitial()
        self.propTrack.pause()

    def destroy(self):
        self.floatSequence.clearToInitial()
        del self.floatSequence
        self.propTrack.clearToInitial()
        del self.propTrack
        CogdoFlyingMinion.destroy(self)


class CogdoFlyingMinionWalking(CogdoFlyingMinion):

    def __init__(self, index, motionPath = None):
        radius = Globals.Gameplay.WalkingMinionCollisionRadius
        offset = Globals.Gameplay.WalkingMinionCollisionHeightOffset
        collSolid = CollisionSphere(0, 0, offset, radius)
        CogdoFlyingMinion.__init__(self, index, collSolid, motionPath)

    def startMoving(self, elapsedTime):
        CogdoFlyingMinion.startMoving(self, elapsedTime)
        self.suit.loop('walk')

    def stopMoving(self):
        CogdoFlyingMinion.stopMoving(self)
        self.suit.loop('neutral')


class CogdoFlyingFan(CogdoFlyingObstacle):

    def __init__(self, index, model, motionPath = None):
        collSolid = CollisionTube(0, 0, 0, 0, 0, Globals.Gameplay.FanCollisionTubeHeight, Globals.Gameplay.FanCollisionTubeRadius)
        CogdoFlyingObstacle.__init__(self, Globals.Level.ObstacleTypes.Fan, index, model, collSolid)
        self.streamers = self.model.findAllMatches('**/streamer*')
        self._initIntervals()

    def _initIntervals(self):
        self.streamerIvals = []
        minDur = Globals.Gameplay.FanStreamerMinDuration
        maxDur = Globals.Gameplay.FanStreamerMaxDuration
        for streamer in self.streamers:
            dur = random.uniform(minDur, maxDur)
            streamerLerp = LerpFunc(streamer.setH, fromData=0.0, toData=360.0, duration=dur, name='%s.streamerLerp%i-%s' % (self.__class__.__name__, self.index, streamer.getName()))
            self.streamerIvals.append(streamerLerp)

    def startMoving(self, elapsedTime = 0.0):
        CogdoFlyingObstacle.startMoving(self, elapsedTime)
        timeDelay = 0.0
        maxDur = Globals.Gameplay.FanStreamerMaxDuration
        for ival in self.streamerIvals:
            taskName = 'delayedStreamerSpinTask-fan-%i-%s' % (self.index, ival.getName())
            taskMgr.doMethodLater(timeDelay, ival.loop, taskName, extraArgs=[])
            timeDelay += maxDur / (len(self.streamers) - 1)

    def stopMoving(self):
        CogdoFlyingObstacle.stopMoving(self)
        taskMgr.removeTasksMatching('delayedStreamerSpinTask-fan-%i*' % self.index)
        for streamerLerp in self.streamerIvals:
            streamerLerp.pause()

    def setBlowDirection(self):
        tempNodePath = NodePath('temp')
        tempNodePath.reparentTo(self.model)
        tempNodePath.setPos(0, 0, 1)
        self.blowVec = tempNodePath.getPos(render) - self.model.getPos(render)
        self.blowVec.normalize()
        tempNodePath.removeNode()
        del tempNodePath

    def getBlowDirection(self):
        return Vec3(self.blowVec)

    def destroy(self):
        taskMgr.removeTasksMatching('delayedStreamerSpinTask-fan-%i*' % self.index)
        for streamerLerp in self.streamerIvals:
            streamerLerp.clearToInitial()

        del self.streamerIvals[:]
        CogdoFlyingObstacle.destroy(self)
        del self.blowVec