use log::debug;
use super::ansi_color::ANSIColor;
use super::ansi_visitor::ANSIVisitor;
use std::error::Error;
use std::io::Write;

fn escape_tex_chars(s: &str) -> String {
  const BS_TAG: &str = r"[BACKSLASHTAG]";
  s.replace('\\', BS_TAG).replace('_', r"\_").replace('{', r"\{")
    .replace('}', r"\}").replace(BS_TAG, r"{\textbackslash}")
}

/// This struct implements the `ANSIVisitor` trait to convert ANSI codes into
/// LaTeX commands.
///
/// The following example converts a text containing an ANSI escape code that
/// set the background to a color, into a TeX code:
/// ```
/// use ansiconv::{ANSI2TeX, ANSIParser};
/// use std::io::Cursor;
///
/// // Create the converter instance and its output writer
/// let mut out_string = Cursor::new(Vec::<u8>::new());
/// let mut converter = ANSI2TeX::new(&mut out_string);
///
/// // Create an ANSI string to parse and the parser instance to use on it
/// let mut in_string = Cursor::new(b"\x1b[48;5;50mHello!\x1b[0m");
/// let mut parser = ANSIParser::new(&mut in_string);
///
/// // Launch the parsing
/// parser.accept(&mut converter);
///
/// // Get the result
/// let s = std::str::from_utf8(out_string.get_ref()).unwrap();
/// println!("{s}");
/// ```
pub struct ANSI2TeX<'a> {
  env: Option<String>,
  out_stream: &'a mut dyn Write,
  fg_default_color: ANSIColor,
  fg_color: Option<ANSIColor>,
  bg_color: Option<ANSIColor>,
  bold: bool,
  underline: bool,
  bracket_level: usize,
  default_color_initialized: bool,
}

impl<'a> ANSI2TeX<'a> {

  /// Construct a new `ANSI2TeX` instance from an object implementing the
  /// `Write` trait.
  pub fn new(out_stream: &'a mut dyn Write) -> Self {

    let fg = ANSIColor::new(0xff, 0xff, 0xff);

    Self {
      env: None,
      out_stream,
      fg_default_color: fg,
      fg_color: None,
      bg_color: None,
      bold: false,
      underline: false,
      bracket_level: 0,
      default_color_initialized: false,
    }
  }

  /// Change the default foreground color.
  ///
  /// If unset, *white* is used.
  #[allow(unused)]
  #[must_use]
  pub fn fg_default_color(mut self, c: ANSIColor) -> Self {
    self.fg_default_color = c;
    self
  }

  /// Set an enclosing *TeX environment* to use.
  /// 
  /// The generated TeX code will be surrounded by `\begin{some_env}` and
  /// `\end{some_env}`.
  #[must_use]
  pub fn env(mut self, env: &str) -> Self {
    self.env = Some(env.to_string());
    self
  }

  fn reset_bg(&mut self) {
    self.bg_color = None;
  }

  fn reset_fg(&mut self) {
    self.fg_color = None;
  }

  fn reset_all(&mut self) {
    self.reset_fg();
    self.reset_bg();
    self.bold = false;
    self.underline = false;
  }

  fn close_brackets(&mut self, n: usize) -> Result<(), Box<dyn Error>> {
    write!(self.out_stream, "{}", "}".repeat(n))?;
    Ok(())
  }

  fn write_text(&mut self, s: &str) -> Result<(), Box<dyn Error>> {

    if ! s.is_empty() {

      if ! self.default_color_initialized {
        write!(self.out_stream,
          "\\textcolor[HTML]{{{}}}{{",
          self.fg_default_color.to_hex())?;
        self.bracket_level += 1;
        self.default_color_initialized = true;
      }

      let mut lvl = 0usize;

      if let Some(ref color) = self.bg_color {
        write!(self.out_stream, "\\colorbox[HTML]{{{}}}{{", color.to_hex())?;
        lvl+=1;
      }

      if let Some(ref color) = self.fg_color {
        write!(self.out_stream, "\\textcolor[HTML]{{{}}}{{", color.to_hex())?;
        lvl+=1;
      }

      if self.bold {
        write!(self.out_stream, "\\textbf{{")?;
        lvl+=1;
      }

      if self.underline {
        write!(self.out_stream, "\\underline{{")?;
        lvl+=1;
      }

      write!(self.out_stream, "{}", escape_tex_chars(s))?;
      self.close_brackets(lvl)?;
    }

    Ok(())
  }
}

impl ANSIVisitor for ANSI2TeX<'_> {

  fn visit_begin(&mut self) -> Result<(), Box<dyn Error>> {
    debug!("Visit begin");
    if let Some(ref env) = self.env {
      writeln!(self.out_stream, r"\begin{{{env}}}")?;
    }
    Ok(())
  }

  fn visit_end(&mut self) -> Result<(), Box<dyn Error>> {
    debug!("Visit end");
    self.close_brackets(self.bracket_level)?;
    if let Some(ref env) = self.env {
      if self.bracket_level > 0 {
        writeln!(self.out_stream)?;
      }
      write!(self.out_stream, r"\end{{{env}}}")?;
    }
    Ok(())
  }

  fn visit_text(&mut self, s: &str) -> Result<(), Box<dyn Error>> {
    debug!("Visit text: {s}");
    self.write_text(s)
  }

  fn visit_reset(&mut self) -> Result<(), Box<dyn Error>> {
    debug!("Visit reset");
    self.reset_all();
    Ok(())
  }

  fn visit_reset_fg_color(&mut self) -> Result<(), Box<dyn Error>> {
    debug!("Visit reset FG color");
    self.reset_fg();
    Ok(())
  }

  fn visit_reset_bg_color(&mut self) -> Result<(), Box<dyn Error>> {
    debug!("Visit reset BG color");
    self.reset_bg();
    Ok(())
  }

  fn visit_set_bold(&mut self, bold: bool
    ) -> Result<(), Box<dyn Error>> {
    debug!("Visit set bold {bold}");
    self.bold = bold;
    Ok(())
  }

  fn visit_set_faint(&mut self, faint: bool
    ) -> Result<(), Box<dyn Error>> {
    if faint {
      self.bold = false;
    }
    Ok(())
  }

  fn visit_set_underline(&mut self, underline: bool
    ) -> Result<(), Box<dyn Error>> {
    debug!("Visit set underline {underline}");
    self.underline = underline;
    Ok(())
  }

  fn visit_set_fg_color(&mut self, color: ANSIColor
    ) -> Result<(), Box<dyn Error>> {
    debug!("Visit set FG color {}", color.to_hex());
    self.fg_color = Some(color);
    Ok(())
  }

  fn visit_set_bg_color(&mut self, color: ANSIColor
    ) -> Result<(), Box<dyn Error>> {
    debug!("Visit set BG color {}", color.to_hex());
    self.bg_color = Some(color);
    Ok(())
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::ANSIParser;
  use std::collections::HashMap;
  use std::io::Cursor;

  #[test]
  fn test_empty_output_text() -> Result<(), Box<dyn Error>> {
      for instr in [
        "", // empty
      "\x1b[1m", // Bold, no text
      "\x1b]8;;https://foo/\x1b\\", // OSC
      ] {
        let mut out_string = Cursor::new(Vec::<u8>::new());
        let mut converter = ANSI2TeX::new(&mut out_string);
        let mut in_string = Cursor::new(instr);
        let mut parser = ANSIParser::new(&mut in_string);
        parser.accept(&mut converter)?;
        let s = std::str::from_utf8(out_string.get_ref())?;
        assert!(s.is_empty(), "Output should be empty: {s}");
      }
    Ok(())
  }

  #[test]
  fn test_conversion() -> Result<(), Box<dyn Error>> {
    let mut cases = HashMap::new();
    cases.insert("ABC", // Text
      r"\textcolor[HTML]{ffffff}{ABC}");
    cases.insert("\x1b[0mxyz", // Reset
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[8mxyz", r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[16mxyz", r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[31mxyz",
      r"\textcolor[HTML]{ffffff}{\textcolor[HTML]{800000}{xyz}}");
    cases.insert("\x1b[48;5;100mxyz",
      r"\textcolor[HTML]{ffffff}{\colorbox[HTML]{878700}{xyz}}");
    cases.insert("\x1b[38;5;100mxyz",
      r"\textcolor[HTML]{ffffff}{\textcolor[HTML]{878700}{xyz}}");
    cases.insert("\x1b[48;2;16;16;16mxyz",
      r"\textcolor[HTML]{ffffff}{\colorbox[HTML]{101010}{xyz}}");
    cases.insert("\x1b[38;2;16;16;16mxyz",
      r"\textcolor[HTML]{ffffff}{\textcolor[HTML]{101010}{xyz}}");
    cases.insert("\x1b[22;255mxyz", // Bad CSI code
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1bAxyz", // Bad ESC code
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[1mxyz", // Bold
      r"\textcolor[HTML]{ffffff}{\textbf{xyz}}");
    cases.insert("\x1b[2mxyz", // Faint
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[1m\x1b[2mxyz", // Bold then Faint
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[1;31mxyz", // Bold with color
      r"\textcolor[HTML]{ffffff}{\textcolor[HTML]{800000}{\textbf{xyz}}}");
    cases.insert("\x1b[48;5;160;38;5;226mxyz", // Both FG and BG
      r"\textcolor[HTML]{ffffff}{\colorbox[HTML]{d70000}{\textcolor[HTML]{ffff00}{xyz}}}");
    cases.insert("\x1b[22mxyz", // Disable bold & faint
      r"\textcolor[HTML]{ffffff}{xyz}");
    cases.insert("\x1b[22;36mxyz", // Disable bold & faint, then FG Color
      r"\textcolor[HTML]{ffffff}{\textcolor[HTML]{008080}{xyz}}");
    cases.insert("\x1b[4mabc", // Underline
      r"\textcolor[HTML]{ffffff}{\underline{abc}}");
    cases.insert("\x1b[4mabc\x1b[0mdef", // Underline
      r"\textcolor[HTML]{ffffff}{\underline{abc}def}");
    cases.insert("\x1b[4mabc\x1b[24mdef", // Underline
      r"\textcolor[HTML]{ffffff}{\underline{abc}def}");

    for (input, expected) in cases {
      let mut out_string = Cursor::new(Vec::<u8>::new());
      let mut converter = ANSI2TeX::new(&mut out_string);
      let mut in_stream = Cursor::new(input);
      let mut parser = ANSIParser::new(&mut in_stream);
      parser.accept(&mut converter)?;
      let s = std::str::from_utf8(out_string.get_ref())?;
      assert_eq!(expected, s, "Got \"{s}\" while expecting \"{expected}\".");
    }
    Ok(())
  }

  #[test]
  fn test_tex_env_with_no_text() -> Result<(), Box<dyn Error>> {
    let mut out_string = Cursor::new(Vec::<u8>::new());
    let mut converter = ANSI2TeX::new(&mut out_string).env("foo");
    let mut in_string = Cursor::new("");
    let mut parser = ANSIParser::new(&mut in_string);
    parser.accept(&mut converter)?;
    let s = std::str::from_utf8(out_string.get_ref())?;
    let t = "\\begin{foo}\n\\end{foo}";
    assert_eq!(s, t, "\"{s}\" and \"{t}\" differ.");
    Ok(())
  }

  #[test]
  fn test_tex_env() -> Result<(), Box<dyn Error>> {
    let mut out_string = Cursor::new(Vec::<u8>::new());
    let mut converter = ANSI2TeX::new(&mut out_string).env("foo");
    let mut in_string = Cursor::new("abc");
    let mut parser = ANSIParser::new(&mut in_string);
    parser.accept(&mut converter)?;
    let s = std::str::from_utf8(out_string.get_ref())?;
    let t = "\\begin{foo}\n\\textcolor[HTML]{ffffff}{abc}\n\\end{foo}";
    assert_eq!(s, t, "\"{s}\" and \"{t}\" differ.");
    Ok(())
  }
}
