// Copyright 2023 Silvio Mayolo
//
// This file is part of GDLisp.
//
// GDLisp is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GDLisp is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GDLisp. If not, see <https://www.gnu.org/licenses/>.

//! This module provides several helpers for syntactic forms which
//! need to be special-cased in the parser.
//!
//! This includes forms such as `(quote x)`, which is generated by the
//! `'x` syntax.

use super::ast::AST;
use crate::pipeline::source::SourceOffset;
use crate::ir::special_form::access_slot::ACCESS_SLOT_FORM_NAME;

/// Produces an arbitrary syntactic expression of the form `(a b)`,
/// where `a` is a symbol and `b` is an arbitrary [`AST`].
pub fn unary(head: &str, value: AST, pos: SourceOffset) -> AST {
  AST::list(vec!(
    AST::symbol(head, pos),
    value
  ), pos)
}

/// Produces an arbitrary syntactic expression of the form `(a b c)`,
/// where `a` is a symbol, and `b` and `c` are arbitrary [`AST`]
/// values.
pub fn binary(head: &str, b: AST, c: AST, pos: SourceOffset) -> AST {
  AST::list(vec!(
    AST::symbol(head, pos),
    b,
    c,
  ), pos)
}

/// Produces an arbitrary syntactic expression of the form `(a b c)`,
/// where `a` is a symbol, and `b`, `c`, and `d` are arbitrary [`AST`]
/// values.
pub fn trinary(head: &str, b: AST, c: AST, d: AST, pos: SourceOffset) -> AST {
  AST::list(vec!(
    AST::symbol(head, pos),
    b,
    c,
    d,
  ), pos)
}

/// Produces the syntactic form `(quote value)`.
pub fn quote(value: AST, pos: SourceOffset) -> AST {
  unary("quote", value, pos)
}

/// Produces the syntactic form `(function value)`.
pub fn function(value: AST, pos: SourceOffset) -> AST {
  unary("function", value, pos)
}

/// Produces the syntactic form `(quasiquote value)`.
pub fn quasiquote(value: AST, pos: SourceOffset) -> AST {
  unary("quasiquote", value, pos)
}

/// Produces the syntactic form `(unquote value)`.
pub fn unquote(value: AST, pos: SourceOffset) -> AST {
  unary("unquote", value, pos)
}

/// Produces the syntactic form `(unquote-spliced value)`.
pub fn unquote_spliced(value: AST, pos: SourceOffset) -> AST {
  unary("unquote-spliced", value, pos)
}

/// Produces the syntactic form `(array ...)`, given the tail of the
/// list of arguments.
pub fn array(tail: AST, pos: SourceOffset) -> AST {
  AST::cons(AST::symbol("array", pos), tail, pos)
}

/// Produces the syntactic form `(dict ...)`, given the tail of the
/// list of arguments.
pub fn dict(tail: AST, pos: SourceOffset) -> AST {
  AST::cons(AST::symbol("dict", pos), tail, pos)
}

/// Produces the syntactic form `(vector x y)`
pub fn vector2(x: AST, y: AST, pos: SourceOffset) -> AST {
  binary("vector", x, y, pos)
}

/// Produces the syntactic form `(vector x y z)`
pub fn vector3(x: AST, y: AST, z: AST, pos: SourceOffset) -> AST {
  trinary("vector", x, y, z, pos)
}

/// Produces the syntactic form `(access-slot x y)`
pub fn access_slot(x: AST, y: AST, pos: SourceOffset) -> AST {
  binary(ACCESS_SLOT_FORM_NAME, x, y, pos)
}

/// Produces a function call which will perform `(x:get-node y)`.
///
/// Written in full generality, this is the form `((access-slot x
/// get-node) y)`.
pub fn get_node_on(x: AST, y: AST, pos: SourceOffset) -> AST {
  AST::list(vec!(
    AST::symbol("sys/get-node", pos),
    x,
    y,
  ), pos)
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::sxp::ast::AST;

  #[test]
  fn test_unary() {
    assert_eq!(
      unary("foo", AST::int(10, SourceOffset(1)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("foo", SourceOffset(3)),
          AST::int(10, SourceOffset(1)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_binary() {
    assert_eq!(
      binary("bar", AST::int(10, SourceOffset(1)), AST::int(20, SourceOffset(99)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("bar", SourceOffset(3)),
          AST::int(10, SourceOffset(1)),
          AST::int(20, SourceOffset(99)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_trinary() {
    assert_eq!(
      trinary("baz", AST::int(10, SourceOffset(1)), AST::int(20, SourceOffset(99)), AST::int(30, SourceOffset(999)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("baz", SourceOffset(3)),
          AST::int(10, SourceOffset(1)),
          AST::int(20, SourceOffset(99)),
          AST::int(30, SourceOffset(999)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_quote() {
    assert_eq!(
      quote(AST::int(9, SourceOffset(100)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("quote", SourceOffset(3)),
          AST::int(9, SourceOffset(100)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_function() {
    assert_eq!(
      function(AST::int(9, SourceOffset(100)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("function", SourceOffset(3)),
          AST::int(9, SourceOffset(100)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_quasiquote() {
    assert_eq!(
      quasiquote(AST::int(9, SourceOffset(100)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("quasiquote", SourceOffset(3)),
          AST::int(9, SourceOffset(100)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_unquote() {
    assert_eq!(
      unquote(AST::int(9, SourceOffset(100)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("unquote", SourceOffset(3)),
          AST::int(9, SourceOffset(100)),
        ),
        SourceOffset(3),
      )
    )
  }

  #[test]
  fn test_unquote_spliced() {
    assert_eq!(
      unquote_spliced(AST::int(9, SourceOffset(100)), SourceOffset(3)),
      AST::list(
        vec!(
          AST::symbol("unquote-spliced", SourceOffset(3)),
          AST::int(9, SourceOffset(100)),
        ),
        SourceOffset(3),
      )
    )
  }

  // TODO The rest of these

}
