use std::collections::HashMap;
use std::fmt::{Debug, Formatter};


pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2;
pub const MOMENT_DATA_BELOW_THRESHOLD: f32 = 999.0;
pub const MOMENT_DATA_FOLDED: f32 = 998.0;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Message31Header {
    pub radar_identifier: String,
    pub collection_time: u32,
    pub date: u16,
    pub azimuth_number: u16,
    pub azimuth_angle: f32,
    pub compression_indicator: u8,
    pub spare: u8,
    pub radial_length: u16,
    pub azimuth_resolution_spacing: u8,
    pub radial_status: u8,
    pub elevation_number: u8,
    pub cut_sector_number: u8,
    pub elevation_angle: f32,
    pub radial_spot_blanking_status: u8,
    pub azimuth_indexing_mode: u8,
    pub data_block_count: u16
}

pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct VolumeData {
    pub datablock_type: String,
    pub data_name: String,
    pub lrtup: u16,
    pub version_major: u8,
    pub version_minor: u8,
    pub lat: f32,
    pub long: f32,
    pub site_height: u16,
    pub feedhorn_height: u16,
    pub calibration_constant: f32,
    pub shvtx_power_hor: f32,
    pub shvtx_power_vert: f32,
    pub system_diff_ref: f32,
    pub initial_system_diff_phase: f32,
    pub vcp: u16,
    pub processing_status: u16
}

pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct ElevationData {
    pub datablock_type: String,
    pub data_name: String,
    pub lrtup: u16,
    pub atmos: [u8; 2],
    pub calib_const: f32
}

pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct RadialData {
    pub datablock_type: String,
    pub data_name: String,
    pub lrtup: u16,
    pub unambiguous_range: u16,
    pub noise_level_horz: f32,
    pub noise_level_vert: f32,
    pub nyquist_velocity: u16,
    pub radial_flags: u16,
    pub calib_const_horz_channel: f32,
    pub calib_const_vert_channel: f32
}

pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4;

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct GenericDataMoment {
    pub datablock_type: String,
    pub data_name: String,
    pub reserved: u32,
    pub data_moment_gate_count: u16,
    pub data_moment_range: u16,
    pub data_moment_range_sample_interval: u16,
    pub tover: u16,
    pub snr_threshold: u16,
    pub control_flags: u8,
    pub data_word_size: u8,
    pub scale: f32,
    pub offset: f32
}

#[derive(PartialEq, Clone)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct DataMoment {
    pub gdm: GenericDataMoment,
    pub data: Vec<u8>
}

impl Debug for DataMoment {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "DataMoment {{gdm: {:?}, data: <{} bytes omitted>}}", self.gdm, self.data.len())
    }
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Message31 {
    pub header: Message31Header,
    pub volume_info: Option<VolumeData>,
    pub elevation_info: Option<ElevationData>,
    pub radial_info: Option<RadialData>,
    pub available_data: HashMap<String, DataMoment>
}

impl DataMoment {
    #[profiling::function]
    pub fn scaled_data(&self) -> Vec<f32> {
        let mut gates = vec![0u16; self.gdm.data_moment_gate_count as usize];

        if self.gdm.data_word_size == 8 {
            for byte in &self.data {
                gates.push(*byte as u16);
            }
        } else if self.gdm.data_word_size == 16 {
            for chunk in self.data.chunks(2) {
                gates.push(u16::from_be_bytes(chunk.try_into().unwrap()));
            }
        }

        let mut scaled_data = vec![];

        for gate in &gates {
            if *gate == 0 {
                // below threshold
                scaled_data.push(MOMENT_DATA_BELOW_THRESHOLD);
            } else if *gate == 1 {
                // folded
                scaled_data.push(MOMENT_DATA_FOLDED);
            } else {
                scaled_data.push(scale_uint(*gate, self.gdm.offset, self.gdm.scale))
            }
        }

        scaled_data
    }
}

#[profiling::function]
fn scale_uint(gate: u16, offset: f32, scale: f32) -> f32 {
    if scale == 0.0 {
        gate as f32
    } else {
        (gate as f32 - offset) / scale
    }
}