#[macro_use]
use crate::stdlib::*;
use self::assign::*;
use na::{Vector3, DVector, Vector2, Vector4, RowDVector, Matrix1, Matrix3, Matrix4, RowVector3, RowVector4, RowVector2, DMatrix, Rotation3, Matrix2x3, Matrix3x2, Matrix6, Matrix2};

// x.a = 1 --------------------------------------------------------------------

// Table Set ------------------------------------------------------------------

macro_rules! impl_col_set_fxn {
  ($fxn_name:ident, $vector_size_in:ident, $vector_size_out:ident, $out_type:ty) => {
    #[derive(Debug)]
    struct $fxn_name {
      source: Ref<$vector_size_in<$out_type>>,
      sink: Ref<$vector_size_out<Value>>,
    }
    impl MechFunctionImpl for $fxn_name {
      fn solve(&self) {
        let source_ptr = self.source.as_ptr();
        let sink_ptr = self.sink.as_mut_ptr();
        unsafe { 
          for i in 0..(*source_ptr).len() {
            paste! {
              (&mut (*sink_ptr))[i] = Value::[<$out_type:camel>](Ref::new((*source_ptr).index(i).clone()));
            }
          }
        }
      }
      fn out(&self) -> Value { Value::MatrixValue(Matrix::$vector_size_out(self.sink.clone())) }
      fn to_string(&self) -> String { format!("{:#?}", self) }
    }
    #[cfg(feature = "compiler")]
    impl MechFunctionCompiler for $fxn_name 
    where
      $vector_size_in<$out_type>: CompileConst + ConstElem + AsValueKind,
      $vector_size_out<Value>: CompileConst + ConstElem + AsValueKind,
      $out_type: CompileConst + ConstElem + AsValueKind,
    {
      fn compile(&self, ctx: &mut CompileCtx) -> MResult<Register> {
        let name = format!("{}<{},{}>", stringify!($fxn_name), $vector_size_in::<$out_type>::as_value_kind(), $vector_size_out::<Value>::as_value_kind());
        compile_unop!(name, self.sink, self.source, ctx, FeatureFlag::Builtin(FeatureKind::Assign) );
      }
    }
  }
}

macro_rules! impl_col_set_fxn_shapes {
  ($type:ident) => {
    paste!{
      #[cfg(feature = "matrix1")]
      impl_col_set_fxn!([<TableSetCol $type:camel M1>], Matrix1, Matrix1, $type);
      #[cfg(feature = "vector2")]
      impl_col_set_fxn!([<TableSetCol $type:camel V2>], Vector2, Vector2, $type);
      #[cfg(feature = "vector3")]
      impl_col_set_fxn!([<TableSetCol $type:camel V3>], Vector3, Vector3, $type);
      #[cfg(feature = "vector4")]
      impl_col_set_fxn!([<TableSetCol $type:camel V4>], Vector4, Vector4, $type);
      #[cfg(feature = "vectord")]
      impl_col_set_fxn!([<TableSetCol $type:camel VD>], DVector, DVector, $type);
      #[cfg(all(feature = "vectord", feature = "vector4"))]
      impl_col_set_fxn!([<TableSetCol $type:camel VDV4>], Vector4, DVector, $type);
      #[cfg(all(feature = "vectord", feature = "vector3"))]
      impl_col_set_fxn!([<TableSetCol $type:camel VDV3>], Vector3, DVector, $type);
      #[cfg(all(feature = "vectord", feature = "vector2"))]
      impl_col_set_fxn!([<TableSetCol $type:camel VDV2>], Vector2, DVector, $type);
      #[cfg(all(feature = "vectord", feature = "matrix1"))]
      impl_col_set_fxn!([<TableSetCol $type:camel VDM1>], Matrix1, DVector, $type);
    }
  }
}

#[cfg(feature = "bool")]
impl_col_set_fxn_shapes!(bool);
#[cfg(feature = "i8")]
impl_col_set_fxn_shapes!(i8);
#[cfg(feature = "i16")]
impl_col_set_fxn_shapes!(i16);
#[cfg(feature = "i32")]
impl_col_set_fxn_shapes!(i32);
#[cfg(feature = "i64")]
impl_col_set_fxn_shapes!(i64);
#[cfg(feature = "i128")]
impl_col_set_fxn_shapes!(i128);
#[cfg(feature = "u8")]
impl_col_set_fxn_shapes!(u8);
#[cfg(feature = "u16")]
impl_col_set_fxn_shapes!(u16);
#[cfg(feature = "u32")]
impl_col_set_fxn_shapes!(u32);
#[cfg(feature = "u64")]
impl_col_set_fxn_shapes!(u64);
#[cfg(feature = "u128")]
impl_col_set_fxn_shapes!(u128);
#[cfg(feature = "f32")]
impl_col_set_fxn_shapes!(f32);
#[cfg(feature = "f64")]
impl_col_set_fxn_shapes!(f64);
#[cfg(feature = "string")]
impl_col_set_fxn_shapes!(String);
#[cfg(feature = "complex")]
impl_col_set_fxn_shapes!(C64);
#[cfg(feature = "rational")]
impl_col_set_fxn_shapes!(R64);

macro_rules! impl_set_column_match_arms {
  ($arg:expr, $($lhs_type:ident, $type_ident:ident, $type_feature:literal);+ $(;)?) => {
    paste::paste! {
      match $arg {
        (Value::Table(tbl), source, Value::Id(k)) => {
          let tbl_brrw = tbl.borrow();
          match (tbl_brrw.get(&k), tbl_brrw.rows(), source) {
            $(
              #[cfg(all(feature = $type_feature, feature = "matrix1"))]
              (Some((ValueKind::$lhs_type, Matrix::Matrix1(sink))), 1, Value::[<Matrix $lhs_type>](Matrix::Matrix1(source))) =>Ok(Box::new([<TableSetCol $lhs_type M1>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vector2"))]
              (Some((ValueKind::$lhs_type, Matrix::Vector2(sink))), 2, Value::[<Matrix $lhs_type>](Matrix::Vector2(source))) =>Ok(Box::new([<TableSetCol $lhs_type V2>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vector3"))]
              (Some((ValueKind::$lhs_type, Matrix::Vector3(sink))), 3, Value::[<Matrix $lhs_type>](Matrix::Vector3(source))) =>Ok(Box::new([<TableSetCol $lhs_type V3>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vector4"))]
              (Some((ValueKind::$lhs_type, Matrix::Vector4(sink))), 4, Value::[<Matrix $lhs_type>](Matrix::Vector4(source))) =>Ok(Box::new([<TableSetCol $lhs_type V4>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vectord"))]
              (Some((ValueKind::$lhs_type, Matrix::DVector(sink))), n, Value::[<Matrix $lhs_type>](Matrix::DVector(source))) =>Ok(Box::new([<TableSetCol $lhs_type VD>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vectord", feature = "vector4"))]
              (Some((ValueKind::$lhs_type, Matrix::DVector(sink))), n, Value::[<Matrix $lhs_type>](Matrix::Vector4(source))) =>Ok(Box::new([<TableSetCol $lhs_type VDV4>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vectord", feature = "vector3"))]
              (Some((ValueKind::$lhs_type, Matrix::DVector(sink))), n, Value::[<Matrix $lhs_type>](Matrix::Vector3(source))) =>Ok(Box::new([<TableSetCol $lhs_type VDV3>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vectord", feature = "vector2"))]
              (Some((ValueKind::$lhs_type, Matrix::DVector(sink))), n, Value::[<Matrix $lhs_type>](Matrix::Vector2(source))) =>Ok(Box::new([<TableSetCol $lhs_type VDV2>]{ source: source.clone(), sink: sink.clone() })),
              #[cfg(all(feature = $type_feature, feature = "vectord", feature = "matrix1"))]
              (Some((ValueKind::$lhs_type, Matrix::DVector(sink))), n, Value::[<Matrix $lhs_type>](Matrix::Matrix1(source))) =>Ok(Box::new([<TableSetCol $lhs_type VDM1>]{ source: source.clone(), sink: sink.clone() })),
            )+
            x => return Err(MechError2::new(
              UndefinedTableColumnError { id: k.clone() }, None).with_compiler_loc()
            )
          }
        }
        (sink, source, key) => Err(MechError2::new(
            UnhandledFunctionArgumentKind3 { arg: (sink.kind(), source.kind(), key.kind()), fxn_name: stringify!($fxn_name).to_string() },
            None
          ).with_compiler_loc()
        ),
      }
    }
  }
}

fn impl_set_column_fxn(sink: Value, source: Value, key: Value) -> MResult<Box<dyn MechFunction>> {
  impl_set_column_match_arms!(
    (sink,source,key),
    Bool, bool, "bool";
    I8,   i8,   "i8";
    I16,  i16,  "i16";
    I32,  i32,  "i32";
    I64,  i64,  "i64";
    I128, i128, "i128";
    U8,   u8,   "u8";
    U16,  u16,  "u16";
    U32,  u32,  "u32";
    U64,  u64,  "u64";
    U128, u128, "u128";
    F32,  f32,  "f32";
    F64,  f64,  "f64";
    String, String, "string";
    C64, C64,"complex";
    R64, R64,"rational";
  )
}

pub struct AssignTableColumn {}
impl NativeFunctionCompiler for AssignTableColumn {
  fn compile(&self, arguments: &Vec<Value>) -> MResult<Box<dyn MechFunction>> {
    if arguments.len() < 3 {
      return Err(MechError2::new(IncorrectNumberOfArguments { expected: 1, found: arguments.len() }, None).with_compiler_loc());
    }
    let sink = arguments[0].clone();
    let source = arguments[1].clone();
    let key = arguments[2].clone();
    match impl_set_column_fxn(sink.clone(), source.clone(), key.clone()) {
      Ok(fxn) => Ok(fxn),
      Err(_) => {
        match (&sink,&source,&key) {
          (Value::MutableReference(sink),_,_) => { impl_set_column_fxn(sink.borrow().clone(), source.clone(), key.clone()) }
          (sink, source, key) => Err(MechError2::new(
              UnhandledFunctionArgumentKind3 { arg: (sink.kind(), source.kind(), key.kind()), fxn_name: "table/assign-column".to_string() },
              None
            ).with_compiler_loc()
          ),
        }
      }
    }
  }
}

// table1 += table2 ------------------------------------------------------------

#[derive(Debug)]
struct TableAppendRecord {
  sink: Ref<MechTable>,
  source: Ref<MechRecord>,
}
impl MechFunctionImpl for TableAppendRecord {
  fn solve(&self) {
    unsafe {
      let mut sink_ptr = (&mut *(self.sink.as_mut_ptr()));
      let source_ptr = &(*(self.source.as_ptr()));
      sink_ptr.append_record(source_ptr.clone());
    }
  }
  fn out(&self) -> Value { Value::Table(self.sink.clone()) }
  fn to_string(&self) -> String { format!("{:#?}", self) }
}
#[cfg(feature = "compiler")]
impl MechFunctionCompiler for TableAppendRecord {
  fn compile(&self, ctx: &mut CompileCtx) -> MResult<Register> {
    let name = format!("TableAppendRecord");
    compile_unop!(name, self.sink, self.source, ctx, FeatureFlag::Builtin(FeatureKind::Assign) );
  }
}

#[derive(Debug)]
struct TableAppendTable {
  sink: Ref<MechTable>,
  source: Ref<MechTable>,
}
impl MechFunctionImpl for TableAppendTable {
  fn solve(&self) {
    unsafe {
      let mut sink_ptr = (&mut *(self.sink.as_mut_ptr()));
      let source_ptr = &(*(self.source.as_ptr()));
      sink_ptr.append_table(&source_ptr);
    }
  }
  fn out(&self) -> Value { Value::Table(self.sink.clone()) }
  fn to_string(&self) -> String { format!("{:#?}", self) }
}
#[cfg(feature = "compiler")]
impl MechFunctionCompiler for TableAppendTable {
  fn compile(&self, ctx: &mut CompileCtx) -> MResult<Register> {
    let name = format!("TableAppendTable");
    compile_unop!(name, self.sink, self.source, ctx, FeatureFlag::Builtin(FeatureKind::Table) );
  }
}

pub fn add_assign_table_fxn(sink: Value, source: Value) -> MResult<Box<dyn MechFunction>> {
  match (sink.clone(),source.clone()) {
    (Value::Table(tbl), Value::Record(rcrd)) => {
      tbl.borrow().check_record_schema(&rcrd.borrow())?;
      return Ok(Box::new(TableAppendRecord{ sink: tbl, source: rcrd }))
    }
    (Value::Table(tbl_sink), Value::Table(tbl_src)) => {
      tbl_sink.borrow().check_table_schema(&tbl_src.borrow())?;
      return Ok(Box::new(TableAppendTable{ sink: tbl_sink, source: tbl_src }))
    }
    (sink,source) => return Err(MechError2::new(
        UnhandledFunctionArgumentKind2 { arg: (sink.kind(),source.kind()), fxn_name: "table/add-assign".to_string() },
        None
      ).with_compiler_loc()
    ),
  }
}

pub struct AddAssignTable {}
impl NativeFunctionCompiler for AddAssignTable {
  fn compile(&self, arguments: &Vec<Value>) -> MResult<Box<dyn MechFunction>> {
    if arguments.len() <= 1 {
      return Err(MechError2::new(IncorrectNumberOfArguments { expected: 1, found: arguments.len() }, None).with_compiler_loc());
    }
    let sink = arguments[0].clone();
    let source = arguments[1].clone();
    match add_assign_table_fxn(sink.clone(),source.clone()) {
      Ok(fxn) => Ok(fxn),
      Err(x) => {
        match (sink,source) {
          (Value::MutableReference(sink),Value::MutableReference(source)) => { add_assign_table_fxn(sink.borrow().clone(),source.borrow().clone()) },
          (sink,Value::MutableReference(source)) => { add_assign_table_fxn(sink.clone(),source.borrow().clone()) },
          (Value::MutableReference(sink),source) => { add_assign_table_fxn(sink.borrow().clone(),source.clone()) },
          (sink,source) => Err(MechError2::new(
              UnhandledFunctionArgumentKind2 { arg: (sink.kind(),source.kind()), fxn_name: "table/add-assign".to_string() },
              None
            ).with_compiler_loc()
          ),
        }
      }
    }
  }
}

#[derive(Debug, Clone)]
pub struct UndefinedTableColumnError {
  pub id: u64,
}
impl MechErrorKind2 for UndefinedTableColumnError {
  fn name(&self) -> &str { "UndefinedTableColumn" }
  fn message(&self) -> String {
      format!("Column {:?} is not defined in this table.", self.id)
  }
}