use crate::errors::*;
use crate::utils::geo_tools::calculate_coverage;
use ndarray::ArrayD;
pub mod error;
mod parser;

use chrono::Utc;

macro_rules! block_prepare {
    ($data: ident, $_ty: ty, $shape:ident, $(($branch: tt),)+) => {
        match $data {
            $(
                $branch(data) => ArrayD::from_shape_vec($shape.as_slice(), data.into_iter().map(|b| b as $_ty).collect::<Vec<$_ty>>()).unwrap(),
            )+
        }
    };
}

macro_rules! block_data_type_prepare {
    ($data: ident, $($branch: tt => $data_type: tt,)+) => {
        match $data {
            $(
                $branch => $data_type,
            )+
            _ => DataType::Unknown
        }
    };
}

#[derive(Debug, Clone)]
pub struct Data {
    pub filetime: chrono::DateTime<Utc>,
    pub radar_info: RadarInfo,
    pub blocks: Vec<Block>,
}

#[derive(Debug, Clone, Copy)]
pub enum DataType {
    ET,
    VIL,
    EB,
    DBZ,
    VEl,
    ZDR,
    PHIDP,
    KDP,
    CC,
    HCA,
    QPE,
    QPF,
    Unknown,
}

#[derive(Debug, Clone)]
pub struct Block {
    pub data: ArrayD<f32>,
    pub data_type: DataType,
    pub coord_type: CoordType,
    pub min_value: f32,
    pub max_value: f32,
    pub unvalid_value: f32,
}

#[derive(Debug, Clone)]
pub enum CoordType {
    Polar {
        azimuth_range: [f64; 2],
        r_range: [f64; 2],
        elevation_range: [f64; 2],
        azimuth: Vec<f32>,
        r: Vec<f32>,
        elevation: Vec<f32>,
    },
    Cartesian {
        lat_range: [f64; 2],
        lon_range: [f64; 2],
        hgt_range: [f64; 2],
        lat: Vec<f32>,
        lon: Vec<f32>,
        hgt: Vec<f32>,
    },

    Other,
}

#[derive(Debug, Clone)]
pub struct RadarInfo {
    pub lat_range: [f32; 2],
    pub lon_range: [f32; 2],
    pub center: [f32; 3],
}

impl Data {
    pub fn from_path(path: impl AsRef<std::path::Path>) -> Result<Self> {
        let record = parser::Record::parse_from_path(path)?;

        let mut radar_loc: [Option<f64>; 3] = [None, None, None];

        let blocks = record
            .blocks
            .into_iter()
            .map(|b| {
                let vec_data = b.data;
                use parser::ValueResult::*;

                let info = b.info;

                radar_loc = [info.radar_alt, info.radar_lat, info.radar_lon];
                let shape = info
                    .dimension_size
                    .into_iter()
                    .map(|s| s as usize)
                    .collect::<Vec<usize>>();

                let new_data = block_prepare!(
                    vec_data,
                    f32,
                    shape,
                    (I64),
                    (F64),
                    (I32),
                    (F32),
                    (I16),
                    (U64),
                    (U32),
                    (I8),
                    (U8),
                );

                let dim_len = info.dimension_values.len();
                let dim_last = info.dimension_values.get(dim_len - 1).unwrap();
                let dim_minus_one = info.dimension_values.get(dim_len - 2).unwrap();
                let (dim_minus_two_range, dim_minus_two) = if dim_len == 3 {
                    let hgt = info.dimension_values.get(dim_len - 3).unwrap();
                    (
                        [hgt[0], hgt[hgt.len() - 1]],
                        hgt.iter().map(|v| *v as f32).collect(),
                    )
                } else {
                    ([0.0, 0.0], vec![])
                };

                let dim_last_range = [dim_last[0], dim_last[dim_last.len() - 1]];
                let dim_minus_one_range =
                    [dim_minus_one[0], dim_minus_one[dim_minus_one.len() - 1]];

                let coord_type = if info.dimension_des.contains(&format!("lat")) {
                    CoordType::Cartesian {
                        lat_range: dim_minus_one_range,
                        lon_range: dim_last_range,
                        hgt_range: dim_minus_two_range,
                        lat: dim_minus_one.iter().map(|v| *v as f32).collect(),
                        lon: dim_last.iter().map(|v| *v as f32).collect(),
                        hgt: dim_minus_two,
                    }
                } else if info.dimension_des.contains(&format!("ele"))
                    || info.dimension_des.contains(&format!("el"))
                    || info.dimension_des.contains(&format!("elevation"))
                {
                    CoordType::Polar {
                        azimuth_range: dim_minus_one_range,
                        r_range: dim_last_range,
                        elevation_range: dim_minus_two_range,
                        azimuth: dim_minus_one.iter().map(|v| *v as f32).collect(),
                        r: dim_last.iter().map(|v| *v as f32).collect(),
                        elevation: dim_minus_two,
                    }
                } else {
                    CoordType::Other
                };

                use DataType::*;
                let valuetype = info.value_type.as_str();

                let data_type = block_data_type_prepare!(valuetype,
                    "ET" => ET,
                    "VIL" => VIL,
                    "EB" => EB,
                    "DBZ" =>DBZ,
                    "CR" => DBZ,
                    "R" => DBZ,
                    "V" => VEl,
                    "ZDR" => ZDR,
                    "PHIDP" => PHIDP,
                    "KDP" => KDP,
                    "CC" => CC,
                    "HCA" => HCA,
                    "QPE" => QPE,
                    "QPF" => QPF,
                    "FR" => DBZ,
                );

                Block {
                    data: new_data,
                    data_type,
                    coord_type,
                    min_value: 0.0,
                    max_value: 204.0,
                    unvalid_value: info.fill_value as f32,
                }
            })
            .collect::<Vec<_>>();

        let radar_info = match blocks.first().unwrap().coord_type {
            CoordType::Cartesian {
                lat_range,
                lon_range,
                ..
            } => RadarInfo {
                lat_range: [lat_range[0] as f32, lat_range[1] as f32],
                lon_range: [lon_range[0] as f32, lon_range[1] as f32],
                center: [
                    lat_range[0] as f32 + (lat_range[1] - lat_range[0]) as f32 / 2.0,
                    lon_range[0] as f32 + (lon_range[1] - lon_range[0]) as f32 / 2.0,
                    0.0,
                ],
            },
            CoordType::Polar { r_range, .. } => {
                let (min_lat, max_lat, min_lon, max_lon) =
                    calculate_coverage(radar_loc[1].unwrap(), radar_loc[2].unwrap(), r_range[1]);

                RadarInfo {
                    lat_range: [min_lat as f32, max_lat as f32],
                    lon_range: [min_lon as f32, max_lon as f32],
                    center: [
                        radar_loc[1].unwrap() as f32,
                        radar_loc[2].unwrap() as f32,
                        radar_loc[0].unwrap() as f32,
                    ],
                }
            }
            _ => {
                panic!("Unknown coord type");
            }
        };

        Ok(Self {
            filetime: record.filetime,
            blocks,
            radar_info,
        })
    }

    pub fn get_data_slice(&self, index: usize) -> Option<&[f32]> {
        self.blocks.get(index).map(|b| b.data.as_slice().unwrap())
    }
}

mod test {
    use super::Data;

    #[test]
    fn test_load() {
        // let path = "/Volumes/data6/RadarArray/ShaoXing/radarData/OutputProducts/RadarProducts/FuseDataX/20030506/ZJSXAA_20030506200000_FR.dat.gz";
        let path = "/Users/tsuki/Desktop/ZJSXAA_20230627163400_VIL.dat.gz";

        let data = Data::from_path(path).unwrap();
        println!("{:?}", data);
    }
}
