colo 0.4.1

CLI tool to show and convert colors between various color spaces
use anyhow::{bail, Result};
use color_space::ToRgb;
use console::{Key, Term};

use crate::{
    color::space::{Cmy, Hsl, Hsv, Lab, Rgb},
    color::{Color, ColorSpace},
    State,
};
use three_lines::{InputLine, ThreeLines};

mod three_lines;

pub struct ColorPicker {
    inputs: [ThreeLines; 5],
    current: usize,
}

impl ColorPicker {
    pub fn new(color: Option<Color>, cs: Option<ColorSpace>) -> Self {
        let current = match cs {
            Some(cs) => match cs {
                ColorSpace::Rgb => 0,
                ColorSpace::Cmy => 4,
                ColorSpace::Cmyk => 4,
                ColorSpace::Hsv => 2,
                ColorSpace::Hsl => 1,
                ColorSpace::Lch => 3,
                ColorSpace::Luv => 3,
                ColorSpace::Lab => 3,
                ColorSpace::HunterLab => 3,
                ColorSpace::Xyz => 3,
                ColorSpace::Yxy => 3,
                ColorSpace::Gray => 1,
            },
            None => 1,
        };

        let mut s = Self {
            inputs: [
                rgb_input(),
                hsl_input(),
                hsv_input(),
                lab_input(),
                cmy_input(),
            ],
            current,
        };

        if let Some(c) = color {
            s.inputs[s.current].set_color(c);
        };

        s
    }

    pub fn len(&self) -> usize {
        self.inputs.len()
    }

    pub fn current(&self) -> &ThreeLines {
        &self.inputs[self.current]
    }

    pub fn current_mut(&mut self) -> &mut ThreeLines {
        &mut self.inputs[self.current]
    }

    pub fn next_color_space(&mut self) {
        let c = self.current().color();
        self.current = (self.current + 1) % self.len();
        self.current_mut().set_color(c);
    }

    pub fn prev_color_space(&mut self) {
        let c = self.current().color();
        self.current = (self.current + self.len() - 1) % self.len();
        self.current_mut().set_color(c);
    }

    pub fn display(&mut self, state: State) -> Result<Color> {
        if !state.color {
            bail!(
                "The color picker requires terminal color support \
                and can't be behind a pipe"
            );
        }

        let term = Term::stdout();

        let mut color = self.current().color();

        loop {
            self.current_mut().print(color, &term)?;

            let key = term.read_key()?;
            self.current_mut().reset_term(&term)?;

            match key {
                Key::Enter | Key::Escape => break,
                Key::Tab => self.next_color_space(),
                Key::BackTab => self.prev_color_space(),
                k => self.current_mut().enter_key(k),
            }

            color = self.current().color();
        }
        Ok(color)
    }
}

fn rgb_input() -> ThreeLines {
    fn to_color(v: &[f64]) -> Color {
        Color::Rgb(Rgb::new(v[0], v[1], v[2]))
    }
    fn from_color(c: Color) -> Vec<f64> {
        let rgb = c.to_rgb();
        vec![rgb.r, rgb.g, rgb.b]
    }

    ThreeLines::new(
        "RGB",
        from_color,
        to_color,
        &[
            InputLine::new("R", 0.0)
                .with_bounds(0.0, 255.0)
                .with_color_rgb(255, 60, 60),
            InputLine::new("G", 0.0)
                .with_bounds(0.0, 255.0)
                .with_color_rgb(60, 255, 60),
            InputLine::new("B", 0.0)
                .with_bounds(0.0, 255.0)
                .with_color_rgb(60, 60, 255),
        ],
    )
}

fn cmy_input() -> ThreeLines {
    fn to_color(v: &[f64]) -> Color {
        Color::Cmy(Cmy::new(v[0], v[1], v[2]))
    }
    fn from_color(c: Color) -> Vec<f64> {
        let cmy = Cmy::from(c.to_rgb());
        vec![cmy.c, cmy.m, cmy.y]
    }

    ThreeLines::new(
        "CMY",
        from_color,
        to_color,
        &[
            InputLine::new("C", 0.0)
                .with_bounds(0.0, 1.0)
                .as_percent()
                .with_color_rgb(0, 255, 255),
            InputLine::new("M", 0.0)
                .with_bounds(0.0, 1.0)
                .as_percent()
                .with_color_rgb(255, 0, 255),
            InputLine::new("Y", 0.0)
                .with_bounds(0.0, 1.0)
                .as_percent()
                .with_color_rgb(255, 255, 0),
        ],
    )
}

fn hsl_input() -> ThreeLines {
    fn to_color(v: &[f64]) -> Color {
        Color::Hsl(Hsl::new(v[0], v[1], v[2]))
    }
    fn from_color(c: Color) -> Vec<f64> {
        let hsl = Hsl::from(c.to_rgb());
        vec![hsl.h, hsl.s, hsl.l]
    }

    ThreeLines::new(
        "HSL",
        from_color,
        to_color,
        &[
            InputLine::new("H", 0.0).with_bounds(0.0, 360.0),
            InputLine::new("S", 1.0).with_bounds(0.0, 1.0).as_percent(),
            InputLine::new("L", 0.5).with_bounds(0.0, 1.0).as_percent(),
        ],
    )
}

fn hsv_input() -> ThreeLines {
    fn to_color(v: &[f64]) -> Color {
        Color::Hsv(Hsv::new(v[0], v[1], v[2]))
    }
    fn from_color(c: Color) -> Vec<f64> {
        let hsv = Hsv::from(c.to_rgb());
        vec![hsv.h, hsv.s, hsv.v]
    }

    ThreeLines::new(
        "HSV",
        from_color,
        to_color,
        &[
            InputLine::new("H", 0.0).with_bounds(0.0, 360.0),
            InputLine::new("S", 1.0).with_bounds(0.0, 1.0).as_percent(),
            InputLine::new("V", 1.0).with_bounds(0.0, 1.0).as_percent(),
        ],
    )
}

fn lab_input() -> ThreeLines {
    fn to_color(v: &[f64]) -> Color {
        Color::Lab(Lab::new(v[0], v[1], v[2]))
    }
    fn from_color(c: Color) -> Vec<f64> {
        let lab = Lab::from(c);
        vec![lab.l, lab.a, lab.b]
    }

    ThreeLines::new(
        "Lab",
        from_color,
        to_color,
        &[
            InputLine::new("L", 50.0).with_bounds(0.0, 100.0),
            InputLine::new("A", 0.0).with_bounds(-100.0, 100.0),
            InputLine::new("B", 0.0).with_bounds(-100.0, 100.0),
        ],
    )
}