pub mod auto_translate;
pub mod dalamud_link;
pub mod emphasis;
pub mod icon;
pub mod item;
pub mod map_link;
pub mod new_line;
pub mod player;
pub mod quest;
pub mod raw;
pub mod se_hyphen;
pub mod status;
pub mod text;
pub mod ui_foreground;
pub mod ui_glow;

pub use self::{
    auto_translate::AutoTranslatePayload,
    dalamud_link::DalamudLinkPayload,
    emphasis::EmphasisPayload,
    icon::IconPayload,
    item::ItemPayload,
    map_link::MapLinkPayload,
    new_line::NewLinePayload,
    player::PlayerPayload,
    quest::QuestPayload,
    raw::RawPayload,
    se_hyphen::SeHyphenPayload,
    status::StatusPayload,
    text::TextPayload,
    ui_foreground::UiForegroundPayload,
    ui_glow::UiGlowPayload,
};
use crate::Error;
use std::io::{Read, Cursor, Seek, SeekFrom};

#[derive(Debug, Clone, PartialEq)]
pub enum Payload {
    AutoTranslate(AutoTranslatePayload),
    DalamudLink(DalamudLinkPayload),
    Emphasis(EmphasisPayload),
    Icon(IconPayload),
    Item(ItemPayload),
    MapLink(MapLinkPayload),
    NewLine(NewLinePayload),
    Player(PlayerPayload),
    Quest(QuestPayload),
    Raw(RawPayload),
    SeHyphen(SeHyphenPayload),
    Status(StatusPayload),
    Text(TextPayload),
    UiForeground(UiForegroundPayload),
    UiGlow(UiGlowPayload),
}

impl Payload {
    const START_BYTE: u8 = 0x02;
    const END_BYTE: u8 = 0x03;

    pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<Vec<Self>, Error> {
        let bytes = bytes.as_ref();
        let num_bytes = bytes.len();
        let mut cursor = Cursor::new(bytes);
        let mut payloads = Vec::with_capacity(1);

        while (cursor.position() as usize) < num_bytes {
            payloads.push(Self::read_one(&mut cursor)?);
        }

        Ok(payloads)
    }

    pub fn read_one<R: Read + Seek>(mut reader: R) -> Result<Self, Error> {
        use byteorder::ReadBytesExt;

        let initial = reader.read_u8().map_err(Error::from)?;
        reader.seek(SeekFrom::Current(-1)).map_err(Error::from)?;

        if initial != Self::START_BYTE {
            // chunk len doesn't matter here
            TextPayload::decode(&mut reader, 0).map(Self::Text)
        } else {
            // seek back forward
            reader.seek(SeekFrom::Current(1)).map_err(Error::from)?;

            let post_start = reader.stream_position().map_err(Error::from)?;
            let chunk_type = reader.read_u8().map_err(Error::from)?;
            let chunk_len = TextPayload::read_integer(&mut reader)? as usize;

            let start = reader.stream_position().map_err(Error::from)?;

            let chunk = match SeStringChunkKind::from_u8(chunk_type) {
                SeStringChunkKind::Emphasis => EmphasisPayload::decode(&mut reader, chunk_len).map(Self::Emphasis)?,
                SeStringChunkKind::NewLine => Self::NewLine(NewLinePayload),
                SeStringChunkKind::SeHyphen => Self::SeHyphen(SeHyphenPayload),
                SeStringChunkKind::Interactable => {
                    let sub_type = reader.read_u8().map_err(Error::from)?;
                    match SeInteractableKind::from_u8(sub_type) {
                        SeInteractableKind::PlayerName => PlayerPayload::decode(&mut reader, chunk_len).map(Self::Player)?,
                        SeInteractableKind::ItemLink => ItemPayload::decode(&mut reader, chunk_len).map(Self::Item)?,
                        SeInteractableKind::MapPositionLink => MapLinkPayload::decode(&mut reader, chunk_len).map(Self::MapLink)?,
                        SeInteractableKind::QuestLink => QuestPayload::decode(&mut reader, chunk_len).map(Self::Quest)?,
                        SeInteractableKind::Status => StatusPayload::decode(&mut reader, chunk_len).map(Self::Status)?,
                        SeInteractableKind::DalamudLink => DalamudLinkPayload::decode(&mut reader, chunk_len).map(Self::DalamudLink)?,
                        SeInteractableKind::LinkTerminator | SeInteractableKind::Unknown(_) => {
                            reader.seek(SeekFrom::Start(post_start)).map_err(Error::from)?;
                            let additional = start - post_start;
                            RawPayload::decode(&mut reader, chunk_len + additional as usize).map(Self::Raw)?
                        }
                    }
                }
                SeStringChunkKind::AutoTranslate => AutoTranslatePayload::decode(&mut reader, chunk_len).map(Self::AutoTranslate)?,
                SeStringChunkKind::UiForeground => UiForegroundPayload::decode(&mut reader, chunk_len).map(Self::UiForeground)?,
                SeStringChunkKind::UiGlow => UiGlowPayload::decode(&mut reader, chunk_len).map(Self::UiGlow)?,
                SeStringChunkKind::Icon => IconPayload::decode(&mut reader, chunk_len).map(Self::Icon)?,
                SeStringChunkKind::Unknown(_) => {
                    reader.seek(SeekFrom::Start(post_start)).map_err(Error::from)?;
                    let additional = start - post_start;
                    RawPayload::decode(&mut reader, chunk_len + additional as usize).map(Self::Raw)?
                },
            };

            // skip whatever's left
            let total_read = reader.stream_position().map_err(Error::from)? - start;
            // +1 for END_BYTE
            reader.seek(SeekFrom::Current((chunk_len as u64 - total_read + 1) as i64)).map_err(Error::from)?;

            Ok(chunk)
        }
    }

    pub fn encode(&self) -> Vec<u8> {
        match self {
            Payload::AutoTranslate(p) => p.encode(),
            Payload::DalamudLink(p) => p.encode(),
            Payload::Emphasis(p) => p.encode(),
            Payload::Icon(p) => p.encode(),
            Payload::Item(p) => p.encode(),
            Payload::MapLink(p) => p.encode(),
            Payload::NewLine(p) => p.encode(),
            Payload::Player(p) => p.encode(),
            Payload::Quest(p) => p.encode(),
            Payload::Raw(p) => p.encode(),
            Payload::SeHyphen(p) => p.encode(),
            Payload::Status(p) => p.encode(),
            Payload::Text(p) => p.encode(),
            Payload::UiForeground(p) => p.encode(),
            Payload::UiGlow(p) => p.encode(),
        }
    }
}

trait Encode {
    fn encode(&self) -> Vec<u8>;

    fn make_integer(int: u32) -> Vec<u8> {
        use byteorder::{ByteOrder, LittleEndian};

        let mut vec = Vec::with_capacity(4);

        if int < 0xCF {
            vec.push(int as u8 + 1);
            return vec;
        }

        let mut bytes = [0; 4];
        LittleEndian::write_u32(&mut bytes, int);

        vec.push(0xF0);

        for i in (0..4).rev() {
            if bytes[i] != 0 {
                vec.push(bytes[i]);
                vec[0] |= 1 << i;
            }
        }

        vec[0] -= 1;

        vec
    }

    fn make_packed_integers(high: u32, low: u32) -> Vec<u8> {
        let value = (high << 16) | (low & 0xFF);
        Self::make_integer(value)
    }
}

trait Decode {
    fn decode<R: Read + Seek>(reader: R, chunk_len: usize) -> Result<Self, crate::Error>
        where Self: Sized;

    fn read_integer<R: Read>(mut reader: R) -> Result<u32, crate::Error> {
        use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};

        let mut marker = reader.read_u8().map_err(Error::from)?;
        if marker < 0xD0 {
            return Ok((marker - 1) as u32);
        }

        marker = (marker + 1) & 0b1111;

        let mut ret = [0; 4];
        for i in (0..4).rev() {
            ret[i] = if (marker & (1 << i)) == 0 {
                0
            } else {
                reader.read_u8().map_err(Error::from)?
            };
        }

        Ok(LittleEndian::read_u32(&ret))
    }

    fn read_packed_integers<R: Read>(reader: R) -> Result<(u32, u32), crate::Error> {
        let value = Self::read_integer(reader)?;
        Ok((value >> 16, value & 0xFFFF))
    }
}

pub(crate) enum SeStringChunkKind {
    Icon,
    Emphasis,
    NewLine,
    SeHyphen,
    Interactable,
    AutoTranslate,
    UiForeground,
    UiGlow,
    Unknown(u8),
}

impl SeStringChunkKind {
    fn from_u8(byte: u8) -> Self {
        match byte {
            0x12 => Self::Icon,
            0x1A => Self::Emphasis,
            0x10 => Self::NewLine,
            0x1F => Self::SeHyphen,
            0x27 => Self::Interactable,
            0x2E => Self::AutoTranslate,
            0x48 => Self::UiForeground,
            0x49 => Self::UiGlow,
            x => Self::Unknown(x),
        }
    }

    pub(crate) fn as_u8(&self) -> u8 {
        match self {
            Self::Icon => 0x12,
            Self::Emphasis => 0x1A,
            Self::NewLine => 0x10,
            Self::SeHyphen => 0x1F,
            Self::Interactable => 0x27,
            Self::AutoTranslate => 0x2E,
            Self::UiForeground => 0x48,
            Self::UiGlow => 0x49,
            Self::Unknown(x) => *x,
        }
    }
}

enum SeInteractableKind {
    PlayerName,
    ItemLink,
    MapPositionLink,
    QuestLink,
    Status,
    DalamudLink,
    LinkTerminator,
    Unknown(u8),
}

impl SeInteractableKind {
    fn from_u8(byte: u8) -> Self {
        match byte {
            0x01 => Self::PlayerName,
            0x03 => Self::ItemLink,
            0x04 => Self::MapPositionLink,
            0x05 => Self::QuestLink,
            0x09 => Self::Status,
            0x0F => Self::DalamudLink,
            0xCF => Self::LinkTerminator,
            x => Self::Unknown(x),
        }
    }

    fn as_u8(&self) -> u8 {
        match self {
            Self::PlayerName => 0x01,
            Self::ItemLink => 0x03,
            Self::MapPositionLink => 0x04,
            Self::QuestLink => 0x05,
            Self::Status => 0x09,
            Self::DalamudLink => 0x0F,
            Self::LinkTerminator => 0xCF,
            Self::Unknown(x) => *x,
        }
    }
}
