  use log::{debug,trace};
  use super::ansi_visitor::{ANSIVisitor, Blink};
  use super::ansi_err::ANSIErr;
  use super::ansi_color::ANSIColor;
  use std::error::Error;
  use std::io::{BufRead, Read};

  const ESC: u8 = 0x1b;
  const CR: u8 = 0x0a;

  #[derive(Debug)]
  enum State {
    Text,   // Inside normal text
    FeESC(u8),  // Inside Fe escape sequence
  }

  fn get_csi_color(code: u8, s: &str, mut iter: impl Iterator<Item = u8>
    ) -> Result<ANSIColor, Box<dyn Error>> {

    match iter.next() {

      // RGB Color
      Some(2) => {
        let (r,g,b) = (iter.next().unwrap(), iter.next().unwrap(),
                       iter.next().unwrap());
        Ok(ANSIColor::new(r, g, b))
      },

      // 8-bit palette color
      Some(5) => Ok(ANSIColor::from_ansi_8_bit_color(iter.next().unwrap())),

      // Wrong code
      Some(c) =>
        Err(Box::new(
            ANSIErr::UnexpectedCodeInCSI(code, c, s.to_string()))),

      // No code
      None =>
        Err(Box::new(ANSIErr::MissingCodeInCSI(code, s.to_string()))),
    }
  }

  // Process Select Graphic Rendition (SGR) CSI sequence
  fn process_sgr_csi_seq(visitor: &mut impl ANSIVisitor, s: &str
    ) -> Result<(), Box<dyn Error>> {

    // Convert to str
    if s.is_empty() {
      visitor.visit_reset()?;
      return Ok(());
    }

    // Split sequence of numbers
    let numbers = s.split([';', ':'])
      .filter(|x| !x.is_empty()) // Remove empty strings (i.e.: no-code)
      .map(str::parse::<u8>)
      .collect::<Result<Vec<_>,_>>();

    // Check for errors
    if numbers.is_err() {
      return Err(Box::new(ANSIErr::IntParseErrInCSI(s.to_string())));
    }
    let numbers = numbers?;

    // Iterates over codes
    let mut iter = numbers.into_iter();
    while let Some(c) = iter.next() {

      match c {
           0    => visitor.visit_reset()?,
           1    => visitor.visit_set_bold(true)?,
           2    => visitor.visit_set_faint(true)?,
           3    => visitor.visit_set_italic(true)?,
           4    => visitor.visit_set_underline(true)?,
           5    => visitor.visit_set_blink(Blink::Slow)?,
           6    => visitor.visit_set_blink(Blink::Rapid)?,
           7    => visitor.visit_set_invert(true)?,
           8    => visitor.visit_set_concealed(true)?,
           9    => visitor.visit_set_crossed_out(true)?,
          10    => visitor.visit_set_default_font()?,
       11..=19  => visitor.visit_set_alternative_font(c - 10)?,
          20    => visitor.visit_set_gothic()?,
          21    => visitor.visit_set_doubly_underlined()?,
          22    => {
            visitor.visit_set_bold(false)?;
            visitor.visit_set_faint(false)?;
          },
          23    => visitor.visit_set_italic(false)?,
          24    => visitor.visit_set_underline(false)?,
          25    => visitor.visit_set_blink(Blink::Null)?,
          26    => visitor.visit_set_proportional_spacing(true)?,
          27    => visitor.visit_set_invert(false)?,
          28    => visitor.visit_set_concealed(false)?,
          29    => visitor.visit_set_crossed_out(false)?,
        30..=37 => visitor.visit_set_fg_color(
          ANSIColor::from_ansi_3_4_bit_color(c - 30))?,
          38    => visitor.visit_set_fg_color(get_csi_color(c, s, &mut iter)?)?,
          39    => visitor.visit_reset_fg_color()?,
        40..=47 => visitor.visit_set_bg_color(
          ANSIColor::from_ansi_3_4_bit_color(c - 40))?,
          48    => visitor.visit_set_bg_color(get_csi_color(c, s, &mut iter)?)?,
          49    => visitor.visit_reset_bg_color()?,
          50    => visitor.visit_set_proportional_spacing(false)?,
          51    => visitor.visit_set_framed(true)?,
          52    => visitor.visit_set_encircled(true)?,
          53    => visitor.visit_set_overlined(true)?,
          54    => {
            visitor.visit_set_framed(false)?;
            visitor.visit_set_encircled(false)?;
          },
          55    => visitor.visit_set_overlined(false)?,
          58    => visitor.visit_set_underline_color(
            get_csi_color(c, s, &mut iter)?)?,
          59    => visitor.visit_reset_underline_color()?,
          60    => visitor.visit_set_ideogram_underline()?,
          61    => visitor.visit_set_ideogram_double_underline()?,
          62    => visitor.visit_set_ideogram_overline()?,
          63    => visitor.visit_set_ideogram_double_overline()?,
          64    => visitor.visit_set_ideogram_stress_marking()?,
          65    => visitor.visit_reset_ideogram_attributes()?,
          73    => visitor.visit_set_superscript(true)?,
          74    => visitor.visit_set_subscript(true)?,
          75    => {
            visitor.visit_set_superscript(false)?;
            visitor.visit_set_subscript(false)?;
          },
        90..=97 => visitor.visit_set_fg_color(
          ANSIColor::from_ansi_3_4_bit_color(c - 82))?,
      100..=107 => visitor.visit_set_bg_color(
        ANSIColor::from_ansi_3_4_bit_color(c - 92))?,
          _ => visitor.visit_unknown_csi_sgr_code(c, s)?,
      }
    }

    Ok(())
  }

  /// A parser of ANSI escape sequences.
  ///
  /// The parser takes an input stream when constructed.
  /// The actual parsing is then run when calling the `accept()` method on a
  /// visitor instance.
  ///
  /// Escape codes have been taken from
  /// <https://en.wikipedia.org/wiki/ANSI_escape_code>.
  /// Only *Control Sequence Introducer* (CSI) has been implemented. Other escape
  /// codes are ignored.
  ///
  /// In CSI, only the *Select Graphic Rendition* is interpreted, other sequences
  /// are ignored.
  /// The visitor is called for each of the SGR cases. Each call has a default
  /// implementation, so we only have to implement the desired call backs.
  /// Default implementations include a warning log display, that will be executed
  /// if the corresponding ANSI code is met and no custom implementation is
  /// provided.
  ///
  /// Here is an example in which we implement our own visitor. Two ANSI codes are
  /// contained in the input string, one for setting bold font, and one to reset
  /// all settings. The reset is not implemented in our visitor, thus the default
  /// implementation that emits a warning will be used:
  /// ```
  /// use ansiconv::{ANSIParser, ANSIVisitor};
  /// use std::error::Error;
  /// use std::io::Cursor;
  ///
  /// // Create an ANSI string to parse and the parser instance to use on it
  /// let mut in_string = Cursor::new(b"Hello \x1b[1mPaul\x1b[0m!");
  /// let mut parser = ANSIParser::new(&mut in_string);
  ///
  /// // Implement the visitor trait into a custom struct
  /// struct MyVisitor {
  /// }
  ///
  /// impl MyVisitor {
  ///   fn new() -> Self {
  ///     Self {}
  ///   }
  /// }
  ///
  /// impl ANSIVisitor for MyVisitor {
  ///
  ///   fn visit_text(&mut self, s: &str) -> Result<(), Box<dyn Error>> {
  ///     println!("Received text: {s}");
  ///     Ok(())
  ///   }
  ///
  ///   fn visit_set_bold(&mut self, bold: bool) -> Result<(), Box<dyn Error>> {
  ///     println!("Bold {}", if bold {"on"} else {"off"});
  ///     Ok(())
  ///   }
  /// }
  ///
  /// // Launch the parsing
  /// let mut visitor = MyVisitor::new();
  /// parser.accept(&mut visitor);
  /// ```
  pub struct ANSIParser<'a> {
    in_stream: &'a mut dyn BufRead,
  }

  impl<'a> ANSIParser<'a> {

    /// Construct a new parser instance from an input stream that implements
    /// `BufRead`.
    pub fn new(in_stream: &'a mut dyn BufRead) -> Self {
      Self {
        in_stream,
      }
    }

    /// Execute the parsing if the input stream.
    ///
    /// The parser calls back the passed visitor that must implement the
    /// [`ANSIVisitor`] trait.
    ///
    /// # Errors
    /// Errors may happen when reading the input stream or if a parsing error
    /// occurs with the ANSI escape codes.
    pub fn accept(&mut self, visitor: &mut impl ANSIVisitor
      ) -> Result<(), Box<dyn Error>> {

      let mut buf: Vec<u8> = Vec::with_capacity(1024);
      let mut state = State::Text;
      let mut line = 1;
      let mut col = 0;
      let mut esc = false; // Previous character is ESC

      // Visit begin
      visitor.visit_begin()?;

      // Loop on all bytes
      for byte in self.in_stream.bytes() {

        let b = byte?;
        trace!("Line {line}, col {col}, byte: {b:02x}, {state:?}");

        // Count line & column
        if b == CR { line += 1; col = 0; } else { col += 1; }

        // Escape character
        if b == ESC { esc = true; continue }

        match state {

          State::Text => match b {
            x if esc => {
              if ! buf.is_empty() {
                visitor.visit_text(std::str::from_utf8(&buf)?)?;
                buf.clear();
              }
              state = State::FeESC(x);
            },
             _  => buf.push(b),
          },

          // Control Sequence Introducer (CSI)
          State::FeESC(b'[') => match b {
            0x20..=0x3f => buf.push(b), // Characters of CSI command
            0x40..=0x7e => { // End of CSI command
              let s = std::str::from_utf8(&buf)?;
              let c = b as char;
              debug!("Parsed CSI string for command : {s}{c}");
              visitor.visit_csi_command(c, s)?;
              if b == b'm' {
                process_sgr_csi_seq(visitor, s)?;
              }
              buf.clear();
              state = State::Text;
            },
             x if esc => return Err(Box::new(ANSIErr::EscInCSI(x, line, col))),
             x  => return Err(Box::new(ANSIErr::WrongCharInCSI(x, line, col))),
          },

          // SS2
          State::FeESC(b'N') => {
            visitor.visit_fe_esc_ss2()?;
            state = State::Text;
          },

          // SS3
          State::FeESC(b'O') => {
            visitor.visit_fe_esc_ss3()?;
            state = State::Text;
          },

          // FeESC that take a string
          State::FeESC(c) if c == b'P' || c == b']' || c == b'X' || c == b'^'
            || c == b'_' => match b {
            b'\\' if esc => { // String Terminator
              visitor.visit_fe_esc_string(c, std::str::from_utf8(&buf)?)?;
              buf.clear();
              state = State::Text;
            },
            x if esc
              => return Err(Box::new(ANSIErr::UnknownEscInFeESC(x, line, col))),
            _  => buf.push(b),
          },

          State::FeESC(c) => {
            visitor.visit_fe_esc_unknown(c)?;
            state = State::Text;
            buf.push(b);
          },
        }

        // Reset escape status
        esc = false;
      }

      // Escape character alone at the end of stream
      if esc {
        return Err(Box::new(ANSIErr::UnterminatedEscSeq(line, col)));
      }

      // Process bytes remaining in buffer
      if ! buf.is_empty() {
        match state {
          State::Text => visitor.visit_text(std::str::from_utf8(&buf)?)?,
          State::FeESC(_)
            => return Err(Box::new(ANSIErr::UnterminatedEscSeq(line, col))),
      }
    }

    // Visit end
    visitor.visit_end()
  }
}
