use super::generated::*;
use crate::common::*;
use crate::swc::ast as swc_ast;
use crate::swc::common::comments::SingleThreadedCommentsMapInner;
use crate::swc::parser::token::TokenAndSpan;
pub enum NodeOrToken<'a> {
  Node(Node<'a>),
  Token(&'a TokenAndSpan),
}
impl<'a> NodeOrToken<'a> {
  pub fn unwrap_token(&self) -> &'a TokenAndSpan {
    match self {
      NodeOrToken::Token(token) => token,
      NodeOrToken::Node(node) => panic!("Expected to unwrap a token, but it was a node of kind {}.", node.kind()),
    }
  }
  pub fn unwrap_node(&self) -> &Node<'a> {
    match self {
      NodeOrToken::Node(node) => node,
      NodeOrToken::Token(token) => panic!("Expected to unwrap a node, but it was a token with text '{:?}'.", token.token),
    }
  }
}
impl<'a> SourceRanged for NodeOrToken<'a> {
  fn start(&self) -> SourcePos {
    match self {
      NodeOrToken::Node(node) => node.start(),
      NodeOrToken::Token(token) => token.start(),
    }
  }
  fn end(&self) -> SourcePos {
    match self {
      NodeOrToken::Node(node) => node.end(),
      NodeOrToken::Token(token) => token.end(),
    }
  }
}
macro_rules! implement_root_node {
  ($name:ty) => {
    impl<'a> RootNode<'a> for $name {
      fn maybe_text_info(&self) -> Option<&'a SourceTextInfo> {
        self.text_info
      }
      fn maybe_token_container(&self) -> Option<&'a TokenContainer<'a>> {
        self.tokens
      }
      fn maybe_comment_container(&self) -> Option<&'a CommentContainer<'a>> {
        self.comments
      }
    }
  };
}
implement_root_node!(Module<'a>);
implement_root_node!(&Module<'a>);
implement_root_node!(Script<'a>);
implement_root_node!(&Script<'a>);
#[derive(Clone, Copy)]
pub enum Program<'a> {
  Module(&'a Module<'a>),
  Script(&'a Script<'a>),
}
impl<'a> SourceRanged for Program<'a> {
  fn start(&self) -> SourcePos {
    match self {
      Program::Module(node) => node.start(),
      Program::Script(node) => node.start(),
    }
  }
  fn end(&self) -> SourcePos {
    match self {
      Program::Module(node) => node.end(),
      Program::Script(node) => node.end(),
    }
  }
}
impl<'a> NodeTrait<'a> for Program<'a> {
  fn parent(&self) -> Option<Node<'a>> {
    None
  }
  fn children(&self) -> Vec<Node<'a>> {
    match self {
      Program::Module(node) => node.children(),
      Program::Script(node) => node.children(),
    }
  }
  fn as_node(&self) -> Node<'a> {
    match self {
      Program::Module(node) => node.as_node(),
      Program::Script(node) => node.as_node(),
    }
  }
  fn kind(&self) -> NodeKind {
    match self {
      Program::Module(node) => node.kind(),
      Program::Script(node) => node.kind(),
    }
  }
}
impl<'a> From<&Program<'a>> for Node<'a> {
  fn from(node: &Program<'a>) -> Node<'a> {
    match node {
      Program::Module(node) => (*node).into(),
      Program::Script(node) => (*node).into(),
    }
  }
}
impl<'a> From<Program<'a>> for Node<'a> {
  fn from(node: Program<'a>) -> Node<'a> {
    match node {
      Program::Module(node) => node.into(),
      Program::Script(node) => node.into(),
    }
  }
}
impl<'a> RootNode<'a> for Program<'a> {
  fn maybe_text_info(&self) -> Option<&'a SourceTextInfo> {
    match self {
      Program::Module(module) => module.text_info,
      Program::Script(script) => script.text_info,
    }
  }
  fn maybe_token_container(&self) -> Option<&'a TokenContainer<'a>> {
    match self {
      Program::Module(module) => module.tokens,
      Program::Script(script) => script.tokens,
    }
  }
  fn maybe_comment_container(&self) -> Option<&'a CommentContainer<'a>> {
    match self {
      Program::Module(module) => module.comments,
      Program::Script(script) => script.comments,
    }
  }
}
pub trait NodeTrait<'a>: SourceRanged + Sized {
  fn parent(&self) -> Option<Node<'a>>;
  fn children(&self) -> Vec<Node<'a>>;
  fn as_node(&self) -> Node<'a>;
  fn kind(&self) -> NodeKind;
  fn ancestors(&self) -> AncestorIterator<'a> {
    AncestorIterator::new(self.as_node())
  }
  fn start_line(&self) -> usize {
    self.start_line_fast(self.program())
  }
  fn end_line(&self) -> usize {
    self.end_line_fast(self.program())
  }
  fn start_column(&self) -> usize {
    self.start_column_fast(self.program())
  }
  fn end_column(&self) -> usize {
    self.end_column_fast(self.program())
  }
  fn char_width(&self) -> usize {
    self.char_width_fast(self.program())
  }
  fn child_index(&self) -> usize {
    if let Some(parent) = self.parent() {
      let start_pos = self.start();
      for (i, child) in parent.children().iter().enumerate() {
        if child.start() == start_pos {
          return i;
        }
      }
      panic!("Could not find the child index for some reason.");
    } else {
      0
    }
  }
  fn previous_sibling(&self) -> Option<Node<'a>> {
    if let Some(parent) = self.parent() {
      let child_index = self.child_index();
      if child_index > 0 {
        Some(parent.children().remove(child_index - 1))
      } else {
        None
      }
    } else {
      None
    }
  }
  fn previous_siblings(&self) -> Vec<Node<'a>> {
    if let Some(parent) = self.parent() {
      let child_index = self.child_index();
      if child_index > 0 {
        let mut parent_children = parent.children();
        parent_children.drain(child_index..);
        parent_children
      } else {
        Vec::new()
      }
    } else {
      Vec::new()
    }
  }
  fn next_sibling(&self) -> Option<Node<'a>> {
    if let Some(parent) = self.parent() {
      let next_index = self.child_index() + 1;
      let mut parent_children = parent.children();
      if next_index < parent_children.len() {
        Some(parent_children.remove(next_index))
      } else {
        None
      }
    } else {
      None
    }
  }
  fn next_siblings(&self) -> Vec<Node<'a>> {
    if let Some(parent) = self.parent() {
      let next_index = self.child_index() + 1;
      let mut parent_children = parent.children();
      if next_index < parent_children.len() {
        parent_children.drain(0..next_index);
        parent_children
      } else {
        Vec::new()
      }
    } else {
      Vec::new()
    }
  }
  fn tokens(&self) -> &'a [TokenAndSpan] {
    self.tokens_fast(self.program())
  }
  fn children_with_tokens(&self) -> Vec<NodeOrToken<'a>> {
    self.children_with_tokens_fast(self.program())
  }
  fn children_with_tokens_fast(&self, program: impl RootNode<'a>) -> Vec<NodeOrToken<'a>> {
    let children = self.children();
    let tokens = self.tokens_fast(program);
    let mut result = Vec::new();
    let mut tokens_index = 0;
    for child in children {
      let child_range = child.range();
      for token in &tokens[tokens_index..] {
        if token.start() < child_range.start {
          result.push(NodeOrToken::Token(token));
          tokens_index += 1;
        } else {
          break;
        }
      }
      result.push(NodeOrToken::Node(child));
      for token in &tokens[tokens_index..] {
        if token.end() <= child_range.end {
          tokens_index += 1;
        } else {
          break;
        }
      }
    }
    for token in &tokens[tokens_index..] {
      result.push(NodeOrToken::Token(token));
    }
    result
  }
  fn leading_comments(&self) -> CommentsIterator<'a> {
    self.leading_comments_fast(self.program())
  }
  fn trailing_comments(&self) -> CommentsIterator<'a> {
    self.trailing_comments_fast(self.program())
  }
  fn program(&self) -> Program<'a> {
    let mut current: Node<'a> = self.as_node();
    while let Some(parent) = current.parent() {
      current = parent;
    }
    match current {
      Node::Module(module) => Program::Module(module),
      Node::Script(script) => Program::Script(script),
      _ => panic!("Expected the root node to be a Module or Script, but it was a {}.", current.kind()),
    }
  }
  fn module(&self) -> &Module<'a> {
    match self.program() {
      Program::Module(module) => module,
      Program::Script(_) => {
        panic!("The root node was a Script and not a Module. Use .script() or .program() instead.")
      }
    }
  }
  fn script(&self) -> &Script<'a> {
    match self.program() {
      Program::Script(script) => script,
      Program::Module(_) => {
        panic!("The root node was a Module and not a Script. Use .module() or .program() instead.")
      }
    }
  }
  fn text(&self) -> &'a str {
    self.text_fast(self.program())
  }
  fn previous_token(&self) -> Option<&'a TokenAndSpan> {
    self.previous_token_fast(self.program())
  }
  fn next_token(&self) -> Option<&'a TokenAndSpan> {
    self.next_token_fast(self.program())
  }
  fn previous_tokens(&self) -> &'a [TokenAndSpan] {
    self.previous_tokens_fast(self.program())
  }
  fn next_tokens(&self) -> &'a [TokenAndSpan] {
    self.next_tokens_fast(self.program())
  }
}
pub trait CastableNode<'a> {
  fn to(node: &Node<'a>) -> Option<&'a Self>;
  fn kind() -> NodeKind;
}
#[derive(Clone, Copy)]
pub struct Comments<'a> {
  pub leading: &'a SingleThreadedCommentsMapInner,
  pub trailing: &'a SingleThreadedCommentsMapInner,
}
#[derive(Clone, Copy)]
pub enum ProgramRef<'a> {
  Module(&'a swc_ast::Module),
  Script(&'a swc_ast::Script),
}
impl<'a> From<&'a swc_ast::Program> for ProgramRef<'a> {
  fn from(program: &'a swc_ast::Program) -> Self {
    use swc_ast::Program;
    match program {
      Program::Module(module) => ProgramRef::Module(module),
      Program::Script(script) => ProgramRef::Script(script),
    }
  }
}
impl<'a> From<&'a swc_ast::Module> for ProgramRef<'a> {
  fn from(module: &'a swc_ast::Module) -> Self {
    ProgramRef::Module(module)
  }
}
impl<'a> From<&'a swc_ast::Script> for ProgramRef<'a> {
  fn from(script: &'a swc_ast::Script) -> Self {
    ProgramRef::Script(script)
  }
}
impl<'a> SourceRanged for ProgramRef<'a> {
  fn start(&self) -> SourcePos {
    match self {
      ProgramRef::Module(node) => node.range().start,
      ProgramRef::Script(node) => node.range().start,
    }
  }
  fn end(&self) -> SourcePos {
    match self {
      ProgramRef::Module(node) => node.range().end,
      ProgramRef::Script(node) => node.range().end,
    }
  }
}
#[derive(Clone, Copy)]
pub struct ProgramInfo<'a> {
  pub program: ProgramRef<'a>,
  pub text_info: Option<&'a SourceTextInfo>,
  pub tokens: Option<&'a [TokenAndSpan]>,
  pub comments: Option<Comments<'a>>,
}
#[derive(Clone, Copy)]
pub struct ModuleInfo<'a> {
  pub module: &'a swc_ast::Module,
  pub text_info: Option<&'a SourceTextInfo>,
  pub tokens: Option<&'a [TokenAndSpan]>,
  pub comments: Option<Comments<'a>>,
}
#[derive(Clone, Copy)]
pub struct ScriptInfo<'a> {
  pub script: &'a swc_ast::Script,
  pub text_info: Option<&'a SourceTextInfo>,
  pub tokens: Option<&'a [TokenAndSpan]>,
  pub comments: Option<Comments<'a>>,
}
#[derive(Clone)]
pub struct AncestorIterator<'a> {
  current: Node<'a>,
}
impl<'a> AncestorIterator<'a> {
  pub fn new(node: Node<'a>) -> AncestorIterator<'a> {
    AncestorIterator { current: node }
  }
}
impl<'a> Iterator for AncestorIterator<'a> {
  type Item = Node<'a>;
  fn next(&mut self) -> Option<Node<'a>> {
    let parent = self.current.parent();
    if let Some(parent) = parent {
      self.current = parent;
    }
    parent
  }
}
pub trait TokenExt {
  fn token_index<'a, N: RootNode<'a>>(&self, root_node: N) -> usize;
}
impl TokenExt for TokenAndSpan {
  fn token_index<'a, N: RootNode<'a>>(&self, root_node: N) -> usize {
    root_node.token_container().get_token_index_at_start(self.start()).unwrap()
  }
}
#[cfg(test)]
mod test {
  use super::super::test_helpers::run_test;
  use crate::common::*;
  use crate::view::*;
  #[test]
  fn it_should_get_children() {
    run_test("class Test { a: string; b: number; }", |program| {
      let class_decl = program.children()[0].expect::<ClassDecl>();
      let children = class_decl.class.children();
      assert_eq!(children.len(), 2);
      assert_eq!(children[0].text(), "a: string;");
      assert_eq!(children[1].text(), "b: number;");
    });
  }
  #[test]
  fn it_should_get_all_comments() {
    run_test(
      r#"
/// <reference path="foo" />
const a = 42;
/*
 * block comment
 */
let b = true;
// line comment
let c = "";
function foo(name: /* inline comment */ string) {
  console.log(`hello, ${name}`); // greeting!
}
// trailing comment
"#,
      |program| {
        assert_eq!(program.maybe_comment_container().unwrap().all_comments().count(), 6);
      },
    );
  }
}