from dataclasses import dataclass, field
from typing import cast

from terrex.item.item import Item
from terrex.net.streamer import Reader, Writer
from terrex.net.structure.vec2 import Vec2


@dataclass
class TileEntityBase:
    id: int = 0
    x: int = 0
    y: int = 0


@dataclass
class TrainingDummy(TileEntityBase):
    type: int = 0
    npc_index: int = 0


@dataclass
class ItemFrame(TileEntityBase):
    type: int = 1
    item_type: int = 0
    item_prefix: int = 0
    item_stack: int = 0


@dataclass
class LogicSensor(TileEntityBase):
    type: int = 2
    logic_check_type: int = 0
    on: bool = False


@dataclass
class DisplayDoll(TileEntityBase):
    type: int = 3
    pose: int = 0
    equip: list[Item | None] = field(default_factory=lambda: [None] * 9)
    dyes: list[Item | None] = field(default_factory=lambda: [None] * 9)
    misc: list[Item | None] = field(default_factory=lambda: [None] * 8)


@dataclass
class WeaponRack(TileEntityBase):
    type: int = 4
    item_type: int = 0
    item_prefix: int = 0
    item_stack: int = 0


@dataclass
class HatRack(TileEntityBase):
    type: int = 5
    items: list[Item | None] = field(default_factory=lambda: [None, None])
    dyes: list[Item | None] = field(default_factory=lambda: [None, None])


@dataclass
class FoodPlatter(TileEntityBase):
    type: int = 6
    item_type: int = 0
    item_prefix: int = 0
    item_stack: int = 0


@dataclass
class TeleportationPylon(TileEntityBase):
    type: int = 7


@dataclass
class DeadCellsDisplayJar(TileEntityBase):
    type: int = 8
    item_type: int = 0
    item_prefix: int = 0
    item_stack: int = 0


@dataclass
class LeashedEntityAnchorWithItem(TileEntityBase):
    item_type: int = 0


@dataclass
class KiteAnchor(LeashedEntityAnchorWithItem):
    type: int = 9
    item_type: int = 0


@dataclass
class CritterAnchor(LeashedEntityAnchorWithItem):
    type: int = 10
    item_type: int = 0


TileEntity = (
    TrainingDummy
    | ItemFrame
    | LogicSensor
    | DisplayDoll
    | WeaponRack
    | HatRack
    | FoodPlatter
    | TeleportationPylon
    | DeadCellsDisplayJar
    | KiteAnchor
    | CritterAnchor
)


def read_tile_entity(reader: Reader) -> TileEntity | None:
    typ = reader.read_byte()
    if typ < 0:
        return None
    id_ = reader.read_int()
    x = reader.read_short()
    y = reader.read_short()
    if typ == 0:
        npc_index = reader.read_ushort()
        return TrainingDummy(type=typ, id=id_, x=x, y=y, npc_index=npc_index)
    elif typ == 1:
        item_type = reader.read_ushort()
        item_prefix = reader.read_byte()
        item_stack = reader.read_ushort()
        return ItemFrame(
            type=typ,
            id=id_,
            x=x,
            y=y,
            item_type=item_type,
            item_prefix=item_prefix,
            item_stack=item_stack,
        )
    elif typ == 2:
        logic_check_type = reader.read_byte()
        on = reader.read_bool()
        return LogicSensor(type=typ, id=id_, x=x, y=y, logic_check_type=logic_check_type, on=on)
    elif typ == 3:
        bits_byte = reader.read_byte()
        bits_byte1 = reader.read_byte()
        pose = 0
        if reader.version >= 307:
            pose = reader.read_byte()
        bits_byte2 = 0
        if reader.version >= 308:
            bits_byte2 = reader.read_byte()
        has_item = False
        if reader.version == 311:
            has_item = bool(bits_byte2 & 2)
            bits_byte2 &= ~2
        num_equip = bits_byte | (256 if (bits_byte2 & 2) else 0)
        equip: list[Item | None] = cast(list[Item | None], [None] * 9)
        for i in range(9):
            if num_equip & (1 << i):
                item_id = reader.read_ushort()
                prefix = reader.read_byte()
                stack = reader.read_ushort()
                equip[i] = Item(
                    item_id=item_id,
                    prefix=prefix,
                    stacks=stack,
                    net_id=0,
                    position=Vec2(0, 0),
                    velocity=Vec2(0, 0),
                )
        num_dyes = bits_byte1 | (256 if (bits_byte2 & 4) else 0)
        dyes: list[Item | None] = cast(list[Item | None], [None] * 9)
        for j in range(9):
            if num_dyes & (1 << j):
                item_id = reader.read_ushort()
                prefix = reader.read_byte()
                stack = reader.read_ushort()
                dyes[j] = Item(
                    item_id=item_id,
                    prefix=prefix,
                    stacks=stack,
                    net_id=0,
                    position=Vec2(0, 0),
                    velocity=Vec2(0, 0),
                )
        misc: list[Item | None] = cast(list[Item | None], [None] * 8)
        for k in range(8):
            if bits_byte2 & (1 << k):
                item_id = reader.read_ushort()
                prefix = reader.read_byte()
                stack = reader.read_ushort()
                misc[k] = Item(
                    item_id=item_id,
                    prefix=prefix,
                    stacks=stack,
                    net_id=0,
                    position=Vec2(0, 0),
                    velocity=Vec2(0, 0),
                )
        if has_item:
            item_id = reader.read_ushort()
            prefix = reader.read_byte()
            stack = reader.read_ushort()
            equip[8] = Item(
                item_id=item_id,
                prefix=prefix,
                stacks=stack,
                net_id=0,
                position=Vec2(0, 0),
                velocity=Vec2(0, 0),
            )
        return DisplayDoll(type=typ, id=id_, x=x, y=y, pose=pose, equip=equip, dyes=dyes, misc=misc)
    elif typ == 4:
        item_type = reader.read_ushort()
        item_prefix = reader.read_byte()
        item_stack = reader.read_ushort()
        return WeaponRack(
            type=typ,
            id=id_,
            x=x,
            y=y,
            item_type=item_type,
            item_prefix=item_prefix,
            item_stack=item_stack,
        )
    elif typ == 5:
        flags = reader.read_byte()
        items: list[Item | None] = cast(list[Item | None], [None] * 2)
        hatrack_dyes: list[Item | None] = cast(list[Item | None], [None] * 2)
        for i in range(2):
            if flags & (1 << i):
                item_id = reader.read_ushort()
                prefix = reader.read_byte()
                stack = reader.read_ushort()
                items[i] = Item(
                    item_id=item_id,
                    prefix=prefix,
                    stacks=stack,
                    net_id=0,
                    position=Vec2(0, 0),
                    velocity=Vec2(0, 0),
                )
        for j in range(2):
            if flags & (1 << (j + 2)):
                item_id = reader.read_ushort()
                prefix = reader.read_byte()
                stack = reader.read_ushort()
                hatrack_dyes[j] = Item(
                    item_id=item_id,
                    prefix=prefix,
                    stacks=stack,
                    net_id=0,
                    position=Vec2(0, 0),
                    velocity=Vec2(0, 0),
                )
        return HatRack(type=typ, id=id_, x=x, y=y, items=items, dyes=hatrack_dyes)
    elif typ == 6:
        item_type = reader.read_ushort()
        item_prefix = reader.read_byte()
        item_stack = reader.read_ushort()
        return FoodPlatter(
            type=typ,
            id=id_,
            x=x,
            y=y,
            item_type=item_type,
            item_prefix=item_prefix,
            item_stack=item_stack,
        )
    elif typ == 7:
        return TeleportationPylon(type=typ, id=id_, x=x, y=y)
    # added on v1.4.5.0+
    elif typ == 8:
        item_type = reader.read_ushort()
        item_prefix = reader.read_byte()
        item_stack = reader.read_ushort()
        return DeadCellsDisplayJar(
            type=typ,
            id=id_,
            x=x,
            y=y,
            item_type=item_type,
            item_prefix=item_prefix,
            item_stack=item_stack,
        )
    elif typ == 9:
        item_type = reader.read_ushort()
        return KiteAnchor(type=typ, id=id_, x=x, y=y, item_type=item_type)
    elif typ == 10:
        item_type = reader.read_ushort()
        return CritterAnchor(type=typ, id=id_, x=x, y=y, item_type=item_type)
    else:
        print(f"Unknown TileEntity type: {typ}")
        return None


def write_tile_entity(entity: TileEntity, writer: Writer) -> None:
    # TODO: implement based on type
    pass
