// ---------------------------------------------------------------------------
// - Cvector.cpp                                                             -
// - afnix:mth module - complex vector implementation                        -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Real.hpp"
#include "Math.hpp"
#include "Mthsid.hxx"
#include "Vector.hpp"
#include "Cvector.hpp"
#include "Algebra.hpp"
#include "Utility.hpp"
#include "Exception.hpp"
 
namespace afnix {

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // add a vector with a scalar

  Cvector operator + (const Cvector& x, const Complex& s) {
    x.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.add (x, s);
      // unlock and return
      x.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      throw;
    }
  }

  // add a vector with another one

  Cvector operator + (const Cvector& x, const Cvector& y) {
    x.rdlock ();
    y.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.add (x, y);
      // unlock and return
      x.unlock ();
      y.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // substract a vector with a scalar

  Cvector operator - (const Cvector& x, const Complex& s) {
    x.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.sub (x, s);
      // unlock and return
      x.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      throw;
    }
  }

  // substract a vector with another one

  Cvector operator - (const Cvector& x, const Cvector& y) {
    x.rdlock ();
    y.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.sub (x, y);
      // unlock and return
      x.unlock ();
      y.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // multiply a vector with a scalar

  Cvector operator * (const Cvector& x, const Complex& s) {
    x.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.mul (x, s);
      // unlock and return
      x.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      throw;
    }
  }

  // divide a vector with a scalar

  Cvector operator / (const Cvector& x, const Complex& s) {
    x.rdlock ();
    try {
      // create a result vector
      Cvector r (x.getsize ());
      // add the scalar
      r.mul (x, Complex(1.0, 0.0) / s);
      // unlock and return
      x.unlock ();
      return r;
    } catch (...) {
      x.unlock ();
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a default vector

  Cvector::Cvector (void) {
    p_vtab = nullptr;
  }

  // create a vector by size

  Cvector::Cvector (const t_long size) : Cvi (size) {
    p_vtab = nullptr;
    preset ();
  }
  
  // copy construct this vector

  Cvector::Cvector (const Cvector& that) {
    that.rdlock ();
    try {
      // copy base class
      Cvi::operator = (that);
      // copy locally
      p_vtab = nullptr;
      if (d_size > 0) {
	long zsiz = 2 * d_size; p_vtab = new t_real[zsiz];
	for (t_long i = 0; i < zsiz; i++) p_vtab[i] = that.p_vtab[i];
      }
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
	
  // destroy this vector

  Cvector::~Cvector (void) {
    delete [] p_vtab;
  }

  // assign a vector to this one

  Cvector& Cvector::operator = (const Cvector& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // clean locally
      delete [] p_vtab; p_vtab = nullptr;
      // assign base class
      Cvi::operator = (that);
      // assign locally
      if (d_size > 0) {
	long zsiz = 2 * d_size; p_vtab = new t_real[zsiz];
	for (t_long i = 0; i < zsiz; i++) p_vtab[i] = that.p_vtab[i];
      }
      // unlock and return
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // compare two vectors

  bool Cvector::operator == (const Cvi& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size first
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", 
			 "incompatible size with operator ==");
      }
      // initialize result
      bool result = true;
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	Complex ti = nlget (i);
	Complex xi = x.nlget (i);
	if (ti != xi) {
	  result = false;
	  break;
	}
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return result;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // compute the vector dot product

  Complex Cvector::operator ^ (const Cvi& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size compatibility
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", 
			 "incompatible size with dot product");
      }
      // initialize result
      Complex result = 0.0;
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	result += nlget (i) * x.nlget(i).tocjv();
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return result;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // return the class name

  String Cvector::repr (void) const {
    return "Cvector";
  }

  // return a clone of this object

  Object* Cvector::clone (void) const {
    return new Cvector (*this);
  }
  
  // return the serial did

  t_word Cvector::getdid (void) const {
    return SRL_DEOD_MTH;
  }

  // return the serial sid

  t_word Cvector::getsid (void) const {
    return SRL_CVEC_SID;
  }
  
  // resize this vector

  void Cvector::resize (const t_long size) {
    wrlock ();
    try {
      if (size < 0) {
	throw Exception ("cvector-error", "invalid negatize size in resize");
      }
      if (size == 0)  {
	delete [] p_vtab;
	d_size = 0;
	p_vtab = nullptr;
	unlock ();
	return;
      }
      // do nothing if equal
      if (size == d_size) {
	unlock ();
	return;
      }
      // create a new array by size
      long zsiz = 2 * size; t_real* vtab = new t_real[zsiz];
      // check for smaller size
      if (size < d_size) {
	for (long k = 0; k < zsiz; k++) {
	  vtab[k] = (p_vtab == nullptr) ? 0.0 : p_vtab[k];
	}
      } else {
	for (long k = 0; k < zsiz; k++) {
	  vtab[k] = (p_vtab == nullptr) ? 0.0 : p_vtab[k];
	}
	for (long k = 2*d_size; k < zsiz; k++) vtab[k] = 0.0;
      }
      d_size = size;
      delete [] p_vtab;
      p_vtab = vtab;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // reset this vector

  void Cvector::reset (void) {
    wrlock ();
    try {
      delete [] p_vtab;
      d_size = 0;
      p_vtab = nullptr;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // clear this vector

  void Cvector::clear (void) {
    wrlock ();
    try {
      for (t_long k = 0; k < d_size; k++) {
	p_vtab[k] = 0.0;
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // preset this vector

  void Cvector::preset (void) {
    wrlock ();
    try {
      delete [] p_vtab;
      p_vtab = (d_size == 0) ? nullptr : new t_real[2*d_size];
      clear ();
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // compute the vector norm
  
  t_real Cvector::norm (void) const {
    rdlock ();
    try {
      t_real result = Algebra::norm (*this);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // copy a vector into this one

  Cvi& Cvector::cpy (const Cvi& x) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector copy");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) nlset (i, x.nlget (i));
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with a scalar

  Cvi& Cvector::add (const Cvi& x, const Complex& s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector add");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) nlset (i, x.nlget (i) + s);
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with another one

  Cvi& Cvector::add (const Cvi& x, const Cvi& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("vector-error", "incompatible size in vector add");
      }
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, x.nlget (i) + y.nlget (i));
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // add a vector with another scaled one

  Cvi& Cvector::add (const Cvi& x, const Cvi& y, const Complex& s) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("vector-error", "incompatible size in vector add");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i,  x.nlget (i) + (s * y.nlget (i)));
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // substract a vector with a scalar

  Cvi& Cvector::sub (const Cvi& x, const Complex& s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector sub");
      }
      for (t_long i = 0; i < d_size; i++) nlset (i, x.nlget (i) - s);
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // substract a vector with another one

  Cvi& Cvector::sub (const Cvi& x, const Cvi& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("vector-error", "incompatible size in vector sub");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, x.nlget (i) - y.nlget (i));
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // multiply a vector with a scalar

  Cvi& Cvector::mul (const Cvi& x, const Complex& s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector mul");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) nlset (i, s * x.nlget (i));
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // multiply a vector with another one

  Cvi& Cvector::mul (const Cvi& x, const Cvi& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("vector-error", "incompatible size in vector mul");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i , x.nlget (i) * y.nlget (i));
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // divide a vector with another one

  Cvi& Cvector::div (const Cvi& x, const Cvi& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("vector-error", "incompatible size in vector div");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, x.nlget (i) / y.nlget (i));
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // add a vector with another one

  Cvi& Cvector::aeq (const Cvi& x) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector aeq");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, nlget (i) + x.nlget (i));
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with a scaled vector

  Cvi& Cvector::aeq (const Cvi& x, const Complex& s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector aeq");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, nlget (i) + (s * x.nlget (i)));
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // rescale equal with a vector

  Cvi& Cvector::req (const Cvi& x, const Complex& s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", "incompatible size in vector req");
      }
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	nlset (i, (s * nlget (i)) + x.nlget (i));
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // permutate this vector

  Cvi* Cvector::permutate (const Cpi& p) const {
    rdlock ();
    Cvi* result = nullptr;
    try {
      // create a result vector
      result = new Cvector (d_size);
      // permutate this vector
      Algebra::permutate (*result, *this, p);
      unlock ();
      return result;
    } catch (...) {
      delete result;
      unlock ();
      throw;
    }
  }

  // reverse permutate this vector

  Cvi* Cvector::reverse (const Cpi& p) const {
    rdlock ();
    Cvi* result = nullptr;
    try {
      // create a result vector
      result = new Cvector (d_size);
      // permutate this vector
      Algebra::reverse (*result, *this, p);
      unlock ();
      return result;
    } catch (...) {
      delete result;
      unlock ();
      throw;
    }
  }

  // get the viewable size

  long Cvector::tosize (void) const {
    rdlock ();
    try {
      long result = 2 * d_size * sizeof(t_real);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the viewable data

  t_byte* Cvector::tobyte (void) {
    wrlock ();
    try {
      auto result = reinterpret_cast<t_byte*>(p_vtab);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // get the viewable data

  const t_byte* Cvector::tobyte (void) const {
    rdlock ();
    try {
      auto result = reinterpret_cast<const t_byte*>(p_vtab);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - no lock section                                                        -
  // -------------------------------------------------------------------------

  // no lock - set a vector by position

  void Cvector::nlset (const t_long pos, const Complex& val) {
    t_long idx = 2 * pos;
    auto  zval = reinterpret_cast<const t_real*>(val.tobyte());
    if (p_vtab != nullptr) {
      p_vtab[idx]   = zval[0];
      p_vtab[idx+1] = zval[1];
    }
  }

  // no lock - get a vector by position
  
  Complex Cvector::nlget (const t_long pos) const {
    if (p_vtab == nullptr) return 0.0;
    Complex result (p_vtab[2*pos], p_vtab[2*pos+1]);
    return result;
  }

  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // create a new object in a generic way

  Object* Cvector::mknew (Vector* argv) {
    long argc = (argv == nullptr) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) return new Cvector;
    // check for 1 argument
    if (argc == 1) {
      t_long size = argv->getlong (0);
      return new Cvector (size);
    }
    // invalid arguments
    throw Exception ("argument-error", 
		     "invalid arguments with real vector object");
  }

  // operate this vector with another object

  Object* Cvector::oper (t_oper type, Object* object) {
    Real*    robj = dynamic_cast <Real*>    (object);
    Complex* cobj = dynamic_cast <Complex*> (object);
    Cvector* vobj = dynamic_cast <Cvector*> (object);
    switch (type) {
    case Object::OPER_ADD:
      if (vobj != nullptr) return new Cvector (*this + *vobj);
      if (cobj != nullptr) return new Cvector (*this + *cobj);
      if (robj != nullptr) return new Cvector (*this +
					       Complex (robj->toreal()));
      break;
    case Object::OPER_SUB:
      if (vobj != nullptr) return new Cvector (*this - *vobj);
      if (cobj != nullptr) return new Cvector (*this - *cobj);
      if (robj != nullptr) return new Cvector (*this -
					       Complex (robj->toreal()));
      break;
    case Object::OPER_MUL:
      if (cobj != nullptr) return new Cvector (*this * *cobj);
      if (robj != nullptr) return new Cvector (*this *
					       Complex (robj->toreal()));
      break;
    case Object::OPER_DIV:
      if (cobj != nullptr) return new Cvector (*this / *cobj);
      if (robj != nullptr) return new Cvector (*this /
					       Complex (robj->toreal()));
      break;
    default:
      break;
    }
    // call the cvi operator
    return Cvi::oper (type, object);
  }
}

