use anyhow::{Ok, Result};
use femtovg::{self};
use geo_types::{line_string, LineString};
use image::RgbImage;
use ndarray::parallel::prelude::*;
use ndarray::{Array2, ArrayView2};
use num_traits::{Num, AsPrimitive, FromPrimitive};
pub mod offscreen_renderer;

use crate::{
    coords::Mapper,
    data::{Radar2d, RadarData2d},
};

pub struct Color(femtovg::Color);

impl Color {
    pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self(femtovg::Color::rgba(r, g, b, a))
    }
}

impl From<femtovg::Color> for Color {
    fn from(value: femtovg::Color) -> Self {
        Self(value)
    }
}

pub trait Pipeline<T> {
    type Output;
    fn run(&self, input: T) -> Result<Self::Output>;
}
pub struct ProjPipe<'a> {
    pub mapper: &'a Mapper,
}

impl<'a> ProjPipe<'a> {
    pub fn new(mapper: &'a Mapper) -> Self {
        Self { mapper }
    }
}

impl<'a, 'b: 'a, T, Raw> Pipeline<&'b RadarData2d<T, Raw>> for ProjPipe<'a>
where
    T: Num + Clone + PartialEq + PartialOrd,
    Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
    type Output = Array2<LineString>;

    fn run(&self, input: &'b RadarData2d<T, Raw>) -> Result<Self::Output> {
        let dim1 = input.dim1.view();
        let dim2 = input.dim2.view();

        let shape = input.data.shape();
        let mut polygons = Vec::with_capacity(dim1.len() * dim2.len());

        let d1_dpi = dim1[1] - dim1[0];
        let d2_dpi = dim2[1] - dim2[0];

        for d1 in dim1 {
            for d2 in dim2 {
                let line: LineString = vec![
                    (*d1, *d2),
                    (*d1 + d1_dpi, *d2),
                    (*d1 + d1_dpi, *d2 + d2_dpi),
                    (*d1, *d2 + d2_dpi),
                    (*d1, *d2),
                ]
                .into();
                let projed_polygon = self.mapper.ring_map(&line)?;
                polygons.push(projed_polygon);
            }
        }

        Ok(Array2::from_shape_vec([shape[0], shape[1]], polygons)?)
    }
}

pub struct ShadePipe<T: Num> {
    colors: Vec<Color>,
    levels: Vec<T>,
}

struct ShaderPrepare(Array2<LineString>);

impl<T: Num + PartialOrd> ShadePipe<T> {
    pub fn new(levels: Vec<T>, colors: Vec<Color>) -> Self {
        Self { colors, levels }
    }

    pub fn get_color(&self, v: T) -> &Color {
        let len = self.levels.len();

        let mut left = 0;
        let mut right = len - 1;

        while left < right - 1 {
            let middle = (right + left) / 2;
            if v > self.levels[middle] {
                left = middle;
            } else {
                right = middle;
            }
        }

        &self.colors[left]
    }
}

impl<'a, T, Raw> Pipeline<&'a RadarData2d<T, Raw>> for ShadePipe<T>
where
    T: Num + PartialEq + PartialOrd +  Clone + FromPrimitive,
    Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
    type Output = Array2<Option<femtovg::Color>>;

    fn run(&self, input: &'a RadarData2d<T, Raw>) -> Result<Array2<Option<femtovg::Color>>> {
        let data = input.data.view();

        let result = data.mapv(|v| {
            if T::from_i8(-125).unwrap() == v  {
                None
            } else {
                let color = self.get_color(v);
                Some(color.0)
            }
        });

        Ok(result)
    }
}
