use std::fmt;

use serde::{
    Deserialize, Deserializer, Serialize, Serializer,
    de::{Unexpected, Visitor},
};

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltinColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,
    BrightBlack,
    BrightRed,
    BrightGreen,
    BrightYellow,
    BrightBlue,
    BrightMagenta,
    BrightCyan,
    BrightWhite,
    Default,
}

#[derive(Clone, Hash, Debug, PartialEq, Eq)]
pub struct ArgbColor(pub [u8; 4]);

struct ArgbVisitor;
impl<'de> Visitor<'de> for ArgbVisitor {
    type Value = ArgbColor;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A color in the format rgb:RRGGBB or rgba:RRGGBBAA")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        (|| {
            if let Some(v) = v.strip_prefix("rgb:") {
                let r = u8::from_str_radix(v.get(0..2)?, 16).ok()?;
                let g = u8::from_str_radix(v.get(2..4)?, 16).ok()?;
                let b = u8::from_str_radix(v.get(4..6)?, 16).ok()?;
                Some(ArgbColor([255, r, g, b]))
            } else if let Some(v) = v.strip_prefix("rgba:") {
                let r = u32::from_str_radix(v.get(0..2)?, 16).ok()?;
                let g = u32::from_str_radix(v.get(2..4)?, 16).ok()?;
                let b = u32::from_str_radix(v.get(4..6)?, 16).ok()?;
                let a = u32::from_str_radix(v.get(6..8)?, 16).ok()?;
                Some(ArgbColor([
                    a as u8,
                    ((r * a) >> 8) as u8,
                    ((g * a) >> 8) as u8,
                    ((b * a) >> 8) as u8,
                ]))
            } else {
                None
            }
        })()
        .ok_or(E::invalid_value(
            Unexpected::Other("Invalid color format"),
            &"",
        ))
    }
}

impl<'de> Deserialize<'de> for ArgbColor {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        deserializer.deserialize_string(ArgbVisitor)
    }
}
impl Serialize for ArgbColor {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&format!(
            "rgba:{}{}{}{}",
            self.0[1], self.0[2], self.0[3], self.0[0]
        ))
    }
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Color {
    BuiltinColor(BuiltinColor),
    Rgb(ArgbColor),
}

impl Color {
    pub fn to_rgba(&self, config: &Config) -> Option<[u8; 4]> {
        match self {
            Color::BuiltinColor(color) => match color {
                BuiltinColor::Black => Some(config.black.0),
                BuiltinColor::Red => Some(config.red.0),
                BuiltinColor::Green => Some(config.green.0),
                BuiltinColor::Yellow => Some(config.yellow.0),
                BuiltinColor::Blue => Some(config.blue.0),
                BuiltinColor::Magenta => Some(config.magenta.0),
                BuiltinColor::Cyan => Some(config.cyan.0),
                BuiltinColor::White => Some(config.white.0),
                BuiltinColor::BrightBlack => Some(config.bright_black.0),
                BuiltinColor::BrightRed => Some(config.bright_red.0),
                BuiltinColor::BrightGreen => Some(config.bright_green.0),
                BuiltinColor::BrightYellow => Some(config.bright_yellow.0),
                BuiltinColor::BrightBlue => Some(config.bright_blue.0),
                BuiltinColor::BrightMagenta => Some(config.bright_magenta.0),
                BuiltinColor::BrightCyan => Some(config.bright_cyan.0),
                BuiltinColor::BrightWhite => Some(config.bright_white.0),
                BuiltinColor::Default => None,
            },
            Color::Rgb(ArgbColor(c)) => Some(*c),
        }
    }
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Attribute {
    Underline,
    Reverse,
    Blink,
    Bold,
    Dim,
    Italic,
    Strikethrough,
    FinalFg,
    FinalBg,
    FinalAttr,
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Face {
    pub fg: Color,
    pub bg: Color,
    pub attributes: Vec<Attribute>,
}

impl Default for Face {
    fn default() -> Self {
        Self {
            fg: Color::BuiltinColor(BuiltinColor::White),
            bg: Color::BuiltinColor(BuiltinColor::Black),
            attributes: Vec::new(),
        }
    }
}

#[derive(Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub struct Atom {
    pub face: Face,
    pub contents: String,
}

pub type Line = Vec<Atom>;

#[derive(Debug, PartialEq, Eq, Deserialize)]
pub struct Coord {
    line: i32,
    column: i32,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MenuShowStyle {
    Prompt,
    Search,
    Inline,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum InfoShowStyle {
    Prompt,
    Inline,
    InlineAbove,
    InlineBelow,
    MenuDoc,
    Modal,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SetCursorMode {
    Prompt,
    Buffer,
}

fn default_font() -> Vec<String> {
    vec![String::from("monospace")]
}
fn default_font_size() -> u32 {
    14
}
fn black() -> ArgbColor {
    ArgbColor([255, 0, 0, 0])
}
fn red() -> ArgbColor {
    ArgbColor([255, 255, 0, 0])
}
fn green() -> ArgbColor {
    ArgbColor([255, 0, 255, 0])
}
fn yellow() -> ArgbColor {
    ArgbColor([255, 255, 255, 0])
}
fn blue() -> ArgbColor {
    ArgbColor([255, 0, 0, 255])
}
fn magenta() -> ArgbColor {
    ArgbColor([255, 255, 0, 255])
}
fn cyan() -> ArgbColor {
    ArgbColor([255, 0, 255, 255])
}
fn white() -> ArgbColor {
    ArgbColor([255, 255, 255, 255])
}
fn bright_black() -> ArgbColor {
    ArgbColor([255, 0, 0, 0])
}
fn bright_red() -> ArgbColor {
    ArgbColor([255, 255, 0, 0])
}
fn bright_green() -> ArgbColor {
    ArgbColor([255, 0, 255, 0])
}
fn bright_yellow() -> ArgbColor {
    ArgbColor([255, 255, 255, 0])
}
fn bright_blue() -> ArgbColor {
    ArgbColor([255, 0, 0, 255])
}
fn bright_magenta() -> ArgbColor {
    ArgbColor([255, 255, 0, 255])
}
fn bright_cyan() -> ArgbColor {
    ArgbColor([255, 0, 255, 255])
}
fn bright_white() -> ArgbColor {
    ArgbColor([255, 255, 255, 255])
}

struct CommaListVisitor;
impl<'de> Visitor<'de> for CommaListVisitor {
    type Value = Vec<String>;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A list of comma seperated fonts")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        Ok(v.split(',').map(String::from).collect())
    }
}

fn comma_list_deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_string(CommaListVisitor)
}

struct StringIntegerVisitor;
impl<'de> Visitor<'de> for StringIntegerVisitor {
    type Value = u32;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("An integer")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        v.parse()
            .map_err(|_| E::invalid_value(Unexpected::Other("Expected string"), &""))
    }
}
fn string_integer<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_string(StringIntegerVisitor)
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
pub struct Config {
    #[serde(
        default = "default_font",
        deserialize_with = "comma_list_deserialize",
        rename = "mosham_fonts"
    )]
    pub fonts: Vec<String>,
    #[serde(
        default = "default_font_size",
        deserialize_with = "string_integer",
        rename = "mosham_font_size"
    )]
    pub font_size: u32,
    #[serde(default = "black", rename = "mosham_black")]
    pub black: ArgbColor,
    #[serde(default = "red", rename = "mosham_red")]
    pub red: ArgbColor,
    #[serde(default = "green", rename = "mosham_green")]
    pub green: ArgbColor,
    #[serde(default = "yellow", rename = "mosham_yellow")]
    pub yellow: ArgbColor,
    #[serde(default = "blue", rename = "mosham_blue")]
    pub blue: ArgbColor,
    #[serde(default = "magenta", rename = "mosham_magenta")]
    pub magenta: ArgbColor,
    #[serde(default = "cyan", rename = "mosham_cyan")]
    pub cyan: ArgbColor,
    #[serde(default = "white", rename = "mosham_white")]
    pub white: ArgbColor,
    #[serde(default = "bright_black", rename = "mosham_bright_black")]
    pub bright_black: ArgbColor,
    #[serde(default = "bright_red", rename = "mosham_bright_red")]
    pub bright_red: ArgbColor,
    #[serde(default = "bright_green", rename = "mosham_bright_green")]
    pub bright_green: ArgbColor,
    #[serde(default = "bright_yellow", rename = "mosham_bright_yellow")]
    pub bright_yellow: ArgbColor,
    #[serde(default = "bright_blue", rename = "mosham_bright_blue")]
    pub bright_blue: ArgbColor,
    #[serde(default = "bright_magenta", rename = "mosham_bright_magenta")]
    pub bright_magenta: ArgbColor,
    #[serde(default = "bright_cyan", rename = "mosham_bright_cyan")]
    pub bright_cyan: ArgbColor,
    #[serde(default = "bright_white", rename = "mosham_bright_white")]
    pub bright_white: ArgbColor,
}
impl Default for Config {
    fn default() -> Self {
        Self {
            fonts: default_font(),
            font_size: default_font_size(),
            black: black(),
            red: red(),
            green: green(),
            yellow: yellow(),
            blue: blue(),
            magenta: magenta(),
            cyan: cyan(),
            white: white(),
            bright_black: bright_black(),
            bright_red: bright_red(),
            bright_green: bright_green(),
            bright_yellow: bright_yellow(),
            bright_blue: bright_blue(),
            bright_magenta: bright_magenta(),
            bright_cyan: bright_cyan(),
            bright_white: bright_white(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
/// JSONRPC call *from* Kakoune
pub enum Request {
    Draw(Vec<Line>, Face, Face),
    DrawStatus(Line, Line, Face),
    MenuShow(Vec<Line>, Coord, Face, Face, MenuShowStyle),
    MenuSelect([i32; 1]),
    MenuHide([(); 0]),
    InfoShow(Line, Vec<Line>, Coord, Face, InfoShowStyle),
    InfoHide([(); 0]),
    SetCursor(SetCursorMode, Coord),
    SetUiOptions([Config; 1]),
    Refresh([bool; 1]),
}

#[derive(Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MouseEvent {
    Move,
    Press,
    Release,
    WheelUp,
    WheelDown,
}

#[derive(Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
/// JSONRPC call *to* Kakoune
/// NOTE: contrary to the name, this is a JSONRPC request(method call)
pub enum Response {
    Keys(Vec<String>),
    Resize(u32, u32),
    Mouse(MouseEvent, i32, i32),
    MenuSelect([i32; 0]),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rpc<T> {
    pub jsonrpc: String,
    #[serde(flatten)]
    pub inner: T,
}
impl<T> From<T> for Rpc<T> {
    fn from(inner: T) -> Self {
        Rpc {
            jsonrpc: String::from("2.0"),
            inner,
        }
    }
}