# • ▌ ▄ ·. ▄▄▄▄·     ▄▄▄ .·▄▄▄▄  ▪  ▄▄▄▄▄      ▄▄▄       ▄▄▄·▄▄▄        
# ·██ ▐███▪▐█ ▀█▪    ▀▄.▀·██▪ ██ ██ •██  ▪     ▀▄ █·    ▐█ ▄█▀▄ █·▪     
# ▐█ ▌▐▌▐█·▐█▀▀█▄    ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄      ██▀·▐▀▀▄  ▄█▀▄ 
# ██ ██▌▐█▌██▄▪▐█    ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌    ▐█▪·•▐█•█▌▐█▌.▐▌
# ▀▀  █▪▀▀▀·▀▀▀▀      ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀  ▀█▄▀▪.▀  ▀    .▀   .▀  ▀ ▀█▄▀▪
#               Magicbane Emulator Project © 2013 - 2022
#                          www.magicbane.com

from collections import OrderedDict

from arcane.enums.arc_item import *
from arcane.enums.hashes import hash_to_string, string_to_hash
from arcane.util import ResStream
from .ArcCombatObj import ArcCombatObj
from .common.Arcane import AttributeValue, SkillLevel, PowerGrant, PowerAction, ResourceInfo
from .common.Class import ClassRequired
from .common.Discipline import DiscRequired
from .common.Race import RaceRequired


class Damage:
    def load_binary(self, stream: ResStream):
        self.damage_type = stream.read_dword()
        self.damage_min = stream.read_dword()
        self.damage_max = stream.read_dword()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.damage_type)
        stream.write_dword(self.damage_min)
        stream.write_dword(self.damage_max)

    def load_json(self, data):
        self.damage_type = STRING_TO_DAMAGE[data['damage_type']]
        self.damage_min = data['damage_min']
        self.damage_max = data['damage_max']

    def save_json(self):
        data = OrderedDict()
        data['damage_type'] = DAMAGE_TO_STRING[self.damage_type]
        data['damage_min'] = self.damage_min
        data['damage_max'] = self.damage_max
        return data


class Offerings:
    def load_binary(self, stream: ResStream):
        self.offering_type = stream.read_string()
        self.offering_value = stream.read_dword()

    def save_binary(self, stream: ResStream):
        stream.write_string(self.offering_type)
        stream.write_dword(self.offering_value)

    def load_json(self, data):
        self.offering_type = data['offering_type']
        self.offering_value = data['offering_value']

    def save_json(self):
        data = OrderedDict()
        data['offering_type'] = self.offering_type
        data['offering_value'] = self.offering_value
        return data


class Weapon:
    def load_binary(self, stream: ResStream):
        self.weapon_wepspeed = stream.read_float()
        self.weapon_max_range = stream.read_float()
        self.weapon_projectile_id = stream.read_qword()
        self.weapon_projectile_speed = stream.read_float()
        self.weapon_combat_idle_anim = stream.read_dword()
        num_damages = stream.read_dword()
        self.weapon_damage = [Damage() for _ in range(num_damages)]
        for damage in self.weapon_damage:
            damage.load_binary(stream)
        num_right_anims = stream.read_dword()
        self.weapon_attack_anim_right = [
            [
                stream.read_dword(),
                stream.read_dword(),
            ] for _ in range(num_right_anims)
        ]
        num_left_anims = stream.read_dword()
        self.weapon_attack_anim_left = [
            [
                stream.read_dword(),
                stream.read_dword(),
            ] for _ in range(num_left_anims)
        ]

    def save_binary(self, stream: ResStream):
        stream.write_float(self.weapon_wepspeed)
        stream.write_float(self.weapon_max_range)
        stream.write_qword(self.weapon_projectile_id)
        stream.write_float(self.weapon_projectile_speed)
        stream.write_dword(self.weapon_combat_idle_anim)
        stream.write_dword(len(self.weapon_damage))
        for damage in self.weapon_damage:
            damage.save_binary(stream)
        stream.write_dword(len(self.weapon_attack_anim_right))
        for anim in self.weapon_attack_anim_right:
            stream.write_dword(anim[0])
            stream.write_dword(anim[1])
        stream.write_dword(len(self.weapon_attack_anim_left))
        for anim in self.weapon_attack_anim_left:
            stream.write_dword(anim[0])
            stream.write_dword(anim[1])

    def load_json(self, data):
        self.weapon_wepspeed = data['weapon_wepspeed']
        self.weapon_max_range = data['weapon_max_range']
        self.weapon_projectile_id = data['weapon_projectile_id']
        self.weapon_projectile_speed = data['weapon_projectile_speed']
        self.weapon_combat_idle_anim = data['weapon_combat_idle_anim']
        self.weapon_damage = []
        for damage_data in data['weapon_damage']:
            damage = Damage()
            damage.load_json(damage_data)
            self.weapon_damage.append(damage)
        self.weapon_attack_anim_right = data['weapon_attack_anim_right']
        self.weapon_attack_anim_left = data['weapon_attack_anim_left']

    def save_json(self):
        data = OrderedDict()
        data['weapon_wepspeed'] = self.weapon_wepspeed
        data['weapon_max_range'] = self.weapon_max_range
        data['weapon_projectile_id'] = self.weapon_projectile_id
        data['weapon_projectile_speed'] = self.weapon_projectile_speed
        data['weapon_combat_idle_anim'] = self.weapon_combat_idle_anim
        data['weapon_damage'] = []
        for damage in self.weapon_damage:
            data['weapon_damage'].append(damage.save_json())
        data['weapon_attack_anim_right'] = self.weapon_attack_anim_right
        data['weapon_attack_anim_left'] = self.weapon_attack_anim_left
        return data


class ArcItem(ArcCombatObj):
    def load_binary(self, stream: ResStream):
        super().load_binary(stream)
        self.item_type = stream.read_dword()
        self.item_eq_slots_value = stream.read_dword()
        self.item_eq_slots_or = stream.read_dword()
        self.item_eq_slots_and = stream.read_dword()
        self.item_eq_slots_type = stream.read_bool()
        self.item_takeable = stream.read_bool()
        self.item_value = stream.read_dword()
        self.item_wt = stream.read_dword()
        self.item_passive_defense_mod = stream.read_float()
        self.item_base_name = stream.read_string()
        self.item_dsc = stream.read_string()
        self.item_render_object_female = stream.read_qword()
        self.item_health_full = stream.read_float()
        self.item_skill_used = stream.read_dword()
        self.item_skill_mastery_used = stream.read_dword()
        self.item_parry_anim_id = stream.read_dword()
        num_adjustments = stream.read_dword()
        self.item_offering_info = [Offerings() for _ in range(num_adjustments)]
        for offering in self.item_offering_info:
            offering.load_binary(stream)

        if self.item_type == ITEM_TYPE_WEAPON:
            self.item_weapon = Weapon()
            self.item_weapon.load_binary(stream)
            self.item_primary_attr = stream.read_dword()
            self.item_secondary_attr = stream.read_dword()

        if self.item_type == ITEM_TYPE_ARMOR:
            self.item_bulk_factor = stream.read_float()
            self.item_defense_rating = stream.read_dword()
        self.item_flags = stream.read_dword()
        self.item_use_flags = stream.read_dword()
        self.item_post_item_id = stream.read_qword()
        self.item_initial_charges = stream.read_dword()
        self.item_book_arcana = stream.read_dword()
        num_skills = stream.read_dword()
        self.item_skill_req = [SkillLevel() for _ in range(num_skills)]
        for skill in self.item_skill_req:
            skill.load_binary(stream)
        self.item_race_req = RaceRequired()
        self.item_race_req.load_binary(stream)
        self.item_class_req = ClassRequired()
        self.item_class_req.load_binary(stream)
        self.item_disc_req = DiscRequired()
        self.item_disc_req.load_binary(stream)
        num_attrs = stream.read_dword()
        self.item_attr_req = [AttributeValue() for _ in range(num_attrs)]
        for attr in self.item_attr_req:
            attr.load_binary(stream)
        self.item_level_req = stream.read_dword()
        self.item_rank_req = stream.read_dword()
        self.item_sex_req = stream.read_dword()
        num_user_actions = stream.read_dword()
        self.item_user_power_action = [
            [
                stream.read_dword(),
                stream.read_dword(),
                stream.read_dword(),
            ] for _ in range(num_user_actions)
        ]
        num_power_grants = stream.read_dword()
        self.item_power_grant = [PowerGrant() for _ in range(num_power_grants)]
        for action in self.item_power_grant:
            action.load_binary(stream)
        num_power_actions = stream.read_dword()
        self.item_power_action = [PowerAction() for _ in range(num_power_actions)]
        for grant in self.item_power_action:
            grant.load_binary(stream)

        self.item_sheathable = stream.read_bool()
        if self.item_sheathable:
            self.item_sheath_slot = stream.read_dword()
            self.item_sheath_offset = stream.read_tuple()
            self.item_sheath_rotation = stream.read_tuple()

        self.item_has_stub = stream.read_bool()
        if self.item_has_stub:
            self.item_stub_holder = [
                stream.read_bool(),
                stream.read_qword(),
            ]
        num_adjustments = stream.read_dword()
        self.item_offering_adjustments = [Offerings() for _ in range(num_adjustments)]
        for offering in self.item_offering_adjustments:
            offering.load_binary(stream)
        num_costs = stream.read_dword()
        self.item_resource_costs = [ResourceInfo() for _ in range(num_costs)]
        for res in self.item_resource_costs:
            res.load_binary(stream)
        self.item_bane_rank = stream.read_dword()
        self.item_ignore_saved_actions = stream.read_bool()

    def save_binary(self, stream: ResStream):
        super().save_binary(stream)
        stream.write_dword(self.item_type)
        stream.write_dword(self.item_eq_slots_value)
        stream.write_dword(self.item_eq_slots_or)
        stream.write_dword(self.item_eq_slots_and)
        stream.write_bool(self.item_eq_slots_type)
        stream.write_bool(self.item_takeable)
        stream.write_dword(self.item_value)
        stream.write_dword(self.item_wt)
        stream.write_float(self.item_passive_defense_mod)
        stream.write_string(self.item_base_name)
        stream.write_string(self.item_dsc)
        stream.write_qword(self.item_render_object_female)
        stream.write_float(self.item_health_full)
        stream.write_dword(self.item_skill_used)
        stream.write_dword(self.item_skill_mastery_used)
        stream.write_dword(self.item_parry_anim_id)
        stream.write_dword(len(self.item_offering_info))
        for offering in self.item_offering_info:
            offering.save_binary(stream)
        if self.item_type == ITEM_TYPE_WEAPON:
            self.item_weapon.save_binary(stream)
            stream.write_dword(self.item_primary_attr)
            stream.write_dword(self.item_secondary_attr)
        if self.item_type == ITEM_TYPE_ARMOR:
            stream.write_float(self.item_bulk_factor)
            stream.write_dword(self.item_defense_rating)
        stream.write_dword(self.item_flags)
        stream.write_dword(self.item_use_flags)
        stream.write_qword(self.item_post_item_id)
        stream.write_dword(self.item_initial_charges)
        stream.write_dword(self.item_book_arcana)
        stream.write_dword(len(self.item_skill_req))
        for skill in self.item_skill_req:
            skill.save_binary(stream)
        self.item_race_req.save_binary(stream)
        self.item_class_req.save_binary(stream)
        self.item_disc_req.save_binary(stream)
        stream.write_dword(len(self.item_attr_req))
        for attr in self.item_attr_req:
            attr.save_binary(stream)
        stream.write_dword(self.item_level_req)
        stream.write_dword(self.item_rank_req)
        stream.write_dword(self.item_sex_req)
        stream.write_dword(len(self.item_user_power_action))
        for power in self.item_user_power_action:
            stream.write_dword(power[0])
            stream.write_dword(power[1])
            stream.write_dword(power[2])
        stream.write_dword(len(self.item_power_grant))
        for action in self.item_power_grant:
            action.save_binary(stream)
        stream.write_dword(len(self.item_power_action))
        for grant in self.item_power_action:
            grant.save_binary(stream)
        stream.write_bool(self.item_sheathable)
        if self.item_sheathable:
            stream.write_dword(self.item_sheath_slot)
            stream.write_tuple(self.item_sheath_offset)
            stream.write_tuple(self.item_sheath_rotation)
        stream.write_bool(self.item_has_stub)
        if self.item_has_stub:
            stream.write_bool(self.item_stub_holder[0])
            stream.write_qword(self.item_stub_holder[1])
        stream.write_dword(len(self.item_offering_adjustments))
        for offering in self.item_offering_adjustments:
            offering.save_binary(stream)
        stream.write_dword(len(self.item_resource_costs))
        for res in self.item_resource_costs:
            res.save_binary(stream)
        stream.write_dword(self.item_bane_rank)
        stream.write_bool(self.item_ignore_saved_actions)

    def load_json(self, data):
        super().load_json(data)
        self.item_type = STRING_TO_ITEM_TYPE[data['item_type']]
        self.item_eq_slots_value = data['item_eq_slots_value']
        self.item_eq_slots_type = data['item_eq_slots_type']
        value = 0
        for i in range(ITEM_EQUIP_SLOT_NUM):
            eq_slot = 1 << i
            if ITEM_EQIP_SLOT_TO_STRING[eq_slot] in data['item_eq_slots_or']:
                value |= eq_slot
        self.item_eq_slots_or = value
        value = 0
        for i in range(ITEM_EQUIP_SLOT_NUM):
            eq_slot = 1 << i
            if ITEM_EQIP_SLOT_TO_STRING[eq_slot] in data['item_eq_slots_and']:
                value |= eq_slot
        self.item_eq_slots_and = value
        self.item_takeable = data['item_takeable']
        self.item_value = data['item_value']
        self.item_wt = data['item_wt']
        self.item_passive_defense_mod = data['item_passive_defense_mod']
        self.item_base_name = data['item_base_name']
        self.item_dsc = data['item_dsc']
        self.item_render_object_female = data['item_render_object_female']
        self.item_health_full = data['item_health_full']
        self.item_skill_used = string_to_hash(data['item_skill_used'])
        self.item_skill_mastery_used = string_to_hash(data['item_skill_mastery_used'])
        self.item_parry_anim_id = data['item_parry_anim_id']
        self.item_offering_info = []
        for offering_data in data['item_offering_info']:
            offering = Offerings()
            offering.load_json(offering_data)
            self.item_offering_info.append(offering)
        if self.item_type == ITEM_TYPE_WEAPON:
            self.item_weapon = Weapon()
            self.item_weapon.load_json(data['item_weapon'])
            self.item_primary_attr = string_to_hash(data['item_primary_attr'])
            self.item_secondary_attr = string_to_hash(data['item_secondary_attr'])
        if self.item_type == ITEM_TYPE_ARMOR:
            self.item_bulk_factor = data['item_bulk_factor']
            self.item_defense_rating = data['item_defense_rating']
        value = 0
        for i in range(ITEM_FLAG_NUM):
            item_flag = 1 << i
            if ITEM_FLAG_TO_STRING[item_flag] in data['item_flags']:
                value |= item_flag
        self.item_flags = value
        value = 0
        for i in range(ITEM_USE_FLAGS_NUM):
            item_use_flag = 1 << i
            if ITEM_USE_FLAGS_TO_STRING[item_use_flag] in data['item_use_flags']:
                value |= item_use_flag
        self.item_use_flags = value
        self.item_post_item_id = data['item_post_item_id']
        self.item_initial_charges = data['item_initial_charges']
        self.item_book_arcana = data['item_book_arcana']
        self.item_skill_req = []
        for skill_data in data['item_skill_req']:
            skill = SkillLevel()
            skill.load_json(skill_data)
            self.item_skill_req.append(skill)
        self.item_race_req = RaceRequired()
        self.item_race_req.load_json(data['item_race_req'])
        self.item_class_req = ClassRequired()
        self.item_class_req.load_json(data['item_class_req'])
        self.item_disc_req = DiscRequired()
        self.item_disc_req.load_json(data['item_disc_req'])
        self.item_attr_req = []
        for attr_data in data['item_attr_req']:
            attr = AttributeValue()
            attr.load_json(attr_data)
            self.item_attr_req.append(attr)
        self.item_level_req = data['item_level_req']
        self.item_rank_req = data['item_rank_req']
        self.item_sex_req = STRING_TO_ITEM_SEX_REQ[data['item_sex_req']]
        self.item_user_power_action = []
        for action in data['item_user_power_action']:
            self.item_user_power_action.append([
                string_to_hash(action['power']),
                action['arguments'][0],
                action['arguments'][1],
            ])
        self.item_power_grant = []
        for grant_data in data['item_power_grant']:
            grant = PowerGrant()
            grant.load_json(grant_data)
            self.item_power_grant.append(grant)
        self.item_power_action = []
        for action_data in data['item_power_action']:
            action = PowerAction()
            action.load_json(action_data)
            self.item_power_action.append(action)
        self.item_sheathable = data['item_sheathable']
        if self.item_sheathable:
            self.item_sheath_slot = STRING_TO_ITEM_SHEATHSLOT[data['item_sheath_slot']]
            self.item_sheath_offset = data['item_sheath_offset']
            self.item_sheath_rotation = data['item_sheath_rotation']
        self.item_has_stub = data['item_has_stub']
        if self.item_has_stub:
            self.item_stub_holder = data['item_has_stub']
        self.item_offering_adjustments = []
        for offering_data in data['item_offering_adjustments']:
            offering = Offerings()
            offering.load_json(offering_data)
            self.item_offering_adjustments.append(offering)
        self.item_resource_costs = []
        for res_data in data['item_resource_costs']:
            res = ResourceInfo()
            res.load_json(res_data)
            self.item_resource_costs.append(res)
        self.item_bane_rank = data['item_bane_rank']
        self.item_ignore_saved_actions = data['item_ignore_saved_actions']

    def save_json(self):
        data = super().save_json()
        data['item_type'] = ITEM_TYPE_TO_STRING[self.item_type]
        data['item_eq_slots_value'] = self.item_eq_slots_value
        data['item_eq_slots_type'] = self.item_eq_slots_type
        if not self.item_eq_slots_or:
            data['item_eq_slots_or'] = [ITEM_EQIP_SLOT_TO_STRING[ITEM_EQUIP_SLOT_NONE]]
        else:
            data['item_eq_slots_or'] = []
            for i in range(ITEM_EQUIP_SLOT_NUM):
                eq_slot = 1 << i
                if self.item_eq_slots_or & eq_slot:
                    data['item_eq_slots_or'].append(ITEM_EQIP_SLOT_TO_STRING[eq_slot])
        if not self.item_eq_slots_and:
            data['item_eq_slots_and'] = [ITEM_EQIP_SLOT_TO_STRING[ITEM_EQUIP_SLOT_NONE]]
        else:
            data['item_eq_slots_and'] = []
            for i in range(ITEM_EQUIP_SLOT_NUM):
                eq_slot = 1 << i
                if self.item_eq_slots_and & eq_slot:
                    data['item_eq_slots_and'].append(ITEM_EQIP_SLOT_TO_STRING[eq_slot])
        data['item_takeable'] = self.item_takeable
        data['item_value'] = self.item_value
        data['item_wt'] = self.item_wt
        data['item_passive_defense_mod'] = self.item_passive_defense_mod
        data['item_base_name'] = self.item_base_name
        data['item_dsc'] = self.item_dsc
        data['item_render_object_female'] = self.item_render_object_female
        data['item_health_full'] = self.item_health_full
        data['item_skill_used'] = hash_to_string(self.item_skill_used)
        data['item_skill_mastery_used'] = hash_to_string(self.item_skill_mastery_used)
        data['item_parry_anim_id'] = self.item_parry_anim_id
        data['item_offering_info'] = []
        for offering in self.item_offering_info:
            data['item_offering_info'].append(offering.save_json())
        if self.item_type == ITEM_TYPE_WEAPON:
            data['item_weapon'] = self.item_weapon.save_json()
            data['item_primary_attr'] = hash_to_string(self.item_primary_attr)
            data['item_secondary_attr'] = hash_to_string(self.item_secondary_attr)
        if self.item_type == ITEM_TYPE_ARMOR:
            data['item_bulk_factor'] = self.item_bulk_factor
            data['item_defense_rating'] = self.item_defense_rating
        data['item_flags'] = []
        if self.item_flags:
            for i in range(ITEM_FLAG_NUM):
                item_flag = 1 << i
                if self.item_flags & item_flag:
                    data['item_flags'].append(ITEM_FLAG_TO_STRING[item_flag])
        data['item_use_flags'] = []
        if self.item_use_flags:
            for i in range(ITEM_USE_FLAGS_NUM):
                item_use_flag = 1 << i
                if self.item_use_flags & item_use_flag:
                    data['item_use_flags'].append(ITEM_USE_FLAGS_TO_STRING[item_use_flag])
        data['item_post_item_id'] = self.item_post_item_id
        data['item_initial_charges'] = self.item_initial_charges
        data['item_book_arcana'] = self.item_book_arcana
        data['item_skill_req'] = []
        for skill in self.item_skill_req:
            data['item_skill_req'].append(skill.save_json())
        data['item_race_req'] = self.item_race_req.save_json()
        data['item_class_req'] = self.item_class_req.save_json()
        data['item_disc_req'] = self.item_disc_req.save_json()
        data['item_attr_req'] = []
        for attr in self.item_attr_req:
            data['item_attr_req'].append(attr.save_json())
        data['item_level_req'] = self.item_level_req
        data['item_rank_req'] = self.item_rank_req
        data['item_sex_req'] = ITEM_SEX_REQ_TO_STRING[self.item_sex_req]
        data['item_user_power_action'] = []
        for action in self.item_user_power_action:
            data['item_user_power_action'].append({
                'power': hash_to_string(action[0]),
                'arguments': action[1:3],
            })
        data['item_power_grant'] = []
        for grant in self.item_power_grant:
            data['item_power_grant'].append(grant.save_json())
        data['item_power_action'] = []
        for action in self.item_power_action:
            data['item_power_action'].append(action.save_json())
        data['item_sheathable'] = self.item_sheathable
        if self.item_sheathable:
            data['item_sheath_slot'] = ITEM_SHEATHSLOT_TO_STRING[self.item_sheath_slot]
            data['item_sheath_offset'] = self.item_sheath_offset
            data['item_sheath_rotation'] = self.item_sheath_rotation
        data['item_has_stub'] = self.item_has_stub
        if self.item_has_stub:
            data['item_has_stub'] = self.item_stub_holder
        data['item_offering_adjustments'] = []
        for offering in self.item_offering_adjustments:
            data['item_offering_adjustments'].append(offering.save_json())
        data['item_resource_costs'] = []
        for res in self.item_resource_costs:
            data['item_resource_costs'].append(res.save_json())
        data['item_bane_rank'] = self.item_bane_rank
        data['item_ignore_saved_actions'] = self.item_ignore_saved_actions
        return data
