use std::error::Error;
use std::f32::consts::PI;
use std::fmt::{Display, Formatter};
use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD;
use crate::colors::color_scheme;

pub mod colors;

pub trait NexradRenderExt {
    fn render(&self, elevation: usize, product: &str, image_size: usize) -> Result<Vec<(u8, u8, u8)>, RenderError>;
}

impl NexradRenderExt for nexrad2::Nexrad2Chunk {
    fn render(&self, elevation: usize, product: &str, image_size: usize) -> Result<Vec<(u8, u8, u8)>, RenderError> {
        let mut pixel_data = vec![[0.0, 0.0, 0.0]; image_size * image_size];

        let center = (image_size as f64) / 2.0;
        let px_per_km = (image_size as f64) / 2.0 / 920.0;

        let radials = self.elevations.get(&elevation).ok_or(RenderError::ElevationMissing)?;

        let moment_range = radials[0].available_data.get("REF").unwrap().gdm.data_moment_range;
        let first_gate_px = moment_range as f32 / 1000.0 * px_per_km as f32;
        let gate_interval_km = radials[0].available_data.get("REF").unwrap().gdm.data_moment_range_sample_interval as f64 / 1000.0;
        println!("{}", gate_interval_km);
        let gate_width_px = gate_interval_km * px_per_km as f64;
        println!("{} {}", px_per_km, gate_width_px);

        for radial in radials {
            let mut azimuth_angle = radial.header.azimuth_angle - 90.0;
            if azimuth_angle < 0.0 {
                azimuth_angle += 360.0;
            }

            let azimuth_spacing = if radial.header.azimuth_resolution_spacing == 1 { 0.5 } else { 1.0 };

            let mut azimuth = azimuth_angle.floor();

            if azimuth < (azimuth_angle + azimuth_spacing).floor() {
                azimuth += azimuth_spacing;
            }

            println!("azimuth: {}", azimuth);

            let start_angle = azimuth * (PI / 180.0);

            println!("azimuth, radians: {}", start_angle);

            let mut distance = first_gate_px;

            let data_moment = radials[0].available_data.get(product).ok_or(RenderError::ProductMissing)?;

            let gates = data_moment.scaled_data();

            for gate in gates {
                if gate != MOMENT_DATA_BELOW_THRESHOLD {
                    let mut pixel_x = (center as f32 + start_angle.cos() * distance).round() as usize;
                    let mut pixel_y = (center as f32 + start_angle.sin() * distance).round() as usize;

                    if pixel_x >= image_size {
                        pixel_x = image_size-1;
                    }
                    if pixel_y >= image_size {
                        pixel_y = image_size-1;
                    }

                    if pixel_y * image_size + pixel_x > (image_size * image_size) {
                        println!("{} {}", pixel_x, pixel_y);
                    }

                    pixel_data[pixel_y * image_size + pixel_x] = color_scheme(product, gate);
                    //println!("{} {} {}", pixel_x, pixel_y, gate);
                    //println!("not below threshold! {} {}", pixel_x, pixel_y);
                    //println!("{}", gate_width_px);
                } else {
                    let mut pixel_x = (center as f32 + start_angle.cos() * distance).round() as usize;
                    let mut pixel_y = (center as f32 + start_angle.sin() * distance).round() as usize;

                    if pixel_x >= image_size {
                        pixel_x = image_size-1;
                    }
                    if pixel_y >= image_size {
                        pixel_y = image_size-1;
                    }

                    if pixel_y * image_size + pixel_x > (image_size * image_size) {
                        println!("{} {}", pixel_x, pixel_y);
                    }

                    pixel_data[pixel_y * image_size + pixel_x] = [0.3, 0.2, 0.1];
                    //println!("not below threshold! {} {}", pixel_x, pixel_y);
                    //println!("{}", gate_width_px);
                }

                distance += gate_width_px as f32;
                azimuth += azimuth_spacing;
            }
        }

        Ok(pixel_data.iter().map(|u| ((u[0] * 255.0).round() as u8, (u[1] * 255.0).round() as u8, (u[2] * 255.0).round() as u8)).collect())
    }
}

#[derive(Debug)]
pub enum RenderError {
    ElevationMissing,
    ProductMissing
}
impl Display for RenderError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ElevationMissing => write!(f, "elevation missing"),
            Self::ProductMissing => write!(f, "product missing")
        }
    }
}
impl Error for RenderError {}