//! AST for OpenCypher queries

use std::{
  cell::RefCell,
  collections::{HashMap, hash_map::Entry},
  sync::atomic::AtomicU64,
};

macro_rules! create_from {
  ( $x:tt, $y:tt, $z:tt ) => {
    impl From<$x> for $z
    {
      fn from(v: $x) -> $z
      {
        $z::$y(v)
      }
    }
  };
}

/// Represent a variable name. Some variable are explicitly created by the parser, if a node/edge should be considered equal and appear in different expression.
/// For instance `()-[]->()-[]->()` needs the creation of a variable.
#[derive(Debug, Clone, Eq)]
pub struct VariableIdentifier
{
  /// Name of the variable, only useful for debug purposes
  pub(crate) name: String,
  /// Unique identifier of the variable, the uniqueness is only guaranteed within a compilation unit.
  pub(crate) id: u64,
}

impl VariableIdentifier
{
  /// Return a reference to the name
  pub fn name(&self) -> &String
  {
    &self.name
  }
  /// Consume the variable identifier and return its name
  pub fn take_name(self) -> String
  {
    self.name
  }
}

impl PartialEq for VariableIdentifier
{
  fn eq(&self, other: &Self) -> bool
  {
    self.id == other.id
  }
}

impl std::hash::Hash for VariableIdentifier
{
  fn hash<H: std::hash::Hasher>(&self, state: &mut H)
  {
    self.id.hash(state);
  }
}

#[derive(Default, Debug)]
pub struct VariableIdentifiers
{
  next_id: AtomicU64,
  identifiers: RefCell<HashMap<String, VariableIdentifier>>,
}

impl VariableIdentifiers
{
  fn next_id(&self) -> u64
  {
    self
      .next_id
      .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
  }
  pub fn create_variable_from_name(&self, name: &str) -> VariableIdentifier
  {
    match self.identifiers.borrow_mut().entry(name.to_string())
    {
      Entry::Occupied(entry) => entry.get().clone(),
      Entry::Vacant(entry) =>
      {
        let vn = VariableIdentifier {
          name: entry.key().clone(),
          id: self.next_id(),
        };
        entry.insert(vn.clone());
        vn
      }
    }
  }
  pub(super) fn create_variable_from_name_optional(
    &self,
    name: Option<&str>,
  ) -> Option<VariableIdentifier>
  {
    name.map(|name| self.create_variable_from_name(name))
  }
  pub(super) fn create_anonymous_variable(&self) -> VariableIdentifier
  {
    let id = self.next_id();
    VariableIdentifier {
      name: format!("anonymous_{}", id),
      id,
    }
  }
}

/// OpenCypher statement
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Statement
{
  CreateGraph(CreateGraph),
  DropGraph(DropGraph),
  UseGraph(UseGraph),
  Create(Create),
  CreateVectorIndex(CreateVectorIndex),
  Match(Match),
  Return(Return),
  Call(Call),
  With(With),
  Unwind(Unwind),
  Delete(Delete),
  Update(Update),
}

macro_rules! create_from_statement {
  ( $x:tt ) => {
    create_from! { $x, $x, Statement }
  };
}

/// Ordered list of statements.
pub type Statements = Vec<Statement>;

/// Ordered list of queries. A query is a list of statements.
pub type Queries = Vec<Statements>;

/// Statement for creating a graph.
#[derive(Debug)]
pub struct CreateGraph
{
  /// Name of the graph to create.
  pub name: String,
  /// Indicate if it should ignore in case the graph already exists.
  pub if_not_exists: bool,
}

create_from_statement!(CreateGraph);

/// Drop the graph.
#[derive(Debug)]
pub struct DropGraph
{
  /// Name of the graph to drop.
  pub name: String,
  /// Indicate if it should ignore in case the graph does not exists.
  pub if_exists: bool,
}
create_from_statement!(DropGraph);

/// Select the graph
#[derive(Debug)]
pub struct UseGraph
{
  /// Name of the graph to use.
  pub name: String,
}
create_from_statement!(UseGraph);

/// Create nodes and/or edges.
#[derive(Debug)]
pub struct Create
{
  /// Pattern for creation.
  pub patterns: Vec<Pattern>,
}

create_from_statement! {Create}

/// Search clause for vector search
#[derive(Debug)]
pub struct Search
{
  /// Variable to search in the patterns
  pub variable: VariableIdentifier,
  /// Vector index name
  pub index: String,
  /// Query expression (vector or parameter)
  pub query: Expression,
  /// Result limit
  pub limit: usize,
  /// Optional score alias
  pub score_alias: Option<VariableIdentifier>,
}

/// Match query
#[derive(Debug)]
pub struct Match
{
  /// Node and edge patterns
  pub patterns: Vec<Pattern>,
  /// Filtering where expression
  pub where_expression: Option<Expression>,
  /// Indicate if the results from the query are optional
  pub optional: bool,
  /// Optional vector search clause
  pub search: Option<Search>,
}

create_from_statement! {Match}

#[derive(Debug)]
pub struct Return
{
  pub all: bool,
  pub expressions: Vec<NamedExpression>,
  pub modifiers: Modifiers,
  pub where_expression: Option<Expression>,
}

create_from_statement! {Return}

#[derive(Debug)]
pub struct With
{
  pub all: bool,
  pub expressions: Vec<NamedExpression>,
  pub modifiers: Modifiers,
  pub where_expression: Option<Expression>,
}

create_from_statement! {With}

#[derive(Debug)]
pub struct Unwind
{
  pub identifier: VariableIdentifier,
  pub expression: Expression,
}

create_from_statement! {Unwind}

#[derive(Debug)]
pub struct Delete
{
  pub detach: bool,
  pub expressions: Vec<Expression>,
}

create_from_statement! {Delete}

#[derive(Debug)]
pub struct Update
{
  pub updates: Vec<OneUpdate>,
}

create_from_statement! {Update}

#[derive(Debug)]
pub struct Call
{
  pub name: String,
  pub arguments: Vec<Expression>,
}

create_from_statement! {Call}

/// Metric for vector distance calculation
#[derive(Debug, Clone, PartialEq)]
pub enum Metric
{
  /// Cosine similarity
  Cosine,
  /// L2 (Euclidean) distance
  L2,
  /// Dot product
  DotProduct,
}

/// Create vector index statement
#[derive(Debug)]
pub struct CreateVectorIndex
{
  /// Index name
  pub name: String,
  /// Label (without colon)
  pub label: String,
  /// Property name
  pub property: String,
  /// Vector dimension
  pub dimension: Option<usize>,
  /// Distance metric
  pub metric: Option<Metric>,
}

create_from_statement! {CreateVectorIndex}

// Set/remove Statements

#[derive(Debug)]
pub enum OneUpdate
{
  SetProperty(UpdateProperty),
  AddProperty(UpdateProperty),
  RemoveProperty(RemoveProperty),
  AddLabels(AddRemoveLabels),
  RemoveLabels(AddRemoveLabels),
}

#[derive(Debug)]
pub struct UpdateProperty
{
  pub target: VariableIdentifier,
  pub path: Vec<String>,
  pub expression: Expression,
}

#[derive(Debug)]
pub struct RemoveProperty
{
  pub target: VariableIdentifier,
  pub path: Vec<String>,
}

#[derive(Debug)]
pub struct AddRemoveLabels
{
  pub target: VariableIdentifier,
  pub labels: Vec<String>,
}

// Modifiers

#[derive(Debug)]
pub struct OrderBy
{
  pub expressions: Vec<OrderByExpression>,
}

#[derive(Default, Debug)]
pub struct Modifiers
{
  pub skip: Option<Expression>,
  pub limit: Option<Expression>,
  pub order_by: Option<OrderBy>,
}

// Expressions

#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Expression
{
  Array(Array),
  FunctionCall(FunctionCall),
  Map(Map),
  IndexAccess(Box<IndexAccess>),
  RangeAccess(Box<RangeAccess>),
  MemberAccess(Box<MemberAccess>),
  Parameter(Parameter),
  Value(Value),
  Variable(Variable),

  LogicalAnd(Box<LogicalAnd>),
  LogicalOr(Box<LogicalOr>),
  LogicalXor(Box<LogicalXor>),
  RelationalEqual(Box<RelationalEqual>),
  RelationalDifferent(Box<RelationalDifferent>),
  RelationalInferior(Box<RelationalInferior>),
  RelationalSuperior(Box<RelationalSuperior>),
  RelationalInferiorEqual(Box<RelationalInferiorEqual>),
  RelationalSuperiorEqual(Box<RelationalSuperiorEqual>),
  RelationalIn(Box<RelationalIn>),
  RelationalNotIn(Box<RelationalNotIn>),

  Addition(Box<Addition>),
  Subtraction(Box<Subtraction>),
  Multiplication(Box<Multiplication>),
  Division(Box<Division>),
  Modulo(Box<Modulo>),
  Exponent(Box<Exponent>),

  Negation(Box<Negation>),
  LogicalNegation(Box<LogicalNegation>),
  IsNull(Box<IsNull>),
  IsNotNull(Box<IsNotNull>),
}

// Order By Expression

#[derive(Debug)]
pub struct OrderByExpression
{
  pub asc: bool,
  pub expression: Expression,
}

// Values: CreatePatterns

#[derive(Debug)]
pub enum Pattern
{
  Node(NodePattern),
  Edge(EdgePattern),
  Path(PathPattern),
}

macro_rules! create_from_pattern {
  ( $x:tt, $y:tt ) => {
    create_from! { $x, $y, Pattern }
  };
}

#[derive(Debug, Clone)]
pub struct NodePattern
{
  pub variable: Option<VariableIdentifier>,
  pub labels: LabelExpression,
  pub properties: Option<Expression>,
}

create_from_pattern! {NodePattern, Node}

#[derive(Debug, Clone)]
pub struct EdgePattern
{
  pub variable: Option<VariableIdentifier>,
  pub source: NodePattern,
  pub destination: NodePattern,
  pub directivity: graphcore::EdgeDirectivity,
  pub labels: LabelExpression,
  pub properties: Option<Expression>,
  /// Indicates the range for a recursive match.
  pub recursive_range: Option<std::ops::Range<usize>>,
}

create_from_pattern! {EdgePattern, Edge}

#[derive(Debug, Clone)]
pub struct PathPattern
{
  pub variable: VariableIdentifier,
  pub edge: EdgePattern,
}

create_from_pattern! {PathPattern, Path}

// Label Expression
#[derive(Debug, Clone, PartialEq)]
pub enum LabelExpression
{
  #[allow(dead_code)]
  Not(Box<LabelExpression>),
  And(Vec<LabelExpression>),
  Or(Vec<LabelExpression>),
  String(String),
  None,
}

impl LabelExpression
{
  /// Return true if all labels should match, i.e. if it is an `and` expression.
  pub fn is_all_inclusive(&self) -> bool
  {
    match self
    {
      LabelExpression::None => true,
      LabelExpression::And(exprs) => !exprs.iter().any(|f| !f.is_all_inclusive()),
      LabelExpression::Or(_) => false,
      LabelExpression::String(_) => true,
      LabelExpression::Not(_) => false,
    }
  }
  /// Return true if it is an empty label expression.
  pub fn is_none(&self) -> bool
  {
    matches!(self, LabelExpression::None)
  }
  /// Return true if it is a string expression.
  pub fn is_string(&self) -> bool
  {
    matches!(self, LabelExpression::String(_))
  }
}

// Expressions

macro_rules! create_from_expr {
  ( $x:tt ) => {
    impl From<$x> for Expression
    {
      fn from(v: $x) -> Expression
      {
        Expression::$x(v)
      }
    }
  };
}

macro_rules! create_from_boxed_expr {
  ( $x:tt ) => {
    impl From<$x> for Expression
    {
      fn from(v: $x) -> Expression
      {
        Expression::$x(Box::new(v))
      }
    }
  };
}

#[derive(Debug, Clone)]
pub struct NamedExpression
{
  pub identifier: VariableIdentifier,
  pub expression: Expression,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Parameter
{
  pub name: String,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Variable
{
  pub identifier: VariableIdentifier,
}

create_from_expr! {Variable}

#[derive(Debug, Clone, PartialEq)]
pub struct MemberAccess
{
  pub left: Expression,
  pub path: Vec<String>,
}

create_from_boxed_expr! {MemberAccess}

#[derive(Debug, Clone, PartialEq)]
pub struct IndexAccess
{
  pub left: Expression,
  pub index: Expression,
}

create_from_boxed_expr! {IndexAccess}

#[derive(Debug, Clone, PartialEq)]
pub struct RangeAccess
{
  pub left: Expression,
  pub start: Option<Expression>,
  pub end: Option<Expression>,
}

create_from_boxed_expr! {RangeAccess}

#[derive(Debug, Clone, PartialEq)]
pub struct FunctionCall
{
  pub name: String,
  pub arguments: Vec<Expression>,
}

create_from_expr! {FunctionCall}

macro_rules! create_binary_op {
  ( $x:tt ) => {
    #[derive(Debug, Clone, PartialEq)]
    pub struct $x
    {
      pub left: Expression,
      pub right: Expression,
    }

    create_from_boxed_expr! { $x }
  };
}

create_binary_op! {LogicalAnd}
create_binary_op! {LogicalOr}
create_binary_op! {LogicalXor}
create_binary_op! {RelationalEqual}
create_binary_op! {RelationalDifferent}
create_binary_op! {RelationalInferior}
create_binary_op! {RelationalSuperior}
create_binary_op! {RelationalInferiorEqual}
create_binary_op! {RelationalSuperiorEqual}
create_binary_op! {RelationalIn}
create_binary_op! {RelationalNotIn}

create_binary_op! {Addition}
create_binary_op! {Subtraction}
create_binary_op! {Multiplication}
create_binary_op! {Division}
create_binary_op! {Modulo}
create_binary_op! {Exponent}

macro_rules! create_unary_op {
  ( $x:tt ) => {
    #[derive(Debug, Clone, PartialEq)]
    pub struct $x
    {
      pub value: Expression,
    }
    create_from_boxed_expr! { $x }
  };
}

create_unary_op! {LogicalNegation}
create_unary_op! {Negation}
create_unary_op! {IsNull}
create_unary_op! {IsNotNull}

// Values

#[derive(Debug, Clone, PartialEq)]
pub struct Value
{
  pub value: graphcore::Value,
}

create_from_expr! {Value}

#[derive(Debug, Clone, PartialEq)]
pub struct Map
{
  pub map: Vec<(String, Expression)>,
}

create_from_expr! {Map}

#[derive(Debug, Clone, PartialEq)]
pub struct Array
{
  pub array: Vec<Expression>,
}

create_from_expr! {Array}
