use nom::number::complete::be_u8;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use super::Definition;
use runefs::parse::{be_u16_smart, be_u32_smart_compat};

/// Contains all the information about a certain location fetched from the cache through
/// the [LocationLoader](../../loader/osrs/struct.LocationLoader.html).
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct LocationDefinition {
    pub id: u16,
    pub region_x: u16,
    pub region_y: u16,
    pub data: Vec<Location>,
}

impl LocationDefinition {
    #[inline]
    pub const fn region_base_coords(&self) -> (u16, u16) {
        (self.region_x << 6, self.region_y << 6)
    }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct Location {
    pub id: u32,
    pub loc_type: u8,
    pub orientation: u8,
    pub pos: (u16, u16, u16),
}

impl Definition for LocationDefinition {
    fn new(id: u16, buffer: &[u8]) -> crate::Result<Self> {
        let loc_def = decode_buffer(id, buffer)?;

        Ok(loc_def)
    }
}

fn decode_buffer(id: u16, mut buffer: &[u8]) -> crate::Result<LocationDefinition> {
    let mut loc_def = LocationDefinition {
        id,
        region_x: (id >> 8) & 0xFF,
        region_y: id & 0xFF,
        ..LocationDefinition::default()
    };

    let mut id = -1;

    loop {
        let (buf, id_offset) = be_u32_smart_compat(buffer)?;
        buffer = buf;

        if id_offset == 0 || buffer.is_empty() {
            break;
        }

        id += id_offset as i32;

        let mut pos = 0;

        loop {
            let (buf, pos_offset) = be_u16_smart(buffer)?;
            buffer = buf;

            if pos_offset == 0 {
                break;
            }

            pos += pos_offset - 1;

            let local_x = pos >> 6 & 0x3F;
            let local_y = pos & 0x3F;
            let local_z = pos >> 12 & 0x3;

            let (buf, attr) = be_u8(buffer)?;
            buffer = buf;

            loc_def.data.push(Location {
                id: id as u32,
                loc_type: attr >> 2,
                orientation: attr & 0x3,
                pos: (
                    loc_def.region_x + local_x,
                    loc_def.region_y + local_y,
                    local_z,
                ),
            });

            if buffer.is_empty() {
                return Ok(loc_def);
            }
        }
    }

    Ok(loc_def)
}
