import Actor
import Message
import Entity
import Sequence
import re
import threading
import Combat
import Map
import TreasureHunt
import Forgemagie
import Elements
import House
import Effect
import AuctionHouse
import Player
import Fight
import Spell
import Inventory
import Chinq
import json
import os
import sys
import utils
from Log import LOG, obj_to_string
from Reader import reader
from Options import options
from playsound import playsound
import Server

DATA_SESSION_SMITH = [168, 118, 113, 351, 164, 166]

class Env():
    def __init__(self, window_name, player=None):
        self.window = window_name
        self.server = Server.Server()
        if player == None:
            self.player = Player.Player(self.server)
        else:
            self.player = player
        self.server.update_player(self.player)
        self.combat = None
        self.fm = None
        self.chinq = None
        self.log_chat = False
        self.map = Map.Map()
        self.auction_house = AuctionHouse.AuctionHouse(self.player, self.server)
        self.current_sequence = None
        self.allow_one_spell = False
        self.current_trade_session = None
        self.hint = None
        self.waiting_for_chall_data = False
        self.start_looking_movement = False
        self.start_looking_sequence = False
        self.potential_user_id = []
        self.inventory = None
        self.i = 0
        self.chat_log = False
        if self.player.server_id is None:
            LOG.error("Running in incognito mode until command /whoami")

    def parse_abstract_game_action_message(self):
        action_id = reader.readVarUhShort()
        source_id = reader.readDouble()
        return source_id, action_id

    def dispatch_packet(self, message):
        reader.load_message(message.data, message.full_message)
        message.print_infos()

        self.current_trade_session == "FM"
        if message.protocol == "HouseProperties":
            house = House.HouseProperties()
            house.details = self.map.add_infos_to_house(house.house_id)
            if house.properties.is_sale_locked == True and house.properties.owner == '?':
                LOG.info(f"{self.map.name} [{self.map.x},{self.map.y}], {house.details.name}")
            #if house.properties.is_sale_locked == False and house.properties.owner == '?':
                #if house.properties.price != 0:
                    #playsound(f'{os.path.dirname(__file__)}/sounds/{options.notification}.mp3', block=False)
                    #LOG.error(f'{self.window} got {message.protocol}')
                #else:
                    #if self.map is not None:
                        #data = {
                            #"pos_x": self.map.x,
                            #"pos_y": self.map.y,
                            #"map_name": self.map.name,
                            #'house_type': house.details.name,
                        #}
                        #self.server.send_to_server(data, "houses/register_house/")

        if self.player.user_id == None and message.protocol == "GameActionFightCastRequest":
            self.start_looking_sequence = True
            self.potential_user_id = []

        if  self.player.user_id == None and message.protocol == "GameMapMovementRequest":
            self.start_looking_movement = True
            self.potential_user_id = []

        if self.start_looking_movement == True and message.protocol == "GameMapMovementConfirm" or message.protocol == "GameActionAcknowledgement":
            self.start_looking_movement = False
            self.start_looking_sequence = False
            if len(self.potential_user_id) == 1:
                LOG.debug(f"Found user_id {self.potential_user_id[0]}")
                self.player.user_id = self.potential_user_id[0]
                ret = Server.threaded_send_to_server("", f"player/{self.player.user_id}")
                if ret.status_code == 200:
                    data = ret.json()
                    self.player.user_id = data['user_id']
                    self.player.username = data['username']
                    self.player.server_id = data['server_id']
                    self.player.server = data['server']
                    self.player.infos_saved = True
                    self.server.update_player(self.player)
                    LOG.info(f"Loaded character {self.player.username}")
            self.potential_user_id = []

        if self.start_looking_movement == True and message.protocol == "GameMapMovement":
            length = reader.readUnsignedShort()
            for _ in range(0, length):
                reader.readShort()
            reader.readShort()
            uid = reader.readDouble()
            if uid > 0:
                self.potential_user_id.append(uid)

        if message.protocol == "BasicWhoIs":
            char = Player.BasicWhoIs()
            if char.me == True:
                self.player.user_id = char.player_id
                self.player.username = char.player_name
                self.player.server_id = char.server_id
                self.player.server = char.server_name
                self.player.send_to_server()
                self.server.update_player(self.player)
                LOG.info(f"Loaded character {self.player.username}")

        if message.protocol == "ExchangeTypesItemsExchangerDescriptionForUser":
            self.auction_house.add_item(self.window)

        if message.protocol == "CurrentMap":
            self.map.parse_map()

        if message.protocol == "GameFightSpectatorJoin":
            self.combat.spectating = True

        if message.protocol == "MapComplementaryInformationsData":
            self.map.parse_map_complementary_infos()
            ok = self.map.check_looking_for(self.hint.looking_for_npc if self.hint != None else None)
            self.map.print_infos()
            if self.hint != None:
                self.hint.to_html(self.map, ok)

        if message.protocol == "MapFightCount":
            pass

        if message.protocol == "InteractiveElementUpdated":
            element_upd = Elements.InteractiveElementUpdated()
            if self.map is not None:
                self.map.handle_interactive_element(element_upd.element)

        if message.protocol == "TreasureHunt":
            LOG.debug("New Treasure Hunt hint")
            self.hint = TreasureHunt.Hint()
            self.hint.parse_message()
            end = False
            e = self.hint.solve_step()
            if e == "ok":
                end = True
            if self.map != None and e != -1:
                self.hint.to_html(self.map, end)

        if message.protocol == "GameFightNewRound":
            if self.combat is None:
                return
            self.combat.new_round()

        if message.protocol == "TreasureHuntFinished":
            LOG.info("Completed hunt")
            if self.map != None:
                self.hint.to_html(self.map, end=True)

        if message.protocol == "GameFightStarting":
            LOG.info("Combat Start")
            self.combat = Combat.Combat().combat_start()

        if message.protocol == "GameFightEnd":
            fight_recap = Fight.FightEnd(self.map)
            if self.combat is not None:
                fight_recap.combat_parse = self.combat.combat_parse
                for player in fight_recap.results:
                    player.name = self.combat.entities[player.id].name
                    if hasattr(self.combat.entities[player.id].actor.entity, 'creature_generic_id'):
                        player.gid = self.combat.entities[player.id].actor.entity.creature_generic_id
                        player.sex = 0
                        player.breed_id = 0
                    else:
                        player.gid = -1
                        player.sex = self.combat.entities[player.id].actor.entity.sex
                        player.breed_id = self.combat.entities[player.id].actor.entity.breed
                    player.team = self.combat.entities[player.id].team
                    player.level = self.combat.entities[player.id].actor.entity.capped_level
                    fight_recap.med_level[player.team] += self.combat.entities[player.id].actor.entity.capped_level
                    fight_recap.nb_players[player.team] += 1
                    player.alive = self.combat.entities[player.id].alive
                    player.damage = self.combat.entities[player.id].get_damage()
                self.combat = None
                fight_recap.med_level[0] = int(fight_recap.med_level[0] / fight_recap.nb_players[0])
                fight_recap.med_level[1] = int(fight_recap.med_level[1] / fight_recap.nb_players[1])
                fight_recap.generate_data()
                LOG.info("Combat End")
                self.server.send_to_server(fight_recap.data, "combat/new", server_req=False)

        if message.protocol == "GameFightLeave":
            if self.combat is not None:
                self.combat.combat_end()
                self.combat = None
            LOG.info("Left Combat")

        if message.protocol == "GameFightTurnList":
            if self.combat is not None:
                self.combat.show_fighters()

        if message.protocol == "GameActionFightSummon":
            if self.combat is None:
                return
            actors, source_id = Actor.parse_fighter_summon()
            if actors != []:
                for a in actors:
                    if a.contextual_id == 0:
                        a.contextual_id = self.combat.get_latest_id()
                    e = Entity.Entity()
                    if source_id in self.combat.entities:
                        e.load_actor(a, self.combat.entities[source_id])
                        self.combat.add_entity(e, summoned=True)
                        self.combat.entities[source_id].summons.append(e)
                        LOG.info(f"New fighter - {e.name} - Summoned by {self.combat.entities[source_id].name}")

        if message.protocol == "GameActionFightMultipleSummon":
            if self.combat is None:
                return
            actors, source_id = Actor.parse_summon()
            if actors != []:
                for a in actors:
                    if a.entity.contextual_id == 0:
                        a.entity.contextual_id = self.combat.get_latest_id()
                    e = Entity.Entity()
                    if source_id in self.combat.entities:
                        e.load_actor(a, self.combat.entities[source_id])
                        self.combat.add_entity(e, summoned=True)
                        self.combat.entities[source_id].summons.append(e)
                        LOG.info(f"New fighter - {e.name} - Summoned by {self.combat.entities[source_id].name}")

        if message.protocol == "GameActionFightDeath":
            if self.combat is None:
                return
            self.combat.entity_death()

        if message.protocol == "GameFightShowFighter":
            if self.combat is None:
                return
            a = Actor.parse_actor()
            if a is not None:
                e = Entity.Entity()
                e.load_actor(a)
                self.combat.add_entity(e)

        if message.protocol == "GameFightSynchronize":
           if self.combat is None:
               self.combat = Combat.Combat().combat_start()
           self.combat.GameFightSynchronize()

        if message.protocol == "GameActionFightSpellCast":
            if self.combat is None:
                return
            if self.allow_one_spell == False:
                return
            source_id, action_id = self.parse_abstract_game_action_message()
            if source_id not in self.combat.entities:
                self.combat.entities[source_id] = Entity.Entity()
            entity = self.combat.entities[source_id]
            ok = self.combat.parse_spell(entity)
            if ok == True:
                entity.set_active_spell()
                self.allow_one_spell = False

        if message.protocol == "GameActionFightCloseCombat":
            if self.combat is None:
                return
            if self.allow_one_spell == False:
                return
            source_id, action_id = self.parse_abstract_game_action_message()
            if source_id not in self.combat.entities:
                self.combat.entities[source_id] = Entity.Entity()
            entity = self.combat.entities[source_id]
            ok = self.combat.parse_attack(entity)
            if ok == True:
                entity.set_active_spell()
                self.allow_one_spell = False

        if message.protocol == "GameActionFightLifePointsLost":
            if self.combat is None:
                return
            source_id, action_id = self.parse_abstract_game_action_message()
            if source_id in self.combat.entities:
                entity = self.combat.entities[source_id]
            else:
                entity = self.combat.entities[source_id] = Entity.Entity()
            if self.current_sequence is None or self.current_sequence.author_id != source_id:
                latent = True
            else:
                latent = False
            curr = entity.parse_damage(latent)
            LOG.info(f"{self.combat.entities[curr.damage[-1]['target_id']].name}: -{curr.damage[-1]['loss']} PV ({curr.damage[-1]['element_name']})")
            self.combat.combat_report()

        if message.protocol == "GameActionFightDispellEffect":
            if self.combat is None:
                return
            self.combat.dispell_single_effect()

        if message.protocol == "GameActionFightModifyEffectsDuration":
            if self.combat is None:
                return
            self.combat.dispell_effects()

        if message.protocol == "GameFightSpectate":
            if self.combat is None:
                return
            self.combat.combat_spectate_init()

        if message.protocol == "GameActionFightLifeAndShieldPointsLost":
            if self.combat is None:
                return
            source_id, action_id = self.parse_abstract_game_action_message()
            if source_id in self.combat.entities:
                entity = self.combat.entities[source_id]
            else:
                entity = self.combat.entities[source_id] = Entity.Entity()
            if self.current_sequence is None or self.current_sequence.author_id != source_id:
                latent = True
            else:
                latent = False
            curr = entity.parse_damage(latent, shield=True)
            LOG.info(f"{self.combat.entities[curr.damage[-1]['target_id']].name}: -{curr.damage[-1]['loss']} PV ({curr.damage[-1]['element_name']})")
            self.combat.combat_report()

        if message.protocol == "SequenceStart":
            self.current_sequence = Sequence.Sequence().parse_sequence()
            if self.combat is not None:
                self.combat.set_sequence(self.current_sequence)
            if self.current_sequence.type == 1 or self.current_sequence.type == 2:
                self.allow_one_spell = True
            if self.start_looking_sequence == True:
                self.start_looking_sequence = False
                self.start_looking_movement = False
                self.player.user_id = self.current_sequence.author_id
                ret = Server.threaded_send_to_server("", f"player/{self.player.user_id}")
                if ret.status_code == 200:
                    data = ret.json()
                    self.player.user_id = data['user_id']
                    self.player.username = data['username']
                    self.player.server_id = data['server_id']
                    self.player.server = data['server']
                    self.player.infos_saved = True
                    self.server.update_player(self.player)
                    LOG.info(f"Loaded character {self.player.username}")

        if message.protocol == "ChatServer" or message.protocol == "ChatServerWithObject":
            channel = reader.readByte()
            val = reader.readString()
            timestamp = reader.readInt()
            fingerprint = reader.readString()
            sender_id = reader.readDouble()
            sender = reader.readString()
            if "beatbox" in val:
                playsound(f'{os.path.dirname(__file__)}/sounds/link_beatbox.mp3', block=False)
            if "start_log" in val:
                self.chat_log = True
            if self.chat_log == True:
                data = {
                    "channel": channel,
                    "message": val,
                    "sender": sender
                }
                self.server.send_to_server(data, "chat_log/add")

        if message.protocol == "SequenceEnd":
            self.current_sequence = None
            if self.combat and self.combat.playing_entity is not None:
                self.combat.playing_entity.sequence_end()

        if message.protocol == "GameFightTurnStart":
            if self.combat is not None:
                self.combat.turn_set(reader.readDouble())

       # if message.protocol == "GameFightTurnStartPlaying":
        #    if self.combat is not None:
         #       self.combat.turn_set()

        if message.protocol == "FighterStatsList":
            if self.combat is not None:
                self.combat.playing_entity.update_stats()

        if message.protocol == "GameFightTurnEnd":
            if self.combat is not None:
                self.combat.turn_end(reader.readDouble())

        if message.protocol == "GameActionFightDispellableEffect":
            effect = Effect.CombatEffect()
            if self.combat is not None:
                self.combat.add_effect(effect.effect)

        if message.protocol == "GameActionFightPointsVariation":
            if self.combat is not None and self.combat.playing_entity is not None:
                self.combat.parse_points_variation()

        if message.protocol == "StorageInventoryContent":
            self.inventory = Inventory.InventoryContent()
            self.inventory.export()
            print ("")

        if message.protocol == "TextInformation":
            message_type = reader.readByte()
            message_id = reader.readVarUhShort()
            if message_id == 188: #CHALL_FAILED_MESSAGE
                data = []
                for _ in range(0, reader.readUnsignedShort()):
                    data.append(reader.readString())
                if self.combat is not None:
                    for uid, user in self.combat.entities.items():
                        if user.name == data[0]:
                            jdata = {
                                "challenge_id": data[1]
                            }
                            self.server.send_to_server(jdata, "challenge_failed/add", username=user.name, user_id=uid)
                self.waiting_for_chall_data = False

        if message.protocol == "ExchangeStartOkCraftWithInformation":
            session = reader.readVarUhInt()
            if session in DATA_SESSION_SMITH:
                LOG.info("Starting FM session..")
                self.current_trade_session = "FM"
                self.fm = Forgemagie.Forgemagie(self.server)
            else:
            #if session == 397: #SKILL_CHINQ
                LOG.info("Starting Chinq session..")
                self.current_trade_session = "CHINQ"
                self.chinq = Chinq.Chinq(self.server)

        if message.protocol == "ExchangeCraftResultMagicWithObjectDesc":
            if self.current_trade_session == "FM":
                if self.fm is not None:
                    self.fm.apply_fm()

        if message.protocol == "ExchangeCraftResultWithObjectDesc":
            if self.current_trade_session == "CHINQ":
                if self.chinq is not None:
                    self.chinq.add_result()

        if message.protocol == "ExchangeObjectAdded":
            if self.current_trade_session == "FM":
                if self.fm is None:
                    self.fm = Forgemagie.Forgemagie(self.server)
                self.fm.load_item()
            elif self.current_trade_session == "CHINQ":
                self.chinq.add_card()

        if message.protocol == "ExchangeObjectRemoved":
            if self.current_trade_session == "FM":
                if self.fm is None:
                    self.fm.unload_item()
            elif self.current_trade_session == "CHINQ":
                self.chinq.remove_card()

        if message.protocol == "ExchangeLeave":
            self.current_trade_session = None
            self.fm = None
            self.chinq = None
