use crate::colors::color_scheme;
use crate::mode::Mode;
use crate::mode::Mode::{
    ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity,
    RadarInoperative, Reflectivity, SpectrumWidth, Velocity,
};
use crate::rescaleCanvas;
use crate::scope::ScopeState;
use crate::utils::parse_date;
use crate::vcp::vcp_string;
use chrono::{DateTime, Timelike, Utc};
use itertools::Itertools;
use log::{debug, info};
use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD;
use std::f64::consts::PI;
use wasm_bindgen::JsValue;

pub const TEXT_COLOR_RED: &str = "#ef0000";
pub const TEXT_COLOR_GREEN: &str = "#4af626";
pub const TEXT_COLOR_WHITE: &str = "#dedede";

pub const RANGE: usize = 50;

pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String {
    format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end)
}
pub fn hms(t: DateTime<Utc>) -> (usize, usize, usize) {
    (t.hour() as usize, t.minute() as usize, t.second() as usize)
}
pub fn zulu(t: DateTime<Utc>) -> String {
    let t2 = hms(t);
    time(t2.0, t2.1, t2.2, "Z")
}

pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
    // DPI rescaling
    rescaleCanvas(&state.render_state.canvas, &state.render_state.context);

    let ctx = &state.render_state.context;
    let canvas = &state.render_state.canvas;

    ctx.set_font(&format!("{}px monospace", state.prefs.fcs));

    // clear canvas
    ctx.reset_transform()?;
    ctx.clear_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64);

    // render out the background
    ctx.set_fill_style(&JsValue::from_str("black"));
    ctx.fill_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64);

    let time = Utc::now();

    ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));

    // render out the scope id line
    if state.show_ui {
        ctx.fill_text(
            &format!(
                "NEXRAD {} {}",
                state
                    .ar2
                    .as_ref()
                    .map(|u| u.volume_header_record.icao.as_str())
                    .unwrap_or("INOP"),
                zulu(time)
            ),
            50.0,
            50.0,
        )?;
        if state.ar2.is_none() {
            // inop alert
            ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
            ctx.fill_text(
                "RADAR INOPERATIVE NO DATA LOADED",
                50.0,
                50.0 + (state.prefs.fcs as f64),
            )?;
            ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
        }
        if state.new_data_available {
            // inop alert
            ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
            ctx.fill_text(
                "NEW DATA AVAIL RLD RQD",
                50.0,
                50.0 + (state.prefs.fcs as f64) * 2.0,
            )?;
            ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
        }
    }

    // render the command buffer
    for (line_no, line) in state.command_buf.split('\n').enumerate() {
        ctx.fill_text(
            line,
            50.0,
            (canvas.height() / 3) as f64 + (state.prefs.fcs * line_no) as f64,
        )?;
    }

    // render the radar site info
    if state.show_ui {
        ctx.set_text_align("right");
        ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?;
        if let Some(ar2) = &state.ar2 {
            ctx.fill_text(
                &format!(
                    "{} VCP {} {}",
                    ar2.volume_header_record.icao,
                    ar2.meta_rda_status_data.vcp,
                    vcp_string(ar2.meta_rda_status_data.vcp)
                ),
                (canvas.width() - 50) as f64,
                50.0 + (state.prefs.fcs as f64),
            )?;
            let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
            let delay = time - recorded;

            let seconds = delay.num_seconds() % 60;
            let minutes = (delay.num_seconds() / 60) % 60;
            let hours = delay.num_seconds() / 3600;

            ctx.fill_text(
                &format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds),
                (canvas.width() - 50) as f64,
                50.0 + (state.prefs.fcs as f64 * 2.0),
            )?;
        } else {
            ctx.fill_text(
                "SITE INFORMATION UNAVAILABLE",
                (canvas.width() - 50) as f64,
                50.0 + state.prefs.fcs as f64,
            )?;
            ctx.fill_text(
                "DELAY INFORMATION UNAVAILABLE",
                (canvas.width() - 50) as f64,
                50.0 + (state.prefs.fcs * 2) as f64,
            )?;
        }

        // mode info
        ctx.fill_text(
            "MODE",
            (canvas.width() - 50) as f64,
            (canvas.height() / 3) as f64,
        )?;
        let modelines = [
            [Reflectivity, Velocity, SpectrumWidth].as_slice(),
            [
                DifferentialReflectivity,
                DifferentialPhase,
                CorrelationCoefficient,
            ]
            .as_slice(),
            [ClutterFilterPowerRemoved, RadarInoperative].as_slice(),
        ];

        let mut available_modes = vec![];

        if let Some(ar2) = &state.ar2 {
            let types = ar2.elevations.get(&state.selected_elevation).unwrap();
            available_modes = types[0].available_data.keys().collect();
        }

        for (line_no, line) in modelines.iter().enumerate() {
            let x = (canvas.width() - 50) as f64;
            let y = (canvas.height() / 3) as f64 + (state.prefs.fcs * (line_no + 1)) as f64;
            for (item_no, item) in line.iter().enumerate() {
                let pad_start = item_no;
                let pad_end = 2 - item_no;
                let pad_start = " ".repeat(pad_start * 6);
                let pad_end = " ".repeat(pad_end * 6);
                let middle = if state.scope_mode == *item {
                    item.selected()
                } else {
                    item.unselected()
                };

                if state.scope_mode == *item {
                    // display selected color
                    if *item == RadarInoperative {
                        ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
                        ctx.fill_text(">RADR INOP<", x, y)?;
                        ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
                        continue;
                    } else {
                        ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
                    }
                } else if *item == RadarInoperative {
                    continue;
                }

                if available_modes.contains(&&item.rname().to_string()) {
                    ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?;
                }

                if state.scope_mode == *item {
                    // reset
                    ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
                }
            }
        }

        ctx.fill_text(
            "ELEVATIONS",
            (canvas.width() - 50) as f64,
            ((canvas.height() / 7) * 4) as f64,
        )?;
        if let Some(ar2) = &state.ar2 {
            let mut line_no = 0;

            for chunk in &ar2
                .elevations
                .iter()
                .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
                .enumerate()
                .chunks(4)
            {
                for (no_in_line, (elevation_no, scans)) in chunk {
                    if state.selected_elevation != *elevation_no {
                        ctx.fill_text(
                            &format!(
                                " {:0>3} {}",
                                elevation_no,
                                " ".repeat(5 * (3 - (no_in_line % 4)))
                            ),
                            (canvas.width() - 50) as f64,
                            ((canvas.height() / 7) * 4) as f64
                                + (state.prefs.fcs * (line_no + 1)) as f64,
                        )?;
                    } else {
                        ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
                        ctx.fill_text(
                            &format!(
                                ">{:0>3}<{}",
                                elevation_no,
                                " ".repeat(5 * (3 - (no_in_line % 4)))
                            ),
                            (canvas.width() - 50) as f64,
                            ((canvas.height() / 7) * 4) as f64
                                + (state.prefs.fcs * (line_no + 1)) as f64,
                        )?;
                        ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
                    }
                }
                line_no += 1;
            }
        } else {
            ctx.fill_text(
                "ELEVATION INFORMATION UNAVAILABLE",
                (canvas.width() - 50) as f64,
                ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64,
            )?;
        }
    }

    // ACTUAL DATA RENDERING
    if let Some(ar2) = &state.ar2 {
        let px_per_km = (canvas.height() / RANGE as u32) as f32;
        let xc = canvas.width() / 2;
        let yc = canvas.height() / 2;

        let radials = ar2.elevations.get(&state.selected_elevation).unwrap();

        let first_gate_px = (radials[0]
            .available_data
            .get("REF")
            .expect("reflectivity is missing!")
            .gdm
            .data_moment_range) as f32
            / 1000.0
            * px_per_km;
        let gate_interval_km = (radials[0]
            .available_data
            .get("REF")
            .expect("reflectivity is missing!")
            .gdm
            .data_moment_range_sample_interval) as f32
            / 1000.0;
        let gate_width_px = gate_interval_km * px_per_km;

        for radial in radials {
            /* weird rounding stolen from go-nexrad */
            let mut azimuth_angle = (radial.header.azimuth_angle as f64) - 90.0;
            if azimuth_angle < 0.0 {
                azimuth_angle = 360.0 + azimuth_angle;
            }

            let mut azimuth = azimuth_angle.floor();

            let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
                1 => 0.5,
                _ => 1.0,
            };
            if (azimuth_angle + azimuth_spacing).floor() > azimuth {
                azimuth += azimuth_spacing;
            }
            /* conclude the weird rounding stolen from go-nexrad */

            // Angles specified clockwise in radians
            let start_angle = azimuth * (PI / 180.0);
            let end_angle = azimuth_spacing * (PI / 180.0);

            let mut distance_x = first_gate_px;
            let mut distance_y = first_gate_px;

            // line width
            // line cap

            let gates = radial
                .available_data
                .get(state.scope_mode.rname())
                .expect("selected unavailable product")
                .scaled_data();

            let num_gates = gates.len();

            for (num, value) in gates.iter().enumerate() {
                if *value != MOMENT_DATA_BELOW_THRESHOLD {
                    ctx.move_to(
                        xc as f64 + start_angle.cos() * distance_x as f64,
                        yc as f64 + start_angle.sin() * distance_y as f64,
                    );

                    ctx.begin_path();

                    if num == 0 {
                        ctx.ellipse(
                            xc as f64,
                            yc as f64,
                            distance_x as f64,
                            distance_y as f64,
                            0.0,
                            start_angle - 0.001,
                            end_angle + 0.001,
                        )?;
                    } else if num == num_gates - 1 {
                        ctx.ellipse(
                            xc as f64,
                            yc as f64,
                            distance_x as f64,
                            distance_y as f64,
                            0.0,
                            start_angle,
                            end_angle,
                        )?;
                    } else {
                        ctx.ellipse(
                            xc as f64,
                            yc as f64,
                            distance_x as f64,
                            distance_y as f64,
                            0.0,
                            start_angle,
                            end_angle + 0.001,
                        )?;
                    }

                    ctx.set_stroke_style(&JsValue::from_str(&format!(
                        "{}px {}",
                        gate_width_px + 1.0,
                        color_scheme(state.scope_mode, *value)
                    )));
                    ctx.stroke();
                }

                distance_x += gate_width_px;
                distance_y += gate_width_px;

                azimuth += azimuth_spacing;
            }
        }
    }

    Ok(())
}
