use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use std::{
  hash::Hash,
  ops::{Add, Div, Mul, Neg, Rem, Sub},
};

mod value_map;

pub(crate) use crate::prelude::*;
use crate::TimeStamp;

pub use value_map::ValueMap;

/// Represent a value in a properties for a Node or an Edge.
#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
#[serde(untagged)]
pub enum Value
{
  /// Null value.
  #[default]
  Null,
  /// A UUID Key in the graph.
  Key(graph::Key),
  /// Boolean value.
  Boolean(bool),
  /// Signed integer value.
  Integer(i64),
  #[serde(
    serialize_with = "serialize_with::serialize_float",
    deserialize_with = "serialize_with::deserialize_float"
  )]
  /// Floating point value.
  Float(f64),
  /// String value.
  String(String),
  /// Timestamp value
  TimeStamp(timestamp::TimeStamp),
  /// Array of values.
  Array(Vec<Value>),
  /// Unordered map of values.
  Map(ValueMap),
  /// A node in the graph.
  Node(graph::Node),
  /// An edge in the graph.
  Edge(graph::Edge),
  /// A path in the graph.
  Path(graph::SinglePath),
}

impl Value
{
  /// Transform this value into a map. This function is guaranteed to succeed,
  /// in case the value does not contains a map, it will create a default empty
  /// map.
  pub fn into_map(self) -> ValueMap
  {
    match self
    {
      Value::Map(o) => o.clone(),
      _ => ValueMap::new(),
    }
  }
  /// Return true if the value is null, false otherwise.
  pub fn is_null(&self) -> bool
  {
    matches!(self, Value::Null)
  }
  /// Remove all elements of a map that are null. Walk through the map values recursively.
  pub fn remove_null(self) -> Self
  {
    match self
    {
      Value::Map(object) => object.remove_null().into(),
      o => o,
    }
  }
}
impl Hash for Value
{
  fn hash<H: std::hash::Hasher>(&self, state: &mut H)
  {
    match self
    {
      Value::Null =>
      {}
      Value::Key(k) => k.hash(state),
      Value::Boolean(b) => b.hash(state),
      Value::Integer(i) => i.hash(state),
      Value::Float(f) =>
      {
        let bits = if f.is_nan()
        {
          0x7ff8000000000000
        }
        else
        {
          f.to_bits()
        };
        bits.hash(state);
      }
      Value::String(s) => s.hash(state),
      Value::TimeStamp(ts) => ts.hash(state),
      Value::Array(a) => a.hash(state),
      Value::Map(m) => m.hash(state),
      Value::Node(n) => n.hash(state),
      Value::Edge(e) => e.hash(state),
      Value::Path(p) => p.hash(state),
    }
  }
}

impl Add for Value
{
  type Output = Result<Value>;
  fn add(self, rhs: Self) -> Self::Output
  {
    match self
    {
      Value::Boolean(..)
      | Value::Key(..)
      | Value::Node(..)
      | Value::Edge(..)
      | Value::Map(..)
      | Value::TimeStamp(..)
      | Value::Path(..) => Err(Error::InvalidBinaryOperands),
      Value::Null => Ok(Value::Null),
      Self::Array(lhs) => match rhs
      {
        Self::Array(rhs) =>
        {
          let mut lhs = lhs.clone();
          lhs.append(&mut rhs.clone());
          Ok(lhs.into())
        }
        _ =>
        {
          let mut lhs = lhs.clone();
          lhs.push(rhs.clone());
          Ok(lhs.into())
        }
      },
      Self::Float(lhs) => match rhs
      {
        Self::Float(rhs) => Ok((lhs + rhs).into()),
        Self::Integer(rhs) => Ok((lhs + rhs as f64).into()),
        Self::Null => Ok(Self::Null),
        _ => Err(Error::InvalidBinaryOperands),
      },
      Self::Integer(lhs) => match rhs
      {
        Self::Float(rhs) => Ok((lhs as f64 + rhs).into()),
        Self::Integer(rhs) => Ok((lhs + rhs).into()),
        Self::Null => Ok(Self::Null),
        _ => Err(Error::InvalidBinaryOperands),
      },
      Self::String(lhs) => match rhs
      {
        Self::String(rhs) => Ok((lhs + &rhs).into()),
        Self::Null => Ok(Self::Null),
        _ => Err(Error::InvalidBinaryOperands),
      },
    }
  }
}

macro_rules! impl_mdsr {
  ($x:tt, $op:tt) => {
    impl $x for Value
    {
      type Output = Result<Value>;
      fn $op(self, rhs: Self) -> Self::Output
      {
        match self
        {
          Value::Boolean(..)
          | Value::Key(..)
          | Value::String(..)
          | Value::TimeStamp(..)
          | Value::Node(..)
          | Value::Edge(..)
          | Value::Array(..)
          | Value::Map(..)
          | Value::Path(..) => Err(Error::InvalidBinaryOperands.into()),
          Value::Null => Ok(Value::Null),
          Self::Float(lhs) => match rhs
          {
            Self::Float(rhs) => Ok(lhs.$op(rhs).into()),
            Self::Integer(rhs) => Ok(lhs.$op(rhs as f64).into()),
            Self::Null => Ok(Self::Null),
            _ => Err(Error::InvalidBinaryOperands.into()),
          },
          Self::Integer(lhs) => match rhs
          {
            Self::Float(rhs) => Ok((lhs as f64).$op(rhs).into()),
            Self::Integer(rhs) => Ok(lhs.$op(rhs).into()),
            Self::Null => Ok(Self::Null),
            _ => Err(Error::InvalidBinaryOperands.into()),
          },
        }
      }
    }
  };
}

impl_mdsr!(Mul, mul);
impl_mdsr!(Sub, sub);
impl_mdsr!(Div, div);
impl_mdsr!(Rem, rem);

impl Value
{
  /// Compute this value to the power of rhs. Return an error if called a non-number values.
  pub fn pow(self, rhs: Value) -> Result<Value>
  {
    match self
    {
      Value::Boolean(..)
      | Value::Key(..)
      | Value::String(..)
      | Value::TimeStamp(..)
      | Value::Node(..)
      | Value::Edge(..)
      | Value::Array(..)
      | Value::Map(..)
      | Value::Path(..) => Err(Error::InvalidBinaryOperands),
      Value::Null => Ok(Value::Null),
      Self::Float(lhs) => match rhs
      {
        Self::Float(rhs) => Ok(lhs.powf(rhs).into()),
        Self::Integer(rhs) => Ok(lhs.powf(rhs as f64).into()),
        Self::Null => Ok(Self::Null),
        _ => Err(Error::InvalidBinaryOperands),
      },
      Self::Integer(lhs) => match rhs
      {
        Self::Float(rhs) => Ok((lhs as f64).powf(rhs).into()),
        Self::Integer(rhs) => match rhs.try_into()
        {
          Ok(rhs) => Ok(lhs.pow(rhs).into()),
          Err(_) => Ok((lhs as f64).powf(rhs as f64).into()),
        },
        Self::Null => Ok(Self::Null),
        _ => Err(Error::InvalidBinaryOperands),
      },
    }
  }
}

impl Neg for Value
{
  type Output = Result<Value>;
  fn neg(self) -> Self::Output
  {
    match self
    {
      Self::Float(fl) => Ok((-fl).into()),
      Self::Integer(i) => Ok((-i).into()),
      Value::Null => Ok(Value::Null),
      Value::Boolean(..)
      | Value::Key(..)
      | Value::String(..)
      | Value::Node(..)
      | Value::Edge(..)
      | Value::TimeStamp(..)
      | Value::Array(..)
      | Value::Map(..)
      | Value::Path(..) => Err(Error::InvalidNegationOperands),
    }
  }
}

impl std::fmt::Display for Value
{
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
  {
    match self
    {
      Value::Null => write!(f, "null"),
      Value::Key(k) => write!(f, "<{}>", k.uuid()),
      Value::Boolean(b) => write!(f, "{}", b),
      Value::Integer(i) => write!(f, "{}", i),
      Value::Float(fl) => write!(f, "{}", fl),
      Value::String(s) => write!(f, "{}", s),
      Value::TimeStamp(t) => write!(f, "{}", t),
      Value::Array(v) => write!(f, "[{}]", v.iter().map(|x| x.to_string()).join(", ")),
      Value::Map(o) => write!(f, "{}", o),
      Value::Node(n) => write!(f, "{}", n),
      Value::Edge(e) => write!(f, "{}", e),
      Value::Path(p) => write!(f, "{}", p),
    }
  }
}

/// Trait to return a reference to the underlying type
pub trait ValueTryIntoRef<T>
{
  /// Return a reference to T
  fn try_into_ref(&self) -> Result<&T, Error>;
}

impl ValueTryIntoRef<Value> for Value
{
  fn try_into_ref(&self) -> Result<&Value, Error>
  {
    Ok(self)
  }
}

impl<T> From<Option<T>> for Value
where
  Value: From<T>,
{
  fn from(value: Option<T>) -> Self
  {
    match value
    {
      Some(value) => value.into(),
      None => Value::Null,
    }
  }
}

/// Used to try conversion and return value instead of error
#[allow(clippy::large_enum_variant)]
pub enum FromValueResult<T>
{
  /// Converted to T
  Ok(T),
  /// Could not convert to T, return original value then
  Invalid(Value),
}

macro_rules! impl_from_value {
  ($type:ty, $vn:tt, try) => {
    impl_from_value!($type, $vn);
    impl TryFrom<Value> for $type
    {
      type Error = Error;
      fn try_from(value: Value) -> Result<$type, Self::Error>
      {
        match value
        {
          Value::$vn(v) => Ok(v),
          _ => Err(
            Error::InvalidValueCast {
              value: Box::new(value),
              typename: stringify!($type),
            }
            .into(),
          ),
        }
      }
    }
    impl From<Value> for FromValueResult<$type>
    {
      fn from(value: Value) -> FromValueResult<$type>
      {
        match value
        {
          Value::$vn(v) => FromValueResult::Ok(v),
          _ => FromValueResult::Invalid(value),
        }
      }
    }
  };
  ($type:ty, $vn:tt) => {
    impl From<$type> for Value
    {
      fn from(v: $type) -> Value
      {
        Value::$vn(v)
      }
    }

    impl From<Vec<$type>> for Value
    {
      fn from(v: Vec<$type>) -> Value
      {
        Value::Array(v.into_iter().map(|v| v.into()).collect())
      }
    }
    impl TryFrom<&Value> for $type
    {
      type Error = Error;
      fn try_from(value: &Value) -> Result<$type, Self::Error>
      {
        <$type>::try_from(value.to_owned())
      }
    }
    impl TryFrom<Value> for Option<$type>
    {
      type Error = Error;
      fn try_from(value: Value) -> Result<Self>
      {
        match value
        {
          Value::Null => Ok(None),
          _ =>
          {
            let t: $type = value.try_into()?;
            Ok(Some(t))
          }
        }
      }
    }
    impl TryFrom<&Value> for Option<$type>
    {
      type Error = Error;
      fn try_from(value: &Value) -> Result<Option<$type>, Self::Error>
      {
        <Option<$type>>::try_from(value.to_owned())
      }
    }
    impl ValueTryIntoRef<$type> for Value
    {
      fn try_into_ref(&self) -> Result<&$type, Error>
      {
        match self
        {
          Value::$vn(v) => Ok(v),
          _ => Err(
            Error::InvalidValueCast {
              value: Box::new(self.clone()),
              typename: stringify!($type),
            }
            .into(),
          ),
        }
      }
    }
  };
}

impl_from_value!(graph::Key, Key, try);
impl_from_value!(bool, Boolean, try);
impl_from_value!(i64, Integer, try);
impl_from_value!(f64, Float);
impl_from_value!(String, String, try);
impl_from_value!(TimeStamp, TimeStamp);
impl_from_value!(graph::Node, Node, try);
impl_from_value!(graph::Edge, Edge, try);
impl_from_value!(graph::SinglePath, Path, try);
impl_from_value!(Vec<Value>, Array, try);
impl_from_value!(ValueMap, Map, try);

impl TryFrom<Value> for f64
{
  type Error = Error;
  fn try_from(value: Value) -> Result<f64, Self::Error>
  {
    match value
    {
      Value::Integer(i) => Ok(i as f64),
      Value::Float(f) => Ok(f),
      _ => Err(Error::InvalidValueCast {
        value: Box::new(value),
        typename: stringify!($type),
      }),
    }
  }
}

impl From<Value> for FromValueResult<f64>
{
  fn from(value: Value) -> FromValueResult<f64>
  {
    match value
    {
      Value::Integer(i) => FromValueResult::Ok(i as f64),
      Value::Float(f) => FromValueResult::Ok(f),
      _ => FromValueResult::Invalid(value),
    }
  }
}

impl From<Value> for FromValueResult<Value>
{
  fn from(value: Value) -> FromValueResult<Value>
  {
    FromValueResult::Ok(value)
  }
}

impl TryFrom<Value> for TimeStamp
{
  type Error = Error;
  fn try_from(value: Value) -> Result<TimeStamp, Self::Error>
  {
    match value
    {
      Value::String(s) => Ok(TimeStamp::parse(&s)?),
      Value::TimeStamp(v) => Ok(v),
      _ => Err(Error::InvalidValueCast {
        value: Box::new(value),
        typename: stringify!($type),
      }),
    }
  }
}

impl From<()> for Value
{
  fn from(_: ()) -> Self
  {
    Self::Null
  }
}

impl From<&str> for Value
{
  fn from(val: &str) -> Self
  {
    Value::String(val.into())
  }
}

/// Convenient macro for creating Array.
///
/// Example:
///
/// ```rust
/// # use graphcore::{Value, array};
/// let value_arr: Value = array!("hello", 12);
/// ```
#[macro_export]
macro_rules! array {
  () => (
      $crate::Value::Array(Default::default())
  );
  ($($x:expr),+ $(,)?) => (
    $crate::Value::Array(
      vec![$($x.into()),+]
    )
  );
}

/// Convenient macro for creating ValueMap.
///
/// Example:
///
/// ```rust
/// # use graphcore::{ValueMap, value_map};
/// let value_map: ValueMap = value_map!("hello" => 12);
/// ```
#[macro_export]
macro_rules! value_map {
  // map-like
  ($($k:expr => $v:expr),* $(,)?) => {
    {
      let value_map: $crate::ValueMap = core::convert::From::from([$(($k.to_string(), $v.into()),)*]);
      value_map
    }
  };
}
