from pandac.PandaModules import *
from toontown.toonbase.ToonBaseGlobal import *
from .DistributedMinigame import *
from direct.fsm import ClassicFSM, State
from direct.fsm import State
from toontown.toonbase import TTLocalizer, ToontownTimer
from toontown.toonbase import ToontownBattleGlobals
from toontown.minigame import PlayingCardGlobals
from toontown.minigame import PairingGameCard
from toontown.minigame import PlayingCardDeck
from toontown.minigame import PairingGameGlobals
from .OrthoWalk import OrthoWalk
from .OrthoDrive import OrthoDrive
from direct.interval.IntervalGlobal import Sequence, Parallel, Func, LerpColorScaleInterval, LerpScaleInterval, LerpFunctionInterval, Wait, SoundInterval
from toontown.toonbase.ToontownGlobals import GlobalDialogColor

class DistributedPairingGame(DistributedMinigame):
    TOON_SPEED = 11
    MAX_FRAME_MOVE = 1
    MAX_FACE_UP_CARDS = 2
    notify = directNotify.newCategory('DistributedPairingGame')
    bonusGlowTime = 0.5
    EndGameTaskName = 'endPairingGame'
    xCardInc = 4
    cardsPerRow = 8
    cardsPerCol = 5

    def __init__(self, cr):
        DistributedMinigame.__init__(self, cr)
        self.gameFSM = ClassicFSM.ClassicFSM('DistributedPairingGame', [State.State('off', self.enterOff, self.exitOff, ['play']), State.State('play', self.enterPlay, self.exitPlay, ['cleanup']), State.State('cleanup', self.enterCleanup, self.exitCleanup, [])], 'off', 'cleanup')
        self.addChildGameFSM(self.gameFSM)
        self.cameraTopView = (17.6, 6.18756, 43.9956, 0, -89, 0)
        self.cameraThreeQuarterView = (14.0, -8.93352, 33.4497, 0, -62.89, 0)
        self.deckSeed = 0
        self.faceUpList = []
        self.localFaceUpList = []
        self.inList = []
        self.inactiveList = []
        self.points = 0
        self.flips = 0
        self.matches = 0
        self.yCardInc = 4
        self.startingPositions = [(0, 0, 0, -45),
         ((self.cardsPerRow - 1) * self.xCardInc,
          (self.cardsPerCol - 1) * self.yCardInc,
          0,
          135),
         ((self.cardsPerRow - 1) * self.xCardInc,
          0,
          0,
          45),
         (0,
          (self.cardsPerCol - 1) * self.yCardInc,
          0,
          -135)]
        self.stageMin = Point2(0, 0)
        self.stageMax = Point2((self.cardsPerRow - 1) * self.xCardInc, (self.cardsPerCol - 1) * self.yCardInc)
        self.gameDuration = PairingGameGlobals.EasiestGameDuration

    def moveCameraToTop(self):
        camera.reparentTo(render)
        p = self.cameraThreeQuarterView
        camera.setPosHpr(p[0], p[1], p[2], p[3], p[4], p[5])

    def getTitle(self):
        return TTLocalizer.PairingGameTitle

    def getInstructions(self):
        if self.numPlayers > 1:
            return TTLocalizer.PairingGameInstructionsMulti
        else:
            return TTLocalizer.PairingGameInstructions

    def getMaxDuration(self):
        return 0

    def load(self):
        self.notify.debug('load')
        DistributedMinigame.load(self)
        self.gameDuration = PairingGameGlobals.calcGameDuration(self.getDifficulty())
        self.gameBoard = loader.loadModel('phase_4/models/minigames/memory_room')
        self.gameBoard.setPosHpr(0.5, 0, 0, 0, 0, 0)
        self.gameBoard.setScale(1.0)
        self.deck = PairingGameGlobals.createDeck(self.deckSeed, self.numPlayers)
        self.notify.debug('%s' % self.deck.cards)
        testCard = self.getDeckOrderIndex(self.cardsPerCol - 1, 0)
        if not testCard > -1:
            self.yCardInc *= 1.25
        self.cards = []
        for index in range(len(self.deck.cards)):
            cardValue = self.deck.cards[index]
            oneCard = PairingGameCard.PairingGameCard(cardValue)
            oneCard.load()
            xPos, yPos = self.getCardPos(index)
            oneCard.setPos(xPos, yPos, 0)
            oneCard.reparentTo(render)
            self.notify.debug('%s' % oneCard.getPos())
            self.notify.debug('suit %s rank %s value %s' % (oneCard.suit, oneCard.rank, oneCard.value))
            self.accept('entercardCollision-%d' % oneCard.value, self.enterCard)
            self.accept('exitcardCollision-%d' % oneCard.value, self.exitCard)
            oneCard.turnDown(doInterval=False)
            self.cards.append(oneCard)

        self.bonusTraversal = list(range(len(self.cards)))
        self.bonusGlow = render.attachNewNode('bonusGlow')
        sign = loader.loadModel('phase_4/models/minigames/garden_sign_memory')
        sign.find('**/sign1').removeNode()
        sign.find('**/sign2').removeNode()
        sign.find('**/collision').removeNode()
        sign.setPos(0, 0, 0.05)
        sign.reparentTo(self.bonusGlow)
        self.bonusGlow.setScale(2.5)
        self.pointsFrame = DirectFrame(relief=None, geom=DGG.getDefaultDialogGeom(), geom_color=GlobalDialogColor, geom_scale=(4, 1, 1), pos=(-0.33, 0, 0.9), scale=0.1, text=TTLocalizer.PairingGamePoints, text_align=TextNode.ALeft, text_scale=TTLocalizer.DPGpointsFrame, text_pos=(-1.94, -0.1, 0.0))
        self.pointsLabel = DirectLabel(parent=self.pointsFrame, relief=None, text='0', text_fg=VBase4(0, 0.5, 0, 1), text_align=TextNode.ARight, text_scale=0.7, pos=(1.82, 0, -0.15))
        self.flipsFrame = DirectFrame(relief=None, geom=DGG.getDefaultDialogGeom(), geom_color=GlobalDialogColor, geom_scale=(4, 1, 1), pos=(0.33, 0, 0.9), scale=0.1, text=TTLocalizer.PairingGameFlips, text_align=TextNode.ALeft, text_scale=TTLocalizer.DPGflipsFrame, text_pos=(-1.94, -0.1, 0.0))
        self.flipsLabel = DirectLabel(parent=self.flipsFrame, relief=None, text='0', text_fg=VBase4(0, 1.0, 0, 1), text_align=TextNode.ARight, text_scale=0.7, pos=(1.82, 0, -0.15))
        self.__textGen = TextNode('ringGame')
        self.__textGen.setFont(ToontownGlobals.getSignFont())
        self.__textGen.setAlign(TextNode.ACenter)
        self.sndPerfect = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_all_matched.ogg')
        self.calcBonusTraversal()
        self.music = base.loader.loadMusic('phase_4/audio/bgm/MG_Pairing.ogg')
        self.matchSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_match.ogg')
        self.matchWithBonusSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_match_bonus_both.ogg')
        self.signalSfx = []
        for i in range(4):
            self.signalSfx.append(base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_jumping_signal.ogg'))

        self.bonusMovesSfx = base.loader.loadSfx('phase_4/audio/sfx/MG_pairing_bonus_moves.ogg')
        return

    def unload(self):
        self.notify.debug('unload')
        DistributedMinigame.unload(self)
        self.removeChildGameFSM(self.gameFSM)
        del self.gameFSM
        self.gameBoard.removeNode()
        del self.gameBoard
        for card in self.cards:
            card.unload()
            del card

        self.cards = []
        self.pointsFrame.removeNode()
        del self.pointsFrame
        self.flipsFrame.removeNode()
        del self.flipsFrame
        del self.__textGen
        del self.sndPerfect
        self.bonusGlow.removeNode()
        del self.bonusGlow
        del self.music
        del self.matchSfx
        del self.matchWithBonusSfx
        for i in range(4):
            del self.signalSfx[0]

        self.signalSfx = []
        del self.bonusMovesSfx

    def onstage(self):
        self.notify.debug('onstage')
        DistributedMinigame.onstage(self)
        self.gameBoard.reparentTo(render)
        for card in self.cards:
            card.reparentTo(render)

        lt = base.localAvatar
        lt.reparentTo(render)
        lt.hideName()
        self.__placeToon(self.localAvId)
        lt.setAnimState('Happy', 1.0)
        lt.setSpeed(0, 0)
        self.moveCameraToTop()

    def offstage(self):
        self.notify.debug('offstage')
        self.gameBoard.hide()
        for card in self.cards:
            card.hide()

        DistributedMinigame.offstage(self)

    def handleDisabledAvatar(self, avId):
        self.notify.debug('handleDisabledAvatar')
        self.notify.debug('avatar ' + str(avId) + ' disabled')
        DistributedMinigame.handleDisabledAvatar(self, avId)

    def setGameReady(self):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameReady')
        if DistributedMinigame.setGameReady(self):
            return
        for index in range(self.numPlayers):
            avId = self.avIdList[index]
            toon = self.getAvatar(avId)
            if toon:
                toon.reparentTo(render)
                self.__placeToon(avId)
                toon.setAnimState('Happy', 1.0)
                toon.startSmooth()
                toon.startLookAround()

    def setGameStart(self, timestamp):
        if not self.hasLocalToon:
            return
        self.notify.debug('setGameStart')
        DistributedMinigame.setGameStart(self, timestamp)
        for avId in self.remoteAvIdList:
            toon = self.getAvatar(avId)
            if toon:
                toon.stopLookAround()

        self.gameFSM.request('play')

    def isInPlayState(self):
        if not self.gameFSM.getCurrentState():
            return False
        if not self.gameFSM.getCurrentState().getName() == 'play':
            return False
        return True

    def enterOff(self):
        self.notify.debug('enterOff')

    def exitOff(self):
        pass

    def enterPlay(self):
        self.notify.debug('enterPlay')
        base.playMusic(self.music, looping=1, volume=0.9)
        orthoDrive = OrthoDrive(self.TOON_SPEED, maxFrameMove=self.MAX_FRAME_MOVE, customCollisionCallback=self.__doPairingGameCollisions)
        self.orthoWalk = OrthoWalk(orthoDrive, broadcast=not self.isSinglePlayer())
        self.orthoWalk.start()
        self.accept('insert', self.__flipKeyPressed)
        self.accept('delete', self.__flipKeyPressed)
        self.accept('time-control', self.__beginSignal)
        self.accept('time-control-up', self.__endSignal)
        self.bonusGlowIndex = 0
        self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
        self.startBonusTask()
        self.timer = ToontownTimer.ToontownTimer()
        self.timer.posInTopRightCorner()
        self.timer.setTime(self.gameDuration)
        self.timer.countdown(self.gameDuration, self.timerExpired)
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.stop()

    def exitPlay(self):
        self.music.stop()
        self.orthoWalk.stop()
        self.orthoWalk.destroy()
        del self.orthoWalk
        self.bonusGlow.hide()
        self.stopBonusTask()
        self.timer.stop()
        self.timer.destroy()
        del self.timer
        self.ignoreAll()
        if base.localAvatar.laffMeter:
            base.localAvatar.laffMeter.start()
        if hasattr(self, 'perfectIval'):
            self.perfectIval.pause()
            del self.perfectIval
        taskMgr.remove(self.EndGameTaskName)
        taskMgr.remove('pairGameContinueSignal')

    def enterCleanup(self):
        self.notify.debug('enterCleanup')

    def exitCleanup(self):
        pass

    def __placeToon(self, avId):
        toon = self.getAvatar(avId)
        if self.numPlayers == 1:
            toon.setPos(0, 0, 0)
            toon.setHpr(0, 0, 0)
        else:
            posIndex = self.avIdList.index(avId)
            pos = self.startingPositions[posIndex]
            toon.setPos(pos[0], pos[1], pos[2])
            toon.setHpr(pos[3], 0, 0)

    def __doPairingGameCollisions(self, oldPos, newPos):
        x = bound(newPos[0], self.stageMin[0], self.stageMax[0])
        y = bound(newPos[1], self.stageMin[1], self.stageMax[1])
        newPos.setX(x)
        newPos.setY(y)
        if self.inList:
            newPos.setZ(0.15)
        else:
            newPos.setZ(0.0)
        if not oldPos == newPos:
            taskMgr.remove('pairGameContinueSignal')
        return newPos

    def getDeckOrderFromValue(self, value):
        for index in range(len(self.cards)):
            if self.cards[index].value == value:
                return index

        return -1

    def getDeckOrderFromPairingGameCard(self, into):
        try:
            index = self.cards.index(into)
        except ValueError:
            index = -1

        return index

    def enterCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])
        self.notify.debug('entered cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)
        if deckOrder not in self.inList:
            self.inList.append(deckOrder)

    def exitCard(self, colEntry):
        intoName = colEntry.getIntoNodePath().getName()
        parts = intoName.split('-')
        value = int(parts[1])
        self.notify.debug('exited cardValue %d' % value)
        deckOrder = self.getDeckOrderFromValue(value)
        if deckOrder in self.inList:
            self.inList.remove(deckOrder)

    def handleMatch(self, cardA, cardB, withBonus):
        self.notify.debug('we got a match %d %d' % (cardA, cardB))
        self.matches += 1
        if cardA in self.faceUpList:
            self.faceUpList.remove(cardA)
        if cardB in self.faceUpList:
            self.faceUpList.remove(cardB)
        self.inactiveList.append(cardA)
        self.inactiveList.append(cardB)
        matchIval = Parallel()
        for card in [cardA, cardB]:
            self.cards[card].setTransparency(1)
            cardSeq = Sequence(LerpColorScaleInterval(self.cards[card], duration=1, colorScale=Vec4(1.0, 1.0, 1.0, 0.0)), Func(self.cards[card].hide))
            matchIval.append(cardSeq)

        if withBonus:
            matchIval.append(SoundInterval(self.matchWithBonusSfx, node=self.cards[card], listenerNode=base.localAvatar, cutOff=240))
        else:
            matchIval.append(SoundInterval(self.matchSfx, node=self.cards[card], listenerNode=base.localAvatar, cutOff=240))
        matchIval.start()
        if len(self.inactiveList) == len(self.cards):
            self.sendUpdate('reportDone')

    def turnUpCard(self, deckOrder):
        self.cards[deckOrder].turnUp()
        self.faceUpList.append(deckOrder)

    def turnDownCard(self, deckOrder):
        self.cards[deckOrder].turnDown()
        if deckOrder in self.faceUpList:
            self.faceUpList.remove(deckOrder)

    def __flipKeyPressed(self):
        if self.inList:
            shortestDistance = 10000
            cardToFlip = -1
            for deckOrder in self.inList:
                dist = base.localAvatar.getDistance(self.cards[deckOrder])
                if dist < shortestDistance:
                    shortestDistance = dist
                    cardToFlip = deckOrder

            deckOrderIndex = cardToFlip
            card = self.cards[deckOrderIndex]
            if card.isFaceDown() and deckOrderIndex not in self.inactiveList:
                self.sendUpdate('openCardRequest', [deckOrderIndex, self.bonusGlowCard])
            elif card.isFaceUp() and deckOrderIndex in self.faceUpList:
                pass

    def moveBonusGlowTask(self, task):
        if len(self.cards) == 0:
            return Task.done
        curT = self.getCurrentGameTime()
        intTime = int(curT / self.bonusGlowTime)
        newIndex = intTime % len(self.cards)
        if not newIndex == self.bonusGlowIndex:
            self.bonusGlowIndex = newIndex
            self.bonusGlowCard = self.bonusTraversal[self.bonusGlowIndex]
            card = self.cards[self.bonusGlowCard]
            self.bonusGlow.setPos(card.getPos())
            base.playSfx(self.bonusMovesSfx, node=card, volume=0.25)
        return Task.cont

    def timerExpired(self):
        self.sendUpdate('reportDone')

    def setDeckSeed(self, deckSeed):
        if not self.hasLocalToon:
            return
        self.deckSeed = deckSeed

    def updateFlipText(self):
        self.flipsLabel['text'] = str(self.flips)
        lowFlipModifier = PairingGameGlobals.calcLowFlipModifier(self.matches, self.flips)
        red = 1.0 - lowFlipModifier
        green = lowFlipModifier
        self.flipsLabel['text_fg'] = Vec4(red, green, 0, 1.0)

    def openCardResult(self, cardToTurnUp, avId, matchingCard, points, cardsToTurnDown):
        if not self.hasLocalToon:
            return
        if not self.isInPlayState():
            return
        if avId == base.localAvatar.doId:
            self.localFaceUpList.append(cardToTurnUp)
        self.turnUpCard(cardToTurnUp)
        gotBonus = False
        if points - self.points > 1:
            gotBonus = True
        if matchingCard > -1:
            self.handleMatch(cardToTurnUp, matchingCard, gotBonus)
        self.flips += 1
        self.updateFlipText()
        self.points = points
        self.pointsLabel['text'] = str(self.points)
        for card in cardsToTurnDown:
            self.turnDownCard(card)

    def startBonusTask(self):
        taskMgr.add(self.moveBonusGlowTask, self.taskName('moveBonusGlowTask'))

    def stopBonusTask(self):
        taskMgr.remove(self.taskName('moveBonusGlowTask'))

    def setEveryoneDone(self):
        if not self.hasLocalToon:
            return
        if self.gameFSM.getCurrentState().getName() != 'play':
            self.notify.warning('ignoring setEveryoneDone msg')
            return
        self.notify.debug('setEveryoneDone')

        def endGame(task, self = self):
            if not PairingGameGlobals.EndlessGame:
                self.gameOver()
            return Task.done

        self.timer.hide()
        self.bonusGlow.hide()
        if len(self.inactiveList) == len(self.cards):
            self.notify.debug('perfect game!')
            perfectTextSubnode = hidden.attachNewNode(self.__genText(TTLocalizer.PairingGamePerfect))
            perfectText = hidden.attachNewNode('perfectText')
            perfectTextSubnode.reparentTo(perfectText)
            frame = self.__textGen.getCardActual()
            offsetY = -abs(frame[2] + frame[3]) / 2.0
            perfectTextSubnode.setPos(0, 0, offsetY)
            perfectText.setColor(1, 0.1, 0.1, 1)

            def fadeFunc(t, text = perfectText):
                text.setColorScale(1, 1, 1, t)

            def destroyText(text = perfectText):
                text.removeNode()

            textTrack = Sequence(Func(perfectText.reparentTo, aspect2d), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=0.3, startScale=0.0), LerpFunctionInterval(fadeFunc, fromData=0.0, toData=1.0, duration=0.5)), Wait(2.0), Parallel(LerpScaleInterval(perfectText, duration=0.5, scale=1.0), LerpFunctionInterval(fadeFunc, fromData=1.0, toData=0.0, duration=0.5, blendType='easeIn')), Func(destroyText), WaitInterval(0.5), Func(endGame, None))
            soundTrack = SoundInterval(self.sndPerfect)
            self.perfectIval = Parallel(textTrack, soundTrack)
            self.perfectIval.start()
        else:
            taskMgr.doMethodLater(1, endGame, self.EndGameTaskName)
        return

    def __genText(self, text):
        self.__textGen.setText(text)
        return self.__textGen.generate()

    def b_setSignaling(self, avId):
        self.setSignaling(avId)
        self.sendUpdate('setSignaling', [self.localAvId])

    def setSignaling(self, avId):
        if not self.hasLocalToon:
            return
        avIndex = self.avIdList.index(avId)
        av = base.cr.doId2do.get(avId)
        if av and avIndex >= 0 and hasattr(self, 'signalSfx') and self.signalSfx:
            base.playSfx(self.signalSfx[avIndex], node=av)

    def __beginSignal(self, mouseParam):
        self.notify.debug('beginSignal')
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)
        taskMgr.doMethodLater(1.67, self.__continueSignal, 'pairGameContinueSignal')

    def __endSignal(self, mouseParam):
        self.notify.debug('endSignal')
        base.localAvatar.b_setEmoteState(-1, 1.0)
        taskMgr.remove('pairGameContinueSignal')

    def __continueSignal(self, task):
        base.localAvatar.b_setEmoteState(1, 1.0)
        self.b_setSignaling(self.localAvId)
        taskMgr.doMethodLater(1.67, self.__continueSignal, 'pairGameContinueSignal')

    def getCardPos(self, deckOrderIndex):
        col = deckOrderIndex % self.cardsPerRow
        row = deckOrderIndex / self.cardsPerRow
        x = col * self.xCardInc
        y = row * self.yCardInc
        return (x, y)

    def getDeckOrderIndex(self, row, col):
        retval = row * self.cardsPerRow
        retval += col
        if retval >= len(self.deck.cards):
            retval = -1
        return retval

    def calcBonusTraversal(self):
        self.bonusTraversal = []
        halfRow = self.cardsPerRow / 2
        if self.cardsPerRow % 2:
            halfRow += 1
        for i in range(halfRow):
            for j in range(2):
                col = i + j * halfRow
                for row in range(self.cardsPerCol):
                    card = self.getDeckOrderIndex(row, col)
                    if card > -1:
                        self.bonusTraversal.append(card)
