from bemani.backend.museca.base import MusecaBase
from bemani.common import ID
from bemani.protocol import Node


class MusecaGameShopHandler(MusecaBase):

    def handle_game_3_shop_request(self, request: Node) -> Node:
        self.update_machine_name(request.child_value('shopname'))

        # Respond with number of milliseconds until next request
        game = Node.void('game_3')
        game.add_child(Node.u32('nxt_time', 1000 * 5 * 60))
        return game


class MusecaGameHiscoreHandler(MusecaBase):

    def handle_game_3_hiscore_request(self, request: Node) -> Node:
        # Grab location for local scores
        locid = ID.parse_machine_id(request.child_value('locid'))

        # Start the response packet
        game = Node.void('game_3')

        # First, grab hit chart
        playcounts = self.data.local.music.get_hit_chart(self.game, self.version, 1024)

        hitchart = Node.void('hitchart')
        game.add_child(hitchart)
        for (songid, count) in playcounts:
            info = Node.void('info')
            hitchart.add_child(info)
            info.add_child(Node.u32('id', songid))
            info.add_child(Node.u32('cnt', count))

        # Now, grab user records
        records = self.data.remote.music.get_all_records(self.game, self.version)
        users = {
            uid: prof for (uid, prof) in
            self.get_any_profiles([r[0] for r in records])
        }

        hiscore_allover = Node.void('hiscore_allover')
        game.add_child(hiscore_allover)

        # Output records
        for (userid, score) in records:
            info = Node.void('info')

            if userid not in users:
                raise Exception('Logic error, could not find profile for user!')
            profile = users[userid]

            info.add_child(Node.u32('id', score.id))
            info.add_child(Node.u32('type', score.chart))
            info.add_child(Node.string('name', profile.get_str('name')))
            info.add_child(Node.string('seq', ID.format_extid(profile.extid)))
            info.add_child(Node.u32('score', score.points))

            # Add to global scores
            hiscore_allover.add_child(info)

        # Now, grab local records
        area_users = [
            uid for (uid, prof) in self.data.local.user.get_all_profiles(self.game, self.version)
            if prof.get_int('loc', -1) == locid
        ]
        records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users)
        missing_players = [
            uid for (uid, _) in records
            if uid not in users
        ]
        for (uid, prof) in self.get_any_profiles(missing_players):
            users[uid] = prof

        hiscore_location = Node.void('hiscore_location')
        game.add_child(hiscore_location)

        # Output records
        for (userid, score) in records:
            info = Node.void('info')

            if userid not in users:
                raise Exception('Logic error, could not find profile for user!')
            profile = users[userid]

            info.add_child(Node.u32('id', score.id))
            info.add_child(Node.u32('type', score.chart))
            info.add_child(Node.string('name', profile.get_str('name')))
            info.add_child(Node.string('seq', ID.format_extid(profile.extid)))
            info.add_child(Node.u32('score', score.points))

            # Add to global scores
            hiscore_location.add_child(info)

        # Now, grab clear rates
        clear_rate = Node.void('clear_rate')
        game.add_child(clear_rate)

        clears = self.get_clear_rates()
        for songid in clears:
            for chart in clears[songid]:
                if clears[songid][chart]['total'] > 0:
                    rate = float(clears[songid][chart]['clears']) / float(clears[songid][chart]['total'])
                    dnode = Node.void('d')
                    clear_rate.add_child(dnode)
                    dnode.add_child(Node.u32('id', songid))
                    dnode.add_child(Node.u32('type', chart))
                    dnode.add_child(Node.s16('cr', int(rate * 10000)))

        return game


class MusecaGameFrozenHandler(MusecaBase):

    def handle_game_3_frozen_request(self, request: Node) -> Node:
        game = Node.void('game_3')
        game.add_child(Node.u8('result', 0))
        return game


class MusecaGameNewHandler(MusecaBase):

    def handle_game_3_new_request(self, request: Node) -> Node:
        refid = request.child_value('refid')
        name = request.child_value('name')
        loc = ID.parse_machine_id(request.child_value('locid'))
        self.new_profile_by_refid(refid, name, loc)

        root = Node.void('game_3')
        return root


class MusecaGameSaveMusicHandler(MusecaBase):

    def handle_game_3_save_m_request(self, request: Node) -> Node:
        refid = request.child_value('refid')

        if refid is not None:
            userid = self.data.remote.user.from_refid(self.game, self.version, refid)
        else:
            userid = None

        # Doesn't matter if userid is None here, that's an anonymous score
        musicid = request.child_value('music_id')
        chart = request.child_value('music_type')
        points = request.child_value('score')
        combo = request.child_value('max_chain')
        clear_type = self.game_to_db_clear_type(request.child_value('clear_type'))
        grade = self.game_to_db_grade(request.child_value('score_grade'))
        stats = {
            'btn_rate': request.child_value('btn_rate'),
            'long_rate': request.child_value('long_rate'),
            'vol_rate': request.child_value('vol_rate'),
            'critical': request.child_value('critical'),
            'near': request.child_value('near'),
            'error': request.child_value('error'),
        }

        # Save the score
        self.update_score(
            userid,
            musicid,
            chart,
            points,
            clear_type,
            grade,
            combo,
            stats,
        )

        # Return a blank response
        return Node.void('game_3')


class MusecaGamePlayEndHandler(MusecaBase):

    def handle_game_3_play_e_request(self, request: Node) -> Node:
        return Node.void('game_3')


class MusecaGameSaveHandler(MusecaBase):

    def handle_game_3_save_request(self, request: Node) -> Node:
        refid = request.child_value('refid')

        if refid is not None:
            userid = self.data.remote.user.from_refid(self.game, self.version, refid)
        else:
            userid = None

        if userid is not None:
            oldprofile = self.get_profile(userid)
            newprofile = self.unformat_profile(userid, request, oldprofile)
        else:
            newprofile = None

        if userid is not None and newprofile is not None:
            self.put_profile(userid, newprofile)

        return Node.void('game_3')
