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


from base64 import b64decode, b64encode
from collections import OrderedDict

from arcane.util import ResStream

EFFETCT_TO_STRING = {
    0: 'PARTICLE',
    1: 'LIGHTNING',
    2: 'GEOMETRY',
}

STRING_TO_EFFECT = {value: key for key, value in EFFETCT_TO_STRING.items()}


class ArcParticle:
    def load_binary(self, stream: ResStream):
        self.particle_attached_bone = stream.read_dword()
        self.particle_count = stream.read_dword()
        self.particle_size = stream.read_float()
        self.particle_life = stream.read_float()
        self.particle_life_rand = stream.read_float()
        self.particle_shape_and_pos = stream.read_dword()
        self.particle_emitter_scale = stream.read_float()
        self.particle_pos_offset = stream.read_tuple()
        self.particle_ppos_reference = stream.read_tuple()
        self.particle_initial_velocities = stream.read_dword()
        self.particle_velocity_scale = stream.read_float()
        self.particle_vel_reference = stream.read_tuple()
        self.particle_dir_random = stream.read_float()
        self.particle_spd_random = stream.read_float()
        self.particle_initial_rot = stream.read_float()
        self.particle_initial_rot_random = stream.read_float()
        self.particle_incremental_rot = stream.read_float()
        self.particle_incremental_rot_random = stream.read_float()
        self.particle_color_keys = [
            [
                stream.read_float(),
                stream.read_float(),
                stream.read_float(),
                stream.read_float(),
            ] for _ in range(5)
        ]
        self.particle_color_keytimes = [stream.read_float() for _ in range(5)]
        self.particle_size_keys = [stream.read_float() for _ in range(5)]
        self.particle_targettype = stream.read_dword()
        self.particle_lifetime = stream.read_float()
        self.particle_texture = stream.read_dword()
        self.particle_blend_type = stream.read_dword()
        self.particle_attractor_bone = stream.read_dword()
        self.particle_directional_grav = stream.read_tuple()
        self.particle_field_function = stream.read_dword()
        self.particle_gravity_strength = stream.read_float()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.particle_attached_bone)
        stream.write_dword(self.particle_count)
        stream.write_float(self.particle_size)
        stream.write_float(self.particle_life)
        stream.write_float(self.particle_life_rand)
        stream.write_dword(self.particle_shape_and_pos)
        stream.write_float(self.particle_emitter_scale)
        stream.write_tuple(self.particle_pos_offset)
        stream.write_tuple(self.particle_ppos_reference)
        stream.write_dword(self.particle_initial_velocities)
        stream.write_float(self.particle_velocity_scale)
        stream.write_tuple(self.particle_vel_reference)
        stream.write_float(self.particle_dir_random)
        stream.write_float(self.particle_spd_random)
        stream.write_float(self.particle_initial_rot)
        stream.write_float(self.particle_initial_rot_random)
        stream.write_float(self.particle_incremental_rot)
        stream.write_float(self.particle_incremental_rot_random)
        for i in range(5):
            stream.write_float(self.particle_color_keys[i][0])
            stream.write_float(self.particle_color_keys[i][1])
            stream.write_float(self.particle_color_keys[i][2])
            stream.write_float(self.particle_color_keys[i][3])
        for i in range(5):
            stream.write_float(self.particle_color_keytimes[i])
        for i in range(5):
            stream.write_float(self.particle_size_keys[i])
        stream.write_dword(self.particle_targettype)
        stream.write_float(self.particle_lifetime)
        stream.write_dword(self.particle_texture)
        stream.write_dword(self.particle_blend_type)
        stream.write_dword(self.particle_attractor_bone)
        stream.write_tuple(self.particle_directional_grav)
        stream.write_dword(self.particle_field_function)
        stream.write_float(self.particle_gravity_strength)

    def load_json(self, data):
        self.particle_attached_bone = data['particle_attached_bone']
        self.particle_count = data['particle_count']
        self.particle_size = data['particle_size']
        self.particle_life = data['particle_life']
        self.particle_life_rand = data['particle_life_rand']
        self.particle_shape_and_pos = data['particle_shape_and_pos']
        self.particle_emitter_scale = data['particle_emitter_scale']
        self.particle_pos_offset = data['particle_pos_offset']
        self.particle_ppos_reference = data['particle_ppos_reference']
        self.particle_initial_velocities = data['particle_initial_velocities']
        self.particle_velocity_scale = data['particle_velocity_scale']
        self.particle_vel_reference = data['particle_vel_reference']
        self.particle_dir_random = data['particle_dir_random']
        self.particle_spd_random = data['particle_spd_random']
        self.particle_initial_rot = data['particle_initial_rot']
        self.particle_initial_rot_random = data['particle_initial_rot_random']
        self.particle_incremental_rot = data['particle_incremental_rot']
        self.particle_incremental_rot_random = data['particle_incremental_rot_random']
        self.particle_color_keys = data['particle_color_keys']
        self.particle_color_keytimes = data['particle_color_keytimes']
        self.particle_size_keys = data['particle_size_keys']
        self.particle_targettype = data['particle_targettype']
        self.particle_lifetime = data['particle_lifetime']
        self.particle_texture = data['particle_texture']
        self.particle_blend_type = data['particle_blend_type']
        self.particle_attractor_bone = data['particle_attractor_bone']
        self.particle_directional_grav = data['particle_directional_grav']
        self.particle_field_function = data['particle_field_function']
        self.particle_gravity_strength = data['particle_gravity_strength']

    def save_json(self):
        data = OrderedDict()
        data['particle_attached_bone'] = self.particle_attached_bone
        data['particle_count'] = self.particle_count
        data['particle_size'] = self.particle_size
        data['particle_life'] = self.particle_life
        data['particle_life_rand'] = self.particle_life_rand
        data['particle_shape_and_pos'] = self.particle_shape_and_pos
        data['particle_emitter_scale'] = self.particle_emitter_scale
        data['particle_pos_offset'] = self.particle_pos_offset
        data['particle_ppos_reference'] = self.particle_ppos_reference
        data['particle_initial_velocities'] = self.particle_initial_velocities
        data['particle_velocity_scale'] = self.particle_velocity_scale
        data['particle_vel_reference'] = self.particle_vel_reference
        data['particle_dir_random'] = self.particle_dir_random
        data['particle_spd_random'] = self.particle_spd_random
        data['particle_initial_rot'] = self.particle_initial_rot
        data['particle_initial_rot_random'] = self.particle_initial_rot_random
        data['particle_incremental_rot'] = self.particle_incremental_rot
        data['particle_incremental_rot_random'] = self.particle_incremental_rot_random
        data['particle_color_keys'] = self.particle_color_keys
        data['particle_color_keytimes'] = self.particle_color_keytimes
        data['particle_size_keys'] = self.particle_size_keys
        data['particle_targettype'] = self.particle_targettype
        data['particle_lifetime'] = self.particle_lifetime
        data['particle_texture'] = self.particle_texture
        data['particle_blend_type'] = self.particle_blend_type
        data['particle_attractor_bone'] = self.particle_attractor_bone
        data['particle_directional_grav'] = self.particle_directional_grav
        data['particle_field_function'] = self.particle_field_function
        data['particle_gravity_strength'] = self.particle_gravity_strength
        return data


class ArcLightning:
    def load_binary(self, stream: ResStream):
        self.lightning_texture = stream.read_dword()
        self.lightning_src_bone = stream.read_dword()
        self.lightning_dst_bone = stream.read_dword()
        self.lightning_width = stream.read_float()
        self.lightning_random_factor = stream.read_float()
        self.lightning_sine_factor = stream.read_float()
        self.lightning_sine_phase = stream.read_float()
        self.lightning_sine_phase_rep = stream.read_float()
        self.lightning_length = stream.read_float()
        self.lightning_random_move_speed = stream.read_float()
        self.lightning_color = stream.read_tuple()
        self.lightning_lifetime = stream.read_float()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.lightning_texture)
        stream.write_dword(self.lightning_src_bone)
        stream.write_dword(self.lightning_dst_bone)
        stream.write_float(self.lightning_width)
        stream.write_float(self.lightning_random_factor)
        stream.write_float(self.lightning_sine_factor)
        stream.write_float(self.lightning_sine_phase)
        stream.write_float(self.lightning_sine_phase_rep)
        stream.write_float(self.lightning_length)
        stream.write_float(self.lightning_random_move_speed)
        stream.write_tuple(self.lightning_color)
        stream.write_float(self.lightning_lifetime)

    def load_json(self, data):
        self.lightning_texture = data['lightning_texture']
        self.lightning_src_bone = data['lightning_src_bone']
        self.lightning_dst_bone = data['lightning_dst_bone']
        self.lightning_width = data['lightning_width']
        self.lightning_random_factor = data['lightning_random_factor']
        self.lightning_sine_factor = data['lightning_sine_factor']
        self.lightning_sine_phase = data['lightning_sine_phase']
        self.lightning_sine_phase_rep = data['lightning_sine_phase_rep']
        self.lightning_length = data['lightning_length']
        self.lightning_random_move_speed = data['lightning_random_move_speed']
        self.lightning_color = data['lightning_color']
        self.lightning_lifetime = data['lightning_lifetime']

    def save_json(self):
        data = OrderedDict()
        data['lightning_texture'] = self.lightning_texture
        data['lightning_src_bone'] = self.lightning_src_bone
        data['lightning_dst_bone'] = self.lightning_dst_bone
        data['lightning_width'] = self.lightning_width
        data['lightning_random_factor'] = self.lightning_random_factor
        data['lightning_sine_factor'] = self.lightning_sine_factor
        data['lightning_sine_phase'] = self.lightning_sine_phase
        data['lightning_sine_phase_rep'] = self.lightning_sine_phase_rep
        data['lightning_length'] = self.lightning_length
        data['lightning_random_move_speed'] = self.lightning_random_move_speed
        data['lightning_color'] = self.lightning_color
        data['lightning_lifetime'] = self.lightning_lifetime
        return data


class ArcGeometry:
    def load_binary(self, stream: ResStream):
        self.geometry_texture = stream.read_dword()
        self.geometry_src_bone = stream.read_dword()
        self.geometry_lifetime = stream.read_float()
        self.geometry_tex_trans_x = stream.read_float()
        self.geometry_tex_trans_y = stream.read_float()
        self.geometry_tex_rot = stream.read_float()
        self.geometry_grow = stream.read_tuple()
        self.geometry_tesselation = stream.read_float()
        self.geometry_size = stream.read_tuple()
        self.geometry_geo_rot_x = stream.read_float()
        self.geometry_fade_falloff = stream.read_float()
        self.geometry_fadein = stream.read_float()
        self.geometry_fadeout = stream.read_float()
        self.geometry_color = [stream.read_float() for _ in range(4)]
        self.geometry_type = stream.read_dword()
        self.geometry_texture_proj = stream.read_dword()
        self.geometry_fade_dir = stream.read_dword()
        self.geometry_offset = stream.read_tuple()

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.geometry_texture)
        stream.write_dword(self.geometry_src_bone)
        stream.write_float(self.geometry_lifetime)
        stream.write_float(self.geometry_tex_trans_x)
        stream.write_float(self.geometry_tex_trans_y)
        stream.write_float(self.geometry_tex_rot)
        stream.write_tuple(self.geometry_grow)
        stream.write_float(self.geometry_tesselation)
        stream.write_tuple(self.geometry_size)
        stream.write_float(self.geometry_geo_rot_x)
        stream.write_float(self.geometry_fade_falloff)
        stream.write_float(self.geometry_fadein)
        stream.write_float(self.geometry_fadeout)
        for i in range(4):
            stream.write_float(self.geometry_color[i])
        stream.write_dword(self.geometry_type)
        stream.write_dword(self.geometry_texture_proj)
        stream.write_dword(self.geometry_fade_dir)
        stream.write_tuple(self.geometry_offset)

    def load_json(self, data):
        self.geometry_texture = data['geometry_texture']
        self.geometry_src_bone = data['geometry_src_bone']
        self.geometry_lifetime = data['geometry_lifetime']
        self.geometry_tex_trans_x = data['geometry_tex_trans_x']
        self.geometry_tex_trans_y = data['geometry_tex_trans_y']
        self.geometry_tex_rot = data['geometry_tex_rot']
        self.geometry_grow = data['geometry_grow']
        self.geometry_tesselation = data['geometry_tesselation']
        self.geometry_size = data['geometry_size']
        self.geometry_geo_rot_x = data['geometry_geo_rot_x']
        self.geometry_fade_falloff = data['geometry_fade_falloff']
        self.geometry_fadein = data['geometry_fadein']
        self.geometry_fadeout = data['geometry_fadeout']
        self.geometry_color = data['geometry_color']
        self.geometry_type = data['geometry_type']
        self.geometry_texture_proj = data['geometry_texture_proj']
        self.geometry_fade_dir = data['geometry_fade_dir']
        self.geometry_offset = data['geometry_offset']

    def save_json(self):
        data = OrderedDict()
        data['geometry_texture'] = self.geometry_texture
        data['geometry_src_bone'] = self.geometry_src_bone
        data['geometry_lifetime'] = self.geometry_lifetime
        data['geometry_tex_trans_x'] = self.geometry_tex_trans_x
        data['geometry_tex_trans_y'] = self.geometry_tex_trans_y
        data['geometry_tex_rot'] = self.geometry_tex_rot
        data['geometry_grow'] = self.geometry_grow
        data['geometry_tesselation'] = self.geometry_tesselation
        data['geometry_size'] = self.geometry_size
        data['geometry_geo_rot_x'] = self.geometry_geo_rot_x
        data['geometry_fade_falloff'] = self.geometry_fade_falloff
        data['geometry_fadein'] = self.geometry_fadein
        data['geometry_fadeout'] = self.geometry_fadeout
        data['geometry_color'] = self.geometry_color
        data['geometry_type'] = self.geometry_type
        data['geometry_texture_proj'] = self.geometry_texture_proj
        data['geometry_fade_dir'] = self.geometry_fade_dir
        data['geometry_offset'] = self.geometry_offset
        return data


class ArcVisualEffect:
    def load_binary(self, stream: ResStream):
        self.effect_type = stream.read_dword()
        self.effect_time = stream.read_float()

        if self.effect_type == 0:
            self.effect = ArcParticle()
        elif self.effect_type == 1:
            self.effect = ArcLightning()
        elif self.effect_type == 2:
            self.effect = ArcGeometry()
        self.effect.load_binary(stream)

    def save_binary(self, stream: ResStream):
        stream.write_dword(self.effect_type)
        stream.write_float(self.effect_time)
        self.effect.save_binary(stream)

    def load_json(self, data):
        self.effect_type = STRING_TO_EFFECT[data['effect_type']]
        self.effect_time = data['effect_time']
        if self.effect_type == 0:
            self.effect = ArcParticle()
        elif self.effect_type == 1:
            self.effect = ArcLightning()
        elif self.effect_type == 2:
            self.effect = ArcGeometry()
        self.effect.load_json(data['effect'])

    def save_json(self):
        data = OrderedDict()
        data['effect_type'] = EFFETCT_TO_STRING[self.effect_type]
        data['effect_time'] = self.effect_time
        data['effect'] = self.effect.save_json()
        return data


class ArcVisual:
    def load_binary(self, stream: ResStream):
        num_effects = stream.read_dword()

        self.vfx_fail = None
        if num_effects == 1617156728:
            stream.buffer.seek(0, 0)
            self.vfx_fail = stream.buffer.read()
            return

        self.vfx_duration = stream.read_float()
        self.vfx_effects = [ArcVisualEffect() for _ in range(num_effects)]
        for effect in self.vfx_effects:
            effect.load_binary(stream)

    def save_binary(self, stream: ResStream):
        if self.vfx_fail:
            stream.buffer.write(self.vfx_fail)
            return

        stream.write_dword(len(self.vfx_effects))
        stream.write_float(self.vfx_duration)
        for effect in self.vfx_effects:
            effect.save_binary(stream)

    def load_json(self, data):
        self.vfx_fail = data.get('vfx_fail')
        if self.vfx_fail:
            self.vfx_fail = b64decode(self.vfx_fail)
            return

        self.vfx_duration = data['vfx_duration']
        self.vfx_effects = []
        for effect_data in data['vfx_effects']:
            effect = ArcVisualEffect()
            effect.load_json(effect_data)
            self.vfx_effects.append(effect)

    def save_json(self):
        data = OrderedDict()
        if self.vfx_fail:
            data['vfx_fail'] = b64encode(self.vfx_fail).decode()
            return data

        data['vfx_duration'] = self.vfx_duration
        data['vfx_effects'] = []
        for effect in self.vfx_effects:
            data['vfx_effects'].append(effect.save_json())
        return data