import bs
import weakref


def bsGetAPIVersion():
    # see bombsquadgame.com/apichanges
    return 4


def bsGetGames():
    return [KingOfTheHillGame]


class KingOfTheHillGame(bs.TeamGameActivity):

    FLAG_NEW = 0
    FLAG_UNCONTESTED = 1
    FLAG_CONTESTED = 2
    FLAG_HELD = 3

    @classmethod
    def getName(cls):
        return 'King of the Hill'

    @classmethod
    def getDescription(cls, sessionType):
        return 'Secure the flag for a set length of time.'

    @classmethod
    def getScoreInfo(cls):
        return {'scoreName': 'Time Held'}

    @classmethod
    def supportsSessionType(cls, sessionType):
        return True if(
            issubclass(sessionType, bs.TeamsSession)
            or issubclass(sessionType, bs.FreeForAllSession)) else False

    @classmethod
    def getSupportedMaps(cls, sessionType):
        return bs.getMapsSupportingPlayType("kingOfTheHill")

    @classmethod
    def getSettings(cls, sessionType):
        return [("Hold Time", {'minValue': 10, 'default': 30, 'increment': 10}),
                ("Time Limit", {'choices': [('None', 0),
                                            ('1 Minute', 60),
                                            ('2 Minutes', 120),
                                            ('5 Minutes', 300),
                                            ('10 Minutes', 600),
                                            ('20 Minutes', 1200)],
                                'default':0}),
                ("Respawn Times", {'choices': [('Shorter', 0.25),
                                               ('Short', 0.5),
                                               ('Normal', 1.0),
                                               ('Long', 2.0),
                                               ('Longer', 4.0)],
                                   'default':1.0})]

    def __init__(self, settings):
        bs.TeamGameActivity.__init__(self, settings)
        self._scoreBoard = bs.ScoreBoard()
        self._swipSound = bs.getSound("swip")
        self._tickSound = bs.getSound('tick')
        self._countDownSounds = {10: bs.getSound('announceTen'),
                                 9: bs.getSound('announceNine'),
                                 8: bs.getSound('announceEight'),
                                 7: bs.getSound('announceSeven'),
                                 6: bs.getSound('announceSix'),
                                 5: bs.getSound('announceFive'),
                                 4: bs.getSound('announceFour'),
                                 3: bs.getSound('announceThree'),
                                 2: bs.getSound('announceTwo'),
                                 1: bs.getSound('announceOne')}

        self._flagRegionMaterial = bs.Material()
        self._flagRegionMaterial.addActions(
            conditions=("theyHaveMaterial",
                        bs.getSharedObject('playerMaterial')),
            actions=(("modifyPartCollision", "collide", True),
                     ("modifyPartCollision", "physical", False),
                     ("call", "atConnect", bs.Call(
                         self._handlePlayerFlagRegionCollide, 1)),
                     ("call", "atDisconnect", bs.Call(
                         self._handlePlayerFlagRegionCollide, 0))))

    def getInstanceDescription(self):
        return ('Secure the flag for ${ARG1} seconds.',
                self.settings['Hold Time'])

    def getInstanceScoreBoardDescription(self):
        return ('secure the flag for ${ARG1} seconds',
                self.settings['Hold Time'])

    def onTransitionIn(self):
        bs.TeamGameActivity.onTransitionIn(self, music='Scary')

    def onTeamJoin(self, team):
        team.gameData['timeRemaining'] = self.settings["Hold Time"]
        self._updateScoreBoard()

    def onPlayerJoin(self, player):
        bs.TeamGameActivity.onPlayerJoin(self, player)
        player.gameData['atFlag'] = 0

    def onBegin(self):
        bs.TeamGameActivity.onBegin(self)
        self.setupStandardTimeLimit(self.settings['Time Limit'])
        self.setupStandardPowerupDrops()
        self._flagPos = self.getMap().getFlagPosition(None)
        bs.gameTimer(1000, self._tick, repeat=True)
        self._flagState = self.FLAG_NEW
        self.projectFlagStand(self._flagPos)

        self._flag = bs.Flag(position=self._flagPos,
                             touchable=False,
                             color=(1, 1, 1))
        self._flagLight = bs.newNode('light',
                                     attrs={'position': self._flagPos,
                                            'intensity': 0.2,
                                            'heightAttenuated': False,
                                            'radius': 0.4,
                                            'color': (0.2, 0.2, 0.2)})

        # flag region
        bs.newNode('region',
                   attrs={'position': self._flagPos,
                          'scale': (1.8, 1.8, 1.8),
                          'type': 'sphere',
                          'materials': [self._flagRegionMaterial,
                                        bs.getSharedObject('regionMaterial')]})
        self._updateFlagState()

    def _tick(self):
        self._updateFlagState()

        # give holding players points
        for player in self.players:
            if player.gameData['atFlag'] > 0:
                self.scoreSet.playerScored(
                    player, 3, screenMessage=False, display=False)

        scoringTeam = None if self._scoringTeam is None else self._scoringTeam()
        if scoringTeam:

            if scoringTeam.gameData['timeRemaining'] > 0:
                bs.playSound(self._tickSound)

            scoringTeam.gameData['timeRemaining'] = max(
                0, scoringTeam.gameData['timeRemaining']-1)
            self._updateScoreBoard()
            if scoringTeam.gameData['timeRemaining'] > 0:
                self._flag.setScoreText(
                    str(scoringTeam.gameData['timeRemaining']))

            # announce numbers we have sounds for
            try:
                bs.playSound(
                    self._countDownSounds
                    [scoringTeam.gameData['timeRemaining']])
            except Exception:
                pass

            # winner
            if scoringTeam.gameData['timeRemaining'] <= 0:
                self.endGame()

    def endGame(self):
        results = bs.TeamGameResults()
        for team in self.teams:
            results.setTeamScore(
                team, self.settings['Hold Time'] - team.gameData
                ['timeRemaining'])
        self.end(results=results, announceDelay=0)

    def _updateFlagState(self):
        holdingTeams = set(player.getTeam()
                           for player in self.players
                           if player.gameData['atFlag'])
        prevState = self._flagState
        if len(holdingTeams) > 1:
            self._flagState = self.FLAG_CONTESTED
            self._scoringTeam = None
            self._flagLight.color = (0.6, 0.6, 0.1)
            self._flag.node.color = (1.0, 1.0, 0.4)
        elif len(holdingTeams) == 1:
            holdingTeam = list(holdingTeams)[0]
            self._flagState = self.FLAG_HELD
            self._scoringTeam = weakref.ref(holdingTeam)
            self._flagLight.color = bs.getNormalizedColor(holdingTeam.color)
            self._flag.node.color = holdingTeam.color
        else:
            self._flagState = self.FLAG_UNCONTESTED
            self._scoringTeam = None
            self._flagLight.color = (0.2, 0.2, 0.2)
            self._flag.node.color = (1, 1, 1)
        if self._flagState != prevState:
            bs.playSound(self._swipSound)

    def _handlePlayerFlagRegionCollide(self, colliding):
        flagNode, playerNode = bs.getCollisionInfo("sourceNode", "opposingNode")
        try:
            player = playerNode.getDelegate().getPlayer()
        except Exception:
            return

        # different parts of us can collide so a single value isn't enough
        # also don't count it if we're dead (flying heads shouldnt be able to
        # win the game :-)
        if colliding and player.isAlive():
            player.gameData['atFlag'] += 1
        else:
            player.gameData['atFlag'] = max(0, player.gameData['atFlag'] - 1)

        self._updateFlagState()

    def _updateScoreBoard(self):
        for team in self.teams:
            self._scoreBoard.setTeamValue(
                team, team.gameData['timeRemaining'],
                self.settings['Hold Time'],
                countdown=True)

    def handleMessage(self, m):
        if isinstance(m, bs.PlayerSpazDeathMessage):
            bs.TeamGameActivity.handleMessage(self, m)  # augment default

            # no longer can count as atFlag once dead
            player = m.spaz.getPlayer()
            player.gameData['atFlag'] = 0
            self._updateFlagState()
            self.respawnPlayer(player)
