use anyhow::{ensure, Result};
use bitvec_helpers::{bitvec_reader::BitVecReader, bitvec_writer::BitVecWriter};

#[cfg(feature = "serde_feature")]
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};

use super::{level6::MAX_PQ_LUMINANCE, ColorPrimaries, ExtMetadataBlock, ExtMetadataBlockInfo};

pub const PRESET_TARGET_DISPLAYS: &[u8] = &[1, 16, 18, 21, 27, 28, 37, 38, 42, 48, 49];

pub const PREDEFINED_REALDEVICE_PRIMARIES: &[[f64; 8]] = &[
    [0.693, 0.304, 0.208, 0.761, 0.1467, 0.0527, 0.3127, 0.329],
    [0.6867, 0.3085, 0.231, 0.69, 0.1489, 0.0638, 0.3127, 0.329],
    [0.6781, 0.3189, 0.2365, 0.7048, 0.141, 0.0489, 0.3127, 0.329],
    [0.68, 0.32, 0.265, 0.69, 0.15, 0.06, 0.3127, 0.329],
    [0.7042, 0.294, 0.2271, 0.725, 0.1416, 0.0516, 0.3127, 0.329],
    [0.6745, 0.310, 0.2212, 0.7109, 0.152, 0.0619, 0.3127, 0.329],
    [
        0.6805, 0.3191, 0.2522, 0.6702, 0.1397, 0.0554, 0.3127, 0.329,
    ],
    [
        0.6838, 0.3085, 0.2709, 0.6378, 0.1478, 0.0589, 0.3127, 0.329,
    ],
    [
        0.6753, 0.3193, 0.2636, 0.6835, 0.1521, 0.0627, 0.3127, 0.329,
    ],
    [
        0.6981, 0.2898, 0.1814, 0.7189, 0.1517, 0.0567, 0.3127, 0.329,
    ],
];

/// Custom target display information
///
/// This block can have varying byte lengths: 5 or 21
/// Depending on the length, the fields parsed default to zero and may not be set.
/// Up to (including):
///     - 5: target_primary_index
///     - 21: target_primary_{red,green,blue,white}_{x,y}
#[repr(C)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde_feature", derive(Deserialize))]
pub struct ExtMetadataBlockLevel10 {
    pub length: u64,
    pub target_display_index: u8,
    pub target_max_pq: u16,
    pub target_min_pq: u16,
    pub target_primary_index: u8,

    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_red_x: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_red_y: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_green_x: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_green_y: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_blue_x: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_blue_y: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_white_x: u16,
    #[cfg_attr(feature = "serde_feature", serde(default))]
    pub target_primary_white_y: u16,
}

impl ExtMetadataBlockLevel10 {
    pub fn parse(reader: &mut BitVecReader, length: u64) -> ExtMetadataBlock {
        let mut block = Self {
            length,
            target_display_index: reader.get_n(8),
            target_max_pq: reader.get_n(12),
            target_min_pq: reader.get_n(12),
            target_primary_index: reader.get_n(8),
            ..Default::default()
        };

        if length > 5 {
            block.target_primary_red_x = reader.get_n(16);
            block.target_primary_red_y = reader.get_n(16);
            block.target_primary_green_x = reader.get_n(16);
            block.target_primary_green_y = reader.get_n(16);
            block.target_primary_blue_x = reader.get_n(16);
            block.target_primary_blue_y = reader.get_n(16);
            block.target_primary_white_x = reader.get_n(16);
            block.target_primary_white_y = reader.get_n(16);
        }

        ExtMetadataBlock::Level10(block)
    }

    pub fn write(&self, writer: &mut BitVecWriter) -> Result<()> {
        self.validate()?;

        writer.write_n(&self.target_display_index.to_be_bytes(), 8);
        writer.write_n(&self.target_max_pq.to_be_bytes(), 12);
        writer.write_n(&self.target_min_pq.to_be_bytes(), 12);
        writer.write_n(&self.target_primary_index.to_be_bytes(), 8);

        if self.length > 5 {
            writer.write_n(&self.target_primary_red_x.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_red_y.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_green_x.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_green_y.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_blue_x.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_blue_y.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_white_x.to_be_bytes(), 16);
            writer.write_n(&self.target_primary_white_y.to_be_bytes(), 16);
        }

        Ok(())
    }

    pub fn validate(&self) -> Result<()> {
        ensure!(!PRESET_TARGET_DISPLAYS.contains(&self.target_display_index));
        ensure!(self.target_max_pq <= MAX_PQ_LUMINANCE);
        ensure!(self.target_min_pq <= MAX_PQ_LUMINANCE);

        if self.length > 5 {
            ensure!(self.target_primary_index == 255);
            ensure!(self.target_primary_red_x > 0);
            ensure!(self.target_primary_red_y > 0);
            ensure!(self.target_primary_green_x > 0);
            ensure!(self.target_primary_green_y > 0);
            ensure!(self.target_primary_blue_x > 0);
            ensure!(self.target_primary_blue_y > 0);
            ensure!(self.target_primary_white_x > 0);
            ensure!(self.target_primary_white_y > 0);
        } else {
            ensure!(self.target_primary_index != 255);
        }

        Ok(())
    }

    pub fn set_from_primaries(&mut self, primaries: &ColorPrimaries) {
        self.target_primary_red_x = primaries.red_x;
        self.target_primary_red_y = primaries.red_y;
        self.target_primary_green_x = primaries.green_x;
        self.target_primary_green_y = primaries.green_y;
        self.target_primary_blue_x = primaries.blue_x;
        self.target_primary_blue_y = primaries.blue_y;
        self.target_primary_white_x = primaries.white_x;
        self.target_primary_white_y = primaries.white_y;
    }
}

impl ExtMetadataBlockInfo for ExtMetadataBlockLevel10 {
    fn level(&self) -> u8 {
        10
    }

    fn bytes_size(&self) -> u64 {
        self.length
    }

    fn required_bits(&self) -> u64 {
        match self.length {
            5 => 40,
            21 => 168,
            _ => unreachable!(),
        }
    }

    fn sort_key(&self) -> (u8, u16) {
        (self.level(), self.target_display_index as u16)
    }
}

impl Default for ExtMetadataBlockLevel10 {
    fn default() -> Self {
        Self {
            length: 5,
            target_display_index: 20,
            target_max_pq: 2081,
            target_min_pq: 0,
            target_primary_index: 2,
            target_primary_red_x: 0,
            target_primary_red_y: 0,
            target_primary_green_x: 0,
            target_primary_green_y: 0,
            target_primary_blue_x: 0,
            target_primary_blue_y: 0,
            target_primary_white_x: 0,
            target_primary_white_y: 0,
        }
    }
}

#[cfg(feature = "serde_feature")]
impl Serialize for ExtMetadataBlockLevel10 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let name = "ExtMetadataBlockLevel9";
        let fields_count = match self.length {
            5 => 5,
            21 => 13,
            _ => unreachable!(),
        };

        let mut state = serializer.serialize_struct(name, fields_count)?;

        state.serialize_field("length", &self.length)?;
        state.serialize_field("target_display_index", &self.target_display_index)?;
        state.serialize_field("target_max_pq", &self.target_max_pq)?;
        state.serialize_field("target_min_pq", &self.target_min_pq)?;
        state.serialize_field("target_primary_index", &self.target_primary_index)?;

        if self.length > 5 {
            state.serialize_field("target_primary_red_x", &self.target_primary_red_x)?;
            state.serialize_field("target_primary_red_y", &self.target_primary_red_y)?;
            state.serialize_field("target_primary_green_x", &self.target_primary_green_x)?;
            state.serialize_field("target_primary_green_y", &self.target_primary_green_y)?;
            state.serialize_field("target_primary_blue_x", &self.target_primary_blue_x)?;
            state.serialize_field("target_primary_blue_y", &self.target_primary_blue_y)?;
            state.serialize_field("target_primary_white_x", &self.target_primary_white_x)?;
            state.serialize_field("target_primary_white_y", &self.target_primary_white_y)?;
        }

        state.end()
    }
}
