use crate::badge::color::util::rgb_to_hex;
use crate::badge::color::AliasColor::*;
use crate::badge::color::NamedColor::*;
use crate::error::Error;
use crate::error::Error::BadColorString;
use lazy_static::lazy_static;
use regex::Regex;
use std::num::ParseIntError;

pub(crate) fn parse_color(color: &str) -> Result<String, Error> {
  if let Some(color) = is_enum_color(color) {
    return Ok(color.to_string());
  }

  if let Some(color) = is_hex_pound(color) {
    return Ok(color.to_string());
  }

  if let Some(color) = is_hex_no_pound(color) {
    return Ok(format!("#{}", color));
  }

  if let Some((r, g, b)) = is_rgb(color) {
    return Ok(format!("#{}", rgb_to_hex(r, g, b)));
  }

  Err(BadColorString(color.to_string()))
}

fn is_enum_color(color: &str) -> Option<&str> {
  match color {
    "brightgreen" => Some(BrightGreen.hex()),
    "green" => Some(Green.hex()),
    "yellow" => Some(Yellow.hex()),
    "yellowgreen" => Some(YellowGreen.hex()),
    "orange" => Some(Orange.hex()),
    "red" => Some(Red.hex()),
    "blue" => Some(Blue.hex()),
    "grey" => Some(Grey.hex()),
    "lightgrey" => Some(LightGrey.hex()),
    "gray" => Some(Gray.hex()),
    "lightgray" => Some(LightGray.hex()),
    "critical" => Some(Critical.hex()),
    "important" => Some(Important.hex()),
    "success" => Some(Success.hex()),
    "informational" => Some(Informational.hex()),
    "inactive" => Some(Inactive.hex()),
    _ => None,
  }
}

fn is_hex_no_pound(color: &str) -> Option<&str> {
  lazy_static! {
    static ref HEX_NO_POUND: Regex = Regex::new("^(?:[0-9a-fA-F]{3}){1,2}$").unwrap();
  };

  if HEX_NO_POUND.is_match(color) {
    Some(color)
  } else {
    None
  }
}

fn is_hex_pound(color: &str) -> Option<&str> {
  lazy_static! {
    static ref HEX_POUND: Regex = Regex::new("^#(?:[0-9a-fA-F]{3}){1,2}$").unwrap();
  };

  if HEX_POUND.is_match(color) {
    Some(color)
  } else {
    None
  }
}

fn is_rgb(color: &str) -> Option<(u8, u8, u8)> {
  lazy_static! {
    static ref RGB: Regex = Regex::new(r"^rgb\((( *0*([1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) *),){2}( *0*([1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) *)\)$").unwrap();
    static ref DIGITS: Regex = Regex::new(r"\d+").unwrap();
  };

  if RGB.is_match(color) {
    let digits = DIGITS
      .find_iter(color)
      .map(|m| &color[m.start()..m.end()])
      .map(|m| u8::from_str_radix(m, 10))
      .collect::<Result<Vec<u8>, ParseIntError>>()
      .ok();
    match digits {
      None => None,
      Some(vec) => {
        if vec.len() != 3 {
          return None;
        }
        Some((vec[0], vec[1], vec[2]))
      }
    }
  } else {
    None
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn is_hex_no_pound_test() {
    assert!(is_hex_no_pound("abcdef").is_some());
    assert!(is_hex_no_pound("abc").is_some());
    assert!(is_hex_no_pound("abcc").is_none());
    assert!(is_hex_no_pound("abccd").is_none());
    assert!(is_hex_no_pound("abccde").is_some());
    assert!(is_hex_no_pound("abcsde").is_none());
    assert!(is_hex_no_pound("#abcdef").is_none());
    assert!(is_hex_no_pound("#abc").is_none());
    assert!(is_hex_no_pound("#abcc").is_none());
    assert!(is_hex_no_pound("#abccd").is_none());
    assert!(is_hex_no_pound("#abccde").is_none());
    assert!(is_hex_no_pound("#abcsde").is_none());
  }

  #[test]
  fn is_hex_pound_test() {
    assert!(is_hex_pound("#abcdef").is_some());
    assert!(is_hex_pound("#abc").is_some());
    assert!(is_hex_pound("#abcc").is_none());
    assert!(is_hex_pound("#abccd").is_none());
    assert!(is_hex_pound("#abccde").is_some());
    assert!(is_hex_pound("#abcsde").is_none());
    assert!(is_hex_pound("abcdef").is_none());
    assert!(is_hex_pound("abc").is_none());
    assert!(is_hex_pound("abcc").is_none());
    assert!(is_hex_pound("abccd").is_none());
    assert!(is_hex_pound("abccde").is_none());
    assert!(is_hex_pound("abcsde").is_none());
  }

  #[test]
  fn is_rgb_test() {
    assert_eq!((10, 200, 50), is_rgb("rgb(10, 200, 50)").unwrap());
    assert_eq!((110, 0, 255), is_rgb("rgb(110, 0, 255)").unwrap());

    assert!(is_rgb("rgb(256, 200, 50)").is_none());
    assert!(is_rgb("rgb255, 200, 50)").is_none());
    assert!(is_rgb("rgb(255, 200, 50").is_none());
    assert!(is_rgb("rrgb(10,200,50)").is_none());
  }

  #[test]
  fn parse_color_test() {
    let oops = "oops".to_string();
    assert_eq!("#0ac832", parse_color("rgb(10, 200, 50)").unwrap());
    assert_eq!("#0ac832", parse_color("0ac832").unwrap());
    assert_eq!("#0ac832", parse_color("#0ac832").unwrap());
    assert_eq!("#4c1", parse_color("brightgreen").unwrap());
    assert_eq!("#007ec6", parse_color("informational").unwrap());
    assert_eq!("oops", parse_color("inrmational").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("egb(30,30,30)").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("egb(30,30,30)").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("0acc").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("0a").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("0accc").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("0acccac").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("#0acc").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("#0a").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("#0accc").unwrap_or(oops.clone()));
    assert_eq!("oops", parse_color("#0acccac").unwrap_or(oops.clone()));
  }
}
