use logos::Logos;

use super::{Error, Result};

#[derive(Logos, Debug, PartialEq, Clone, Copy)]
#[logos(skip r"[ \t\n\r]+")]
pub(crate) enum Token
{
  #[regex(r"//[^\n\r]*", logos::skip, allow_greedy = true)]
  Comment,

  #[token("and", ignore(case))]
  And,
  #[token("as", ignore(case))]
  As,
  #[token("asc", ignore(case))]
  #[token("ascending", ignore(case))]
  Asc,
  #[token("by", ignore(case))]
  By,
  #[token("call", ignore(case))]
  Call,
  #[token("desc", ignore(case))]
  #[token("descending", ignore(case))]
  Desc,
  #[token("create", ignore(case))]
  Create,
  #[token("delete", ignore(case))]
  Delete,
  #[token("detach", ignore(case))]
  Detach,
  #[token("drop", ignore(case))]
  Drop,
  #[token("exists", ignore(case))]
  Exists,
  #[token("for", ignore(case))]
  For,
  #[token("false", ignore(case))]
  False,
  #[token("graph", ignore(case))]
  Graph,
  #[token("if", ignore(case))]
  If,
  #[token("index", ignore(case))]
  Index,
  #[token("in", ignore(case))]
  In,
  #[token("is", ignore(case))]
  Is,
  #[token("limit", ignore(case))]
  Limit,
  #[token("match", ignore(case))]
  Match,
  #[token("remove", ignore(case))]
  Remove,
  #[token("return", ignore(case))]
  Return,
  #[token("score", ignore(case))]
  Score,
  #[token("search", ignore(case))]
  Search,
  #[token("set", ignore(case))]
  Set,
  #[token("not", ignore(case))]
  Not,
  #[token("null", ignore(case))]
  Null,
  #[token("or", ignore(case))]
  Or,
  #[token("on", ignore(case))]
  On,
  #[token("order", ignore(case))]
  Order,
  #[token("optional", ignore(case))]
  Optional,
  #[token("options", ignore(case))]
  Options,
  #[token("skip", ignore(case))]
  Skip,
  #[token("true", ignore(case))]
  True,
  #[token("union", ignore(case))]
  Union,
  #[token("update", ignore(case))]
  Update,
  #[token("unwind", ignore(case))]
  Unwind,
  #[token("use", ignore(case))]
  Use,
  #[token("vector", ignore(case))]
  Vector,
  #[token("with", ignore(case))]
  With,
  #[token("where", ignore(case))]
  Where,
  #[token("xor", ignore(case))]
  Xor,
  #[token("=")]
  Equal,
  #[token("<>")]
  Different,
  #[token("<=")]
  LessEqual,
  #[token(">=")]
  GreaterEqual,
  #[token("->")]
  RightArrow,
  #[token("<-")]
  LeftArrow,
  #[token("<")]
  Less,
  #[token(">")]
  Greater,
  #[token("-")]
  Minus,
  #[token("+")]
  Plus,
  #[token("/")]
  Slash,
  #[token("%")]
  Percent,
  #[token("^")]
  Caret,
  #[token("..")]
  DotDot,
  #[token(".")]
  Dot,
  #[token("*")]
  Star,
  #[token("+=")]
  PlusEqual,
  #[token(",")]
  Comma,
  #[token("(")]
  StartParenthesis,
  #[token(")")]
  EndParenthesis,
  #[token("{")]
  StartBrace,

  #[token("}")]
  EndBrace,
  #[token("[")]
  StartBracket,
  #[token("]")]
  EndBracket,
  #[token(":")]
  Colon,
  #[token(";")]
  Semi,
  #[token("|")]
  Pipe,
  #[regex(r"\$[a-zA-Z0-9_]+")]
  ParameterName,
  #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*")]
  Identifier,
  #[regex(r"0[xX][0-9a-fA-F]+")]
  HexaDecimal,
  #[regex(r"0[oO][0-7]+")]
  OctaDecimal,
  #[regex(r"[1-9]\d*|0")]
  Integer,
  #[regex(r"(?:0|[1-9]\d*)\.(?:\d*|\d*[eE][+-]?\d+)?")]
  #[regex(r"(?:0|[1-9]\d*)[eE][+-]?\d+")]
  #[regex(r"\.\d+(?:[eE][+-]?\d+)?")]
  Number,
  #[regex(r"[+-]?(?:0|[1-9]\d*)\.\.")]
  IntegerDotDot,
  #[regex(r#""([^"\\\x00-\x1F]|\\.)*""#)]
  #[regex(r#"'([^'\\\x00-\x1F]|\\.)*'"#)]
  #[regex(r#"`([^`\\\x00-\x1F]|\\.)*`"#)]
  String,
}

impl Token
{
  pub(crate) fn is_keyword(&self) -> bool
  {
    matches!(
      self,
      Token::And
        | Token::As
        | Token::Asc
        | Token::By
        | Token::Call
        | Token::Desc
        | Token::Create
        | Token::Delete
        | Token::Detach
        | Token::Drop
        | Token::Exists
        | Token::For
        | Token::False
        | Token::Graph
        | Token::If
        | Token::Index
        | Token::In
        | Token::Is
        | Token::Limit
        | Token::Match
        | Token::Not
        | Token::Null
        | Token::On
        | Token::Or
        | Token::Order
        | Token::Optional
        | Token::Options
        | Token::Return
        | Token::Score
        | Token::Search
        | Token::Set
        | Token::Skip
        | Token::True
        | Token::Union
        | Token::Update
        | Token::Unwind
        | Token::Use
        | Token::Vector
        | Token::With
        | Token::Where
        | Token::Xor
    )
  }
}

#[derive(Debug, PartialEq, Clone)]
pub(crate) struct Spanned<T>
{
  pub(crate) value: T,
  pub(crate) span: std::ops::Range<usize>,
}

pub(crate) type SpannedToken = Spanned<Token>;

pub(crate) fn tokenize(input: &str) -> Result<Vec<SpannedToken>>
{
  let mut tokens = Vec::new();
  let mut lexer = Token::lexer(input);
  while let Some(token) = lexer.next()
  {
    tokens.push(Spanned {
      value: token.map_err(|_| Error::new(crate::oc::error::ErrorKind::LexError, lexer.span()))?,
      span: lexer.span(),
    });
  }
  Ok(tokens)
}

#[cfg(test)]
mod tests
{
  use crate::oc::lexer::{Spanned, Token, tokenize};

  #[test]
  fn test_oc_lexer_float()
  {
    assert_eq!(
      tokenize(".1e-5").unwrap(),
      vec![Spanned {
        value: Token::Number,
        span: 0..5
      }]
    );
    assert_eq!(
      tokenize("1.").unwrap(),
      vec![Spanned {
        value: Token::Number,
        span: 0..2
      }]
    );
    assert_eq!(
      tokenize("1.0").unwrap(),
      vec![Spanned {
        value: Token::Number,
        span: 0..3
      }]
    );
    assert_eq!(
      tokenize("1.0e3").unwrap(),
      vec![Spanned {
        value: Token::Number,
        span: 0..5
      }]
    );
    assert_eq!(
      tokenize("2E-01").unwrap(),
      vec![Spanned {
        value: Token::Number,
        span: 0..5
      }]
    );
  }
  #[test]
  fn test_oc_lexer_int()
  {
    assert_eq!(
      tokenize("123").unwrap(),
      vec![Spanned {
        value: Token::Integer,
        span: 0..3
      }]
    );
    assert_eq!(
      tokenize("0x123").unwrap(),
      vec![Spanned {
        value: Token::HexaDecimal,
        span: 0..5
      }]
    );
    assert_eq!(
      tokenize("0o123").unwrap(),
      vec![Spanned {
        value: Token::OctaDecimal,
        span: 0..5
      }]
    );
    assert_eq!(
      tokenize("0o1000000000000000000000").unwrap(),
      vec![Spanned {
        value: Token::OctaDecimal,
        span: 0..24
      }]
    );
  }
  #[test]
  fn test_oc_lexer_range_access()
  {
    assert_eq!(
      tokenize("arr[1..2]").unwrap(),
      vec![
        Spanned {
          value: Token::Identifier,
          span: 0..3
        },
        Spanned {
          value: Token::StartBracket,
          span: 3..4
        },
        Spanned {
          value: Token::IntegerDotDot,
          span: 4..7
        },
        Spanned {
          value: Token::Integer,
          span: 7..8
        },
        Spanned {
          value: Token::EndBracket,
          span: 8..9
        },
      ]
    );
  }
  #[test]
  fn test_oc_lexer_return_carriages()
  {
    // Test Windows line endings
    assert_eq!(
      tokenize("a\r\nb").unwrap(),
      vec![
        Spanned {
          value: Token::Identifier,
          span: 0..1
        },
        Spanned {
          value: Token::Identifier,
          span: 3..4
        },
      ]
    );

    // Test Unix/Linux line endings
    assert_eq!(
      tokenize("a\nb").unwrap(),
      vec![
        Spanned {
          value: Token::Identifier,
          span: 0..1
        },
        Spanned {
          value: Token::Identifier,
          span: 2..3
        },
      ]
    );

    // Test Old Mac OS line endings
    assert_eq!(
      tokenize("a\rb").unwrap(),
      vec![
        Spanned {
          value: Token::Identifier,
          span: 0..1
        },
        Spanned {
          value: Token::Identifier,
          span: 2..3
        },
      ]
    );
  }

  #[test]
  fn test_oc_lexer_strings()
  {
    assert_eq!(
      tokenize(r#""12",  "foo""#).unwrap(),
      vec![
        Spanned {
          value: Token::String,
          span: 0..4
        },
        Spanned {
          value: Token::Comma,
          span: 4..5
        },
        Spanned {
          value: Token::String,
          span: 7..12
        },
      ]
    );
  }
}
