use std::collections::{BTreeMap, HashMap};
use std::f64::consts::PI;
use std::io::{BufWriter, Cursor, Read};
use std::sync::Arc;
use std::time::SystemTime;
use flate2::read::GzDecoder;
use png::{BitDepth, ColorType, Encoder};
use tokio::sync::RwLock;
use wxbox_grib2::GribMessage;
use wxbox_grib2::wgs84::LatLong;
use wxbox_pal::{Color, ColorPalette, Palette};
use crate::pixmap::Pixmap;

pub async fn needs_reload(lct: &RwLock<HashMap<String, SystemTime>>, lutkey: &String, valid_for: u64) -> bool {
    let lct_reader = lct.read().await;

    if let Some(t) = lct_reader.get(lutkey) {
        let dur = SystemTime::now().duration_since(*t).expect("time went backwards").as_secs();
        if dur > valid_for {
            return true;
        }
    } else {
        return true;
    }

    false
}

pub async fn load(url: &str, is_gzipped: bool) -> Vec<u8> {
    let f = reqwest::get(url).await.unwrap();
    let data = f.bytes().await.unwrap().to_vec();

    let out;

    if is_gzipped {
        let b = Cursor::new(data);
        let mut decoder = GzDecoder::new(b);

        let mut data = vec![];
        decoder.read_to_end(&mut data).expect("decoding failure");

        out = data;
    } else {
        out = data;
    }

    out
}


pub async fn reload_if_required(from: &str, needs_gzip: bool, valid_for: u64, lut_key: &String, lct_cache: &RwLock<HashMap<String, SystemTime>>, lut_cache: &RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>) {
    if needs_reload(&lct_cache, lut_key, valid_for).await {
        let mut lct_writer = lct_cache.write().await;

        let message = load(from, needs_gzip).await;
        let grib = GribMessage::new(Cursor::new(message)).unwrap();

        lut_cache.write().await.insert(lut_key.clone(), Arc::new(RwLock::new(grib)));
        lct_writer.insert(lut_key.clone(), SystemTime::now());
    }
}

const TWO_PI: f64 = PI * 2.0;
const HALF_PI: f64 = PI / 2.0;

pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palette, map: &Arc<RwLock<GribMessage>>, missing: Option<f64>, range_folded: Option<f64>, no_coverage: Option<f64>) -> Vec<u8> {
    let mut image: Pixmap = Pixmap::new();

    let denominator = 2.0_f64.powi(z) * tilesize as f64;
    let xtile_times_tilesize = xtile * tilesize as f64;
    let ytile_times_tilesize = ytile * tilesize as f64;

    {
        let message = map.read().await;
        for x in 0..tilesize {
            for y in 0..tilesize {
                let tx = (xtile_times_tilesize + x as f64) / denominator;
                let ty = (ytile_times_tilesize + y as f64) / denominator;

                let lon = (TWO_PI * tx - PI).to_degrees();
                let lat = ((PI - TWO_PI * ty).exp().atan() * 2.0_f64 - HALF_PI).to_degrees();

                let nearest = message.value_for(LatLong {
                    lat,
                    long: lon
                }).map(|u| u as f64);

                let color = match nearest {
                    Some(c) if Some(c) == no_coverage => Color { red: 0, green: 0, blue: 0, alpha: 30 },
                    Some(c) if Some(c) == missing => Color { red: 0, green: 0, blue: 0, alpha: 0 },
                    Some(c) if Some(c) == range_folded => Color { red: 141, green: 0, blue: 160, alpha: 0 },
                    Some(value_at_pos) => {
                        pal.colorize(value_at_pos)
                    },
                    None => Color { red: 0, green: 0, blue: 0, alpha: 30 }
                };

                image.set(y, x, color);
            }
        }
    }

    let mut buf: Vec<u8> = vec![];
    // borrow checker insanity
    {
        let mut cur: Cursor<_> = Cursor::new(&mut buf);
        let w = &mut BufWriter::new(&mut cur);
        let mut encoder = Encoder::new(w, tilesize as u32, tilesize as u32);
        encoder.set_color(ColorType::Rgba);
        encoder.set_depth(BitDepth::Eight);
        encoder.set_source_gamma(png::ScaledFloat::from_scaled(45455));
        encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
        let source_chromaticities = png::SourceChromaticities::new(
            (0.31270, 0.32900),
            (0.64000, 0.33000),
            (0.30000, 0.60000),
            (0.15000, 0.06000)
        );
        encoder.set_source_chromaticities(source_chromaticities);
        let mut writer = encoder.write_header().unwrap();
        writer.write_image_data(&image.to_raw()).expect("failed to encode png");
        writer.finish().unwrap();
    }

    buf
}

#[macro_export]
macro_rules! grib2_handler {
    (mount $f:ident, at: $path:expr, from: $from:expr, needs_gzip: $needs_gzip:expr, valid_for: $valid_for:expr, palette: $pal:expr, missing: $missing:expr, range_folded: $rf:expr, no_coverage: $nc:expr) => {
        #[::actix_web::get($path)]
        async fn $f(path: ::actix_web::web::Path<(i32,u32,u32)>, data: ::actix_web::web::Data<crate::AppState>) -> ::actix_web::HttpResponse {
            crate::sources::grib2::reload_if_required(
                $from,
                $needs_gzip,
                $valid_for,
                &String::from($path),
                &data.grib2_cache_timestamps,
                &data.grib2_cache
            ).await;
            let lct_reader = data.grib2_cache_timestamps.read().await;
            if let Some(map) = data.grib2_cache.read().await.get($path) {
                ::actix_web::HttpResponse::Ok()
                    .insert_header(::actix_web::http::header::ContentType(::mime::IMAGE_PNG))
                    .insert_header(("x-wxbox-tiler-data-valid-time", lct_reader.get($path).expect("impossible").duration_since(::std::time::UNIX_EPOCH).expect("time went backwards").as_secs().to_string()))
                    .insert_header(("Access-Control-Allow-Origin", "*"))
                    .insert_header(("Access-Control-Expose-Headers", "*"))
                    .insert_header(("Access-Control-Allow-Headers", "*"))
                    .body(crate::sources::grib2::render(path.1 as f64, path.2 as f64, path.0, 256, $pal, map, $missing, $rf, $nc).await)
            } else {
                ::actix_web::HttpResponse::new(::actix_web::http::StatusCode::NOT_FOUND)
            }
        }
    };
}