#![allow(clippy::excessive_precision)]
#![allow(dead_code)]

use {
    crate::{
        cmm::cmm_eotf::Eotf,
        format::ARGB8888,
        gfx_api::{GfxContext, GfxError, GfxTexture},
        scale::Scale,
        state::State,
        theme::Theme,
        utils::{copyhashmap::CopyHashMap, windows::WindowsExt},
    },
    ahash::AHashSet,
    linearize::{Linearize, StaticMap, static_map},
    std::{cell::Cell, f32::consts::PI, mem, rc::Rc, sync::LazyLock},
    thiserror::Error,
    tiny_skia::{Color, FillRule, Paint, Path, PathBuilder, Pixmap, Transform},
};

#[derive(Default)]
pub struct Icons {
    icons: CopyHashMap<i32, Option<Rc<SizedIcons>>>,
}

#[allow(dead_code)]
#[derive(Copy, Clone, Debug, Linearize)]
pub enum IconState {
    Active,
    Passive,
}

#[allow(dead_code)]
pub struct SizedIcons {
    pub pin_unfocused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
    pub pin_focused_title: StaticMap<IconState, Rc<dyn GfxTexture>>,
    pub pin_attention_requested: StaticMap<IconState, Rc<dyn GfxTexture>>,
}

#[derive(Debug, Error)]
pub enum IconsError {
    #[error("Could not create a pixmap")]
    CreatePixmap,
    #[error("The requested icons size is non-positive")]
    NonPositiveSize,
    #[error("There is no gfx context")]
    NoRenderContext,
    #[error("Could not create texture")]
    CreateTexture(#[source] GfxError),
}

impl Icons {
    pub fn update_sizes(&self, state: &State) {
        let mut sizes = AHashSet::new();
        let height = state.theme.title_height();
        for &(scale, _) in &*state.scales.lock() {
            let [size] = scale.pixel_size([height]);
            if size > 0 {
                sizes.insert(size);
            }
        }
        self.icons.lock().retain(|size, _| sizes.contains(size));
    }

    pub fn clear(&self) {
        self.icons.clear();
    }

    pub fn get(&self, state: &State, scale: Scale) -> Option<Rc<SizedIcons>> {
        let [size] = scale.pixel_size([state.theme.title_height()]);
        if let Some(icons) = self.icons.get(&size) {
            return icons;
        }
        let icons = match self.create(state, size) {
            Ok(i) => Some(i),
            Err(e) => {
                log::error!("Could not create icons: {}", e);
                None
            }
        };
        self.icons.set(size, icons.clone());
        icons
    }

    fn create(&self, state: &State, size: i32) -> Result<Rc<SizedIcons>, IconsError> {
        let Some(ctx) = state.render_ctx.get() else {
            return Err(IconsError::NoRenderContext);
        };
        Ok(Rc::new(create_icons(size, &state.theme, &ctx)?))
    }
}

pub fn create_icons(
    size: i32,
    theme: &Theme,
    ctx: &Rc<dyn GfxContext>,
) -> Result<SizedIcons, IconsError> {
    if size <= 0 {
        return Err(IconsError::NonPositiveSize);
    }
    let size = size as u32;

    let create_pins = |color: crate::theme::Color| {
        let create_pin = |color: Color| {
            let mut paint = Paint::default();
            paint.set_color(color);
            let s = size as f32 / 100.0;
            let transform = Transform::from_scale(s, s);
            let mut pixmap = Pixmap::new(size, size).ok_or(IconsError::CreatePixmap)?;
            pixmap.fill_path(&PIN_PATH, &paint, FillRule::EvenOdd, transform, None);
            upload_pixmap(pixmap, ctx)
        };
        let colors = calculate_accents(color);
        Ok(static_map! {
            IconState::Passive => create_pin(colors[0])?,
            IconState::Active => create_pin(colors[1])?,
        })
    };

    Ok(SizedIcons {
        pin_unfocused_title: create_pins(theme.colors.unfocused_title_background.get())?,
        pin_focused_title: create_pins(theme.colors.focused_title_background.get())?,
        pin_attention_requested: create_pins(theme.colors.attention_requested_background.get())?,
    })
}

fn upload_pixmap(
    pixmap: Pixmap,
    ctx: &Rc<dyn GfxContext>,
) -> Result<Rc<dyn GfxTexture>, IconsError> {
    let width = pixmap.width();
    let height = pixmap.width();
    let bytes = unsafe { mem::transmute::<Vec<u8>, Vec<Cell<u8>>>(pixmap.take()) };
    for chunk in bytes.array_chunks_ext::<4>() {
        let r = chunk[0].get();
        let b = chunk[2].get();
        chunk[0].set(b);
        chunk[2].set(r);
    }
    let tex: Rc<dyn GfxTexture> = ctx
        .clone()
        .shmem_texture(
            None,
            &bytes,
            ARGB8888,
            width as _,
            height as _,
            width as i32 * 4,
            None,
        )
        .map_err(IconsError::CreateTexture)?;
    Ok(tex)
}

static PIN_PATH: LazyLock<Path> = LazyLock::new(|| {
    let cx = 50.0f32;
    let cy = 40.0f32;
    let r = 30.0f32;
    let xx = cx;
    let xy = 90.0f32;
    let d = xy - cy;
    let v1 = r / d * (d * d - r * r).sqrt();
    let v2 = 1.0 / d * (d * d - r * r);

    let mut path = PathBuilder::new();
    path.move_to(cx, cy - r);
    path.arc_cw_to(cx, cy, cx + r, cy);
    path.arc_cw_to(cx, cy, xx + v1, xy - v2);
    path.line_to(xx, xy);
    path.line_to(xx - v1, xy - v2);
    path.arc_cw_to(cx, cy, cx - r, cy);
    path.arc_cw_to(cx, cy, cx, cy - r);
    path.close();
    path.push_circle(cx, cy, r / 2.5);
    path.finish().unwrap()
});

#[test]
fn pin_path() {
    let _path = &*PIN_PATH;
}

trait PathBuilderExt {
    fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32);
}

impl PathBuilderExt for PathBuilder {
    fn arc_cw_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
        let (x0, y0) = match self.last_point() {
            None => {
                self.move_to(0.0, 0.0);
                (0.0, 0.0)
            }
            Some(p) => (p.x, p.y),
        };

        let ux = x0 - cx;
        let uy = y0 - cy;
        let ul = (ux * ux + uy * uy).sqrt();
        let uxn = ux / ul;
        let uyn = uy / ul;
        let a1 = (uy / ux).atan();

        let tx = x - cx;
        let ty = y - cy;
        let tl = (tx * tx + ty * ty).sqrt();
        let txn = tx / tl;
        let tyn = ty / tl;
        let a2 = (ty / tx).atan();

        let c = 4.0 / 3.0 * ((a2 - a1 + PI) % PI / 4.0).tan();
        let uc = ul * c;
        let tc = tl * c;
        self.cubic_to(
            x0 - uyn * uc,
            y0 + uxn * uc,
            x + tyn * tc,
            y - txn * tc,
            x,
            y,
        );
    }
}

impl From<crate::theme::Color> for Color {
    fn from(v: crate::theme::Color) -> Self {
        let [r, g, b, a] = v.to_array(Eotf::Gamma22);
        let mut c = Self::TRANSPARENT;
        c.set_red(r / a);
        c.set_green(g / a);
        c.set_blue(b / a);
        c.set_alpha(a);
        c
    }
}

fn calculate_accents(srgb: crate::theme::Color) -> [Color; 2] {
    let [l, a, b, alpha] = srgb_to_lab(srgb);
    let l2 = if l < 0.65 { 0.9 } else { l - 0.4 };
    let l1 = (l2 + l) / 2.0;
    [
        lab_to_color([l1, a, b, alpha]),
        lab_to_color([l2, a, b, alpha]),
    ]
}

fn srgb_to_lab(srgb: crate::theme::Color) -> [f32; 4] {
    let [mut r, mut g, mut b, alpha] = srgb.to_array(Eotf::Gamma22);
    if alpha < 1.0 {
        r /= alpha;
        g /= alpha;
        b /= alpha;
    }

    let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
    let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
    let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;

    let l_ = l.cbrt();
    let m_ = m.cbrt();
    let s_ = s.cbrt();

    [
        0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
        1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
        0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
        alpha,
    ]
}

fn lab_to_color([l, a, b, alpha]: [f32; 4]) -> Color {
    let l_ = l + 0.3963377774 * a + 0.2158037573 * b;
    let m_ = l - 0.1055613458 * a - 0.0638541728 * b;
    let s_ = l - 0.0894841775 * a - 1.2914855480 * b;

    let l = l_ * l_ * l_;
    let m = m_ * m_ * m_;
    let s = s_ * s_ * s_;

    let mut c = Color::TRANSPARENT;
    c.set_red(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s);
    c.set_green(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s);
    c.set_blue(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s);
    c.set_alpha(alpha);
    c
}
