#![feature(step_trait)]
use crate::*;
use mech_core::*;
use std::iter::Step;
use nalgebra::{
  base::{Matrix as naMatrix, Storage, StorageMut},
  Dim, Scalar,
};
use mech_core::matrix::Matrix;
use std::marker::PhantomData;

// Exclusive ------------------------------------------------------------------

#[derive(Debug)]
pub struct RangeExclusiveScalar<T, MatA> {
  pub from: Ref<T>,
  pub to: Ref<T>,
  pub out: Ref<MatA>,
  phantom: PhantomData<T>,
}
impl<T, R1, C1, S1> MechFunctionFactory for RangeExclusiveScalar<T, naMatrix<T, R1, C1, S1>>
where
  T: Copy + Debug + Clone + Sync + Send + 
  CompileConst + ConstElem + AsValueKind +
  PartialOrd + 'static + One + Add<Output = T>,
  Ref<naMatrix<T, R1, C1, S1>>: ToValue,
  naMatrix<T, R1, C1, S1>: CompileConst + ConstElem + AsNaKind,
  R1: Dim + 'static, C1: Dim, S1: StorageMut<T, R1, C1> + Clone + Debug + 'static,
{
  fn new(args: FunctionArgs) -> MResult<Box<dyn MechFunction>> {
    match args {
      FunctionArgs::Binary(out, from, to) => {
        let from: Ref<T> = unsafe { from.as_unchecked() }.clone();
        let to: Ref<T> = unsafe { to.as_unchecked() }.clone();
        let out: Ref<naMatrix<T, R1, C1, S1>> = unsafe { out.as_unchecked() }.clone();
        Ok(Box::new(Self { from, to, out, phantom: PhantomData::default() }))
      },
      _ => Err(MechError::new(
          IncorrectNumberOfArguments { expected: 3, found: args.len() },
          None
        ).with_compiler_loc()
      ),
    }
  }
}
impl<T, R1, C1, S1> MechFunctionImpl for RangeExclusiveScalar<T, naMatrix<T, R1, C1, S1>>
where
  Ref<naMatrix<T, R1, C1, S1>>: ToValue,
  T: Copy + Scalar + Clone + Debug + Sync + Send + 'static + PartialOrd + One + Add<Output = T> + 'static,
  R1: Dim, C1: Dim, S1: StorageMut<T, R1, C1> + Clone + Debug,
{
  fn solve(&self) {
    unsafe {
      let out_ptr = self.out.as_ptr() as *mut naMatrix<T, R1, C1, S1>;
      let mut current = *self.from.as_ptr();
      for i in 0..(*out_ptr).len() {
        (&mut (*out_ptr))[i] = current;
        current = current + T::one();
      }
    }
  }
  fn out(&self) -> Value { self.out.to_value() }
  fn to_string(&self) -> String { format!("{:#?}", self) }
}
#[cfg(feature = "compiler")]
impl<T, R1, C1, S1> MechFunctionCompiler for RangeExclusiveScalar<T, naMatrix<T, R1, C1, S1>> 
where
  T: CompileConst + ConstElem + AsValueKind,
  naMatrix<T, R1, C1, S1>: CompileConst + ConstElem + AsNaKind,
{
  fn compile(&self, ctx: &mut CompileCtx) -> MResult<Register> {
    let name = format!("RangeExclusiveScalar<{}{}>", T::as_value_kind(), naMatrix::<T, R1, C1, S1>::as_na_kind());
    compile_binop!(name, self.out, self.from, self.to, ctx, FeatureFlag::Builtin(FeatureKind::RangeExclusive) );
  }
}

#[macro_export]
macro_rules! impl_range_exclusive_match_arms {
  ($fxn:ident, $arg1:expr, $arg2:expr, $($ty:tt, $feat:tt);+ $(;)?) => {
    paste! {
      match ($arg1, $arg2) {
        $(
          #[cfg(feature = $feat)]
          (Value::[<$ty:camel>](from), Value::[<$ty:camel>](to))  => {
            let from_val = *from.borrow();
            let to_val = *to.borrow();
            let diff = to_val - from_val;
            if diff < $ty::zero() {
              return Err(MechError::new(
                EmptyRangeError{},
                None
              ).with_compiler_loc());
            }
            let size = range_size_to_usize!(diff, $ty);
            let mut vec = vec![from_val; size];
            match size {
              0 => Err(MechError::new(
                EmptyRangeError{},
                None
              ).with_compiler_loc()),
              #[cfg(feature = "matrix1")]
              1 => {
                register_range!($fxn, $ty, $feat, Matrix1);
                Ok(Box::new($fxn::<$ty,Matrix1<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(Matrix1::from_element(vec[0])), phantom: PhantomData::default()}))
              }
              #[cfg(all(not(feature = "matrix1"), feature = "matrixd")  )]
              1 => {
                register_range!($fxn, $ty, $feat, DMatrix);
                Ok(Box::new($fxn::<$ty,DMatrix<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(DMatrix::from_element(1,1,vec[0])), phantom: PhantomData::default()}))
              }
              #[cfg(feature = "row_vector2")]
              2 => {
                register_range!($fxn, $ty, $feat, RowVector2);
                Ok(Box::new($fxn::<$ty,RowVector2<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(RowVector2::from_vec(vec)), phantom: PhantomData::default()}))
              }
              #[cfg(feature = "row_vector3")]
              3 => {              
                register_range!($fxn, $ty, $feat, RowVector3);
                Ok(Box::new($fxn::<$ty,RowVector3<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(RowVector3::from_vec(vec)), phantom: PhantomData::default()}))
              }
              #[cfg(feature = "row_vector4")]
              4 => {
                register_range!($fxn, $ty, $feat, RowVector4);
                Ok(Box::new($fxn::<$ty,RowVector4<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(RowVector4::from_vec(vec)), phantom: PhantomData::default()}))
              }
              #[cfg(feature = "row_vectord")]
              n => {
                register_range!($fxn, $ty, $feat, RowDVector);
                Ok(Box::new($fxn::<$ty,RowDVector<$ty>>{from: from.clone(), to: to.clone(), out: Ref::new(RowDVector::from_vec(vec)), phantom: PhantomData::default()}))
              }
            }
          }
        )+
        (arg1,arg2) => Err(MechError::new(
          UnhandledFunctionArgumentKind2 {arg: (arg1.kind(),arg2.kind()), fxn_name: stringify!($fxn).to_string() },
          None
        ).with_compiler_loc()),
      }
    }
  }
}

fn impl_range_exclusive_fxn(arg1_value: Value, arg2_value: Value) -> MResult<Box<dyn MechFunction>> {
  impl_range_exclusive_match_arms!(RangeExclusiveScalar, arg1_value, arg2_value,
    f32, "f32";
    f64, "f64";
    i8,  "i8";
    i16, "i16";
    i32, "i32";
    i64, "i64";
    i128,"i128";
    u8,  "u8";
    u16, "u16";
    u32, "u32";
    u64, "u64";
    u128,"u128";
  )
}

pub struct RangeExclusive {}

impl NativeFunctionCompiler for RangeExclusive {
  fn compile(&self, arguments: &Vec<Value>) -> MResult<Box<dyn MechFunction>> {
    if arguments.len() != 2 {
      return Err(MechError::new(IncorrectNumberOfArguments { expected: 2, found: arguments.len() },None).with_compiler_loc());
    }
    let arg1 = arguments[0].clone();
    let arg2 = arguments[1].clone();
    match impl_range_exclusive_fxn(arg1.clone(), arg2.clone()) {
      Ok(fxn) => Ok(fxn),
      Err(_) => {
        match (arg1,arg2) {
          (Value::MutableReference(arg1),Value::MutableReference(arg2)) => {impl_range_exclusive_fxn(arg1.borrow().clone(),arg2.borrow().clone())}
          (Value::MutableReference(arg1),arg2) => {impl_range_exclusive_fxn(arg1.borrow().clone(),arg2.clone())}
          (arg1,Value::MutableReference(arg2)) => {impl_range_exclusive_fxn(arg1.clone(),arg2.borrow().clone())}
          (arg1,arg2) => Err(MechError::new(
              UnhandledFunctionArgumentKind2 { arg: (arg1.kind(),arg2.kind()), fxn_name: "range/exclusive".to_string() },
              None
            ).with_compiler_loc()
          ),        
        }
      }
    }
  }
}

register_descriptor! {
  FunctionCompilerDescriptor {
    name: "range/exclusive",
    ptr: &RangeExclusive{},
  }
}