from __future__ import annotations

import dataclasses
import enum
from typing import *

import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base.datatypes import Vector3, Quaternion
from hippolyzer.lib.base.multidict import OrderedMultiDict


# Forward declarations so the dataclasses can be declared in natural order
JOINT_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(Joint))
CONSTRAINT_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(Constraint))
POSKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(PosKeyframe))
ROTKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(RotKeyframe))

JOINTS_DICT = OrderedMultiDict[str, "Joint"]


@dataclasses.dataclass
class Animation:
    major_version: int = se.dataclass_field(se.U16, default=1)
    minor_version: int = se.dataclass_field(se.U16, default=0)
    base_priority: int = se.dataclass_field(se.S32)
    duration: float = se.dataclass_field(se.F32)
    emote_name: str = se.dataclass_field(se.CStr())
    loop_in_point: float = se.dataclass_field(se.F32)
    loop_out_point: float = se.dataclass_field(se.F32)
    loop: int = se.dataclass_field(se.S32)
    ease_in_duration: float = se.dataclass_field(se.F32)
    ease_out_duration: float = se.dataclass_field(se.F32)
    hand_pose: HandPose = se.dataclass_field(lambda: se.IntEnum(HandPose, se.U32), default=0)
    joints: JOINTS_DICT = se.dataclass_field(se.MultiDictAdapter(
        se.Collection(se.U32, se.Tuple(se.CStr(), JOINT_DATACLASS)),
    ))
    constraints: List[Constraint] = se.dataclass_field(
        se.Collection(se.S32, CONSTRAINT_DATACLASS),
    )

    def to_bytes(self):
        writer = se.BufferWriter("<")
        writer.write(se.Dataclass(type(self)), self)
        return writer.copy_buffer()

    @classmethod
    def from_bytes(cls, buff) -> "Animation":
        reader = se.BufferReader("<", buff)
        return reader.read(se.Dataclass(cls))


@dataclasses.dataclass
class Joint:
    priority: int = se.dataclass_field(se.S32)
    rot_keyframes: List[RotKeyframe] = se.dataclass_field(
        se.Collection(se.S32, ROTKEYFRAME_DATACLASS)
    )
    pos_keyframes: List[PosKeyframe] = se.dataclass_field(
        se.Collection(se.S32, POSKEYFRAME_DATACLASS),
    )


def _get_version_from_context(ctx: se.ParseContext) -> Tuple[int, int]:
    return ctx._root.major_version, ctx._root.minor_version


@dataclasses.dataclass
class RotKeyframe:
    time: float = se.dataclass_field(lambda: VERSIONED_TIME)
    rot: Quaternion = se.dataclass_field(se.ContextSwitch(
        _get_version_from_context,
        {
            (0, 1): se.PackedQuat(se.Vector3),
            (1, 0): se.PackedQuat(se.Vector3U16(-1.0, 1.0)),
        },
    ))


@dataclasses.dataclass
class PosKeyframe:
    time: float = se.dataclass_field(lambda: VERSIONED_TIME)
    pos: Vector3 = se.dataclass_field(se.ContextSwitch(
        _get_version_from_context,
        {
            (0, 1): se.Vector3,
            (1, 0): se.Vector3U16(-5.0, 5.0),
        },
    ))


class QuantizedTime(se.QuantizedFloatBase):
    def __init__(self, prim_spec: se.SerializablePrimitive):
        super().__init__(prim_spec, zero_median=False)

    def _get_upper_limit(self, ctx: se.ParseContext) -> float:
        # Upper limit is the "duration" from the parent animation
        return ctx._root.duration

    def encode(self, val: Any, ctx: Optional[se.ParseContext]) -> Any:
        return self._float_to_quantized(val, 0.0, self._get_upper_limit(ctx))

    def decode(self, val: Any, ctx: Optional[se.ParseContext], pod: bool = False) -> Any:
        return self._quantized_to_float(val, 0.0, self._get_upper_limit(ctx))


VERSIONED_TIME = se.ContextSwitch(
    _get_version_from_context,
    {
        (0, 1): se.F32,
        (1, 0): QuantizedTime(se.U16),
    },
)


class ConstraintType(enum.IntEnum):
    POINT = 0
    PLANE = 1


@dataclasses.dataclass
class Constraint:
    chain_length: int = se.dataclass_field(se.U8)
    type: ConstraintType = se.dataclass_field(se.IntEnum(ConstraintType, se.U8))
    source_volume: str = se.dataclass_field(se.StrFixed(16))
    source_offset: Vector3 = se.dataclass_field(se.Vector3)
    target_volume: str = se.dataclass_field(se.StrFixed(16))
    target_offset: Vector3 = se.dataclass_field(se.Vector3)
    target_dir: Vector3 = se.dataclass_field(se.Vector3())
    ease_in_start: float = se.dataclass_field(se.F32)
    ease_in_stop: float = se.dataclass_field(se.F32)
    ease_out_start: float = se.dataclass_field(se.F32)
    ease_out_stop: float = se.dataclass_field(se.F32)


class HandPose(enum.IntEnum):
    SPREAD = 0
    RELAXED = 1
    POINT = 2
    FIST = 3
    RELAXED_L = 4
    POINT_L = 5
    FIST_L = 6
    RELAXED_R = 7
    POINT_R = 8
    FIST_R = 9
    SALUTE_R = 10
    TYPING = 11
    PEACE_R = 12
    PALM_R = 13
