// ---------------------------------------------------------------------------
// - Fvector.cpp                                                             -
// - afnix:mth module - float 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-2019 amaury darsch                                   -
// ---------------------------------------------------------------------------

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

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

  // generate a random vector by size

  Fvector Fvector::random (const t_long size, 
			   const t_real rmin, const t_real rmax) {
    // create a vector by size
    Fvector result (size);
    // fill the vector
    Algebra::random (result, rmin, rmax);
    // done
    return  result;
  }
    
  // add a vector with a scalar

  Fvector operator + (const Fvector& x, const t_real s) {
    x.rdlock ();
    try {
      // create a result vector
      Fvector 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

  Fvector operator + (const Fvector& x, const Fvector& y) {
    x.rdlock ();
    y.rdlock ();
    try {
      // create a result vector
      Fvector 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

  Fvector operator - (const Fvector& x, const t_real s) {
    x.rdlock ();
    try {
      // create a result vector
      Fvector 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

  Fvector operator - (const Fvector& x, const Fvector& y) {
    x.rdlock ();
    y.rdlock ();
    try {
      // create a result vector
      Fvector 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

  Fvector operator * (const Fvector& x, const t_real s) {
    x.rdlock ();
    try {
      // create a result vector
      Fvector 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

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

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

  // create a default vector

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

  // create a vector by size

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

  Fvector::Fvector (const Fvector& that) {
    that.rdlock ();
    try {
      // copy base class
      Rvi::operator = (that);
      // copy locally
      p_vtab = nullptr;
      if (d_size > 0) {
	p_vtab = new float[d_size];
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = that.p_vtab[i];
      }
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
	
  // destroy this vector

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

  // assign a vector to this one

  Fvector& Fvector::operator = (const Fvector& 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
      Rvi::operator = (that);
      // assign locally
      if (d_size > 0) {
	p_vtab = new float[d_size];
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = that.p_vtab[i];
      }
      // unlock and return
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // compare two vectors

  bool Fvector::operator == (const Rvi& 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++) {
	t_real ti = nlget (i);
	t_real 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

  t_real Fvector::operator ^ (const Rvi& 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
      t_real result = 0.0;
      // loop in locked mode
      for (t_long i = 0; i < d_size; i++) {
	result += nlget (i) * x.nlget (i);
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return result;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // return the class name

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

  // return a clone of this object

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

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

  // return the serial sid

  t_word Fvector::getsid (void) const {
    return SRL_RVEC_SID;
  }
  
  // resize this vector

  void Fvector::resize (const t_long size) {
    wrlock ();
    try {
      if (size < 0) {
	throw Exception ("rvector-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
      float* vtab = new float[size];
      // check for smaller size
      if (size < d_size) {
	for (long k = 0; k < size; k++) {
	  vtab[k] = (p_vtab == nullptr) ? 0.0F : p_vtab[k];
	}
      } else {
	for (long k = 0; k < d_size; k++) {
	  vtab[k] = (p_vtab == nullptr) ? 0.0F : p_vtab[k];
	}
	for (long k = d_size; k < size; k++) vtab[k] = 0.0F;
      }
      d_size = size;
      delete [] p_vtab;
      p_vtab = vtab;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // reset this vector

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

  // clear this vector

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

  // preset this vector

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

  // compute the vector norm
  
  t_real Fvector::norm (void) const {
    rdlock ();
    try {
      // initialize sum
      t_real sum = 0.0;
      // loop with an array
      for (t_long i = 0; i < d_size; i++) {
	t_real xi = nlget (i);
	sum += xi * xi;
      }
      t_real result = Math::sqrt (sum);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // copy a vector into this one

  Rvi& Fvector::cpy (const Rvi& 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

  Rvi& Fvector::add (const Rvi& x, const t_real 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

  Rvi& Fvector::add (const Rvi& x, const Rvi& 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

  Rvi& Fvector::add (const Rvi& x, const Rvi& y, const t_real 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

  Rvi& Fvector::sub (const Rvi& x, const t_real 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

  Rvi& Fvector::sub (const Rvi& x, const Rvi& 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

  Rvi& Fvector::mul (const Rvi& x, const t_real 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

  Rvi& Fvector::mul (const Rvi& x, const Rvi& 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

  Rvi& Fvector::div (const Rvi& x, const Rvi& 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

  Rvi& Fvector::aeq (const Rvi& 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

  Rvi& Fvector::aeq (const Rvi& x, const t_real 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

  Rvi& Fvector::req (const Rvi& x, const t_real 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

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

  // reverse permutate this vector

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

  // get the viewable size

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

  // get the viewable data

  t_byte* Fvector::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* Fvector::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 Fvector::nlset (const t_long pos, const t_real val) {
    if (p_vtab != nullptr) p_vtab[pos] = val;
  }

  // no lock - get a vector by position
  
  t_real Fvector::nlget (const t_long pos) const {
    return (p_vtab == nullptr) ? 0.0F : p_vtab[pos];
  }

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

  // create a new object in a generic way

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

  // operate this vector with another object

  Object* Fvector::oper (t_oper type, Object* object) {
    Real*    dobj = dynamic_cast <Real*>    (object);
    Fvector* vobj = dynamic_cast <Fvector*> (object);
    switch (type) {
    case Object::ADD:
      if (vobj != nullptr) return new Fvector (*this + *vobj);
      if (dobj != nullptr) return new Fvector (*this + dobj->toreal ());
      break;
    case Object::SUB:
      if (vobj != nullptr) return new Fvector (*this - *vobj);
      if (dobj != nullptr) return new Fvector (*this - dobj->toreal ());
      break;
    case Object::MUL:
      if (dobj != nullptr) return new Fvector (*this * dobj->toreal ());
      break;
    case Object::DIV:
      if (dobj != nullptr) return new Fvector (*this / dobj->toreal ());
      break;
    default:
      break;
    }
    // call the rvi operator
    return Rvi::oper (type, object);
  }
}

