// ---------------------------------------------------------------------------
// - Rvector.cpp                                                             -
// - afnix:mth module - real 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

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

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // this procedure returns a new rvector for deserialization
  static Serial* mksob (void) {
    return new Rvector;
  }
  // register the anonymous vector serial id
  static const t_byte SERIAL_ID = Serial::setsid (SERIAL_ARVI_ID, mksob);

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

  // generate a random vector by size

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

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

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

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

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

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

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

  Rvector::Rvector (void) {
    p_vtab = nilp;
  }

  // create a vector by size

  Rvector::Rvector (const t_long size) : Rvi (size) {
    p_vtab = (d_size == 0) ? nilp : new t_real [d_size];
    clear ();
  }

  // copy construct this vector

  Rvector::Rvector (const Rvector& that) {
    that.rdlock ();
    try {
      d_size = that.d_size;
      p_vtab = (d_size == 0) ? nilp : new t_real[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

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

  // return the class name

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

  // return a clone of this object

  Object* Rvector::clone (void) const {
    return new Rvector (*this);
  }

  // assign a vector to this one

  Rvector& Rvector::operator = (const Rvector& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // delete the old vector
      if (d_size != that.d_size) {
	delete [] p_vtab;
	d_size = that.d_size;
	p_vtab = (d_size == 0) ? nilp : new t_real[d_size];
      }
      // assign the new one
      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 Rvector::operator == (const Rvi& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size first
      if (d_size != x.getsize ()) {
        throw Exception ("vector-error",
                         "incompatible vector size with operator ==");
      }
      // initialize result
      bool result = true;
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) {
	  t_real ti = p_vtab[i];
	  t_real xi = xp->p_vtab[i];
	  if (ti != xi) {
	    result = false;
	    break;
	  }
	}
      } else {
	result = Rvi::operator == (x);
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return result;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // compute the vector dot product

  t_real Rvector::operator ^ (const Rvi& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size compatibility
      if (d_size != x.getsize ()) {
	throw Exception ("vector-error", 
			 "incompatible vector size with dot product");
      }
      // initialize result
      t_real sum = 0.0;
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	// dot product loop with kahan algorithm
	volatile t_real c = 0.0;
	for (t_long i = 0; i < d_size; i++) {
	  volatile t_real y = p_vtab[i] * xp->p_vtab[i] - c;
	  volatile t_real t = sum + y;
	  c = (t - sum) - y;
	  sum = t;
	}
      } else {
	sum = Rvi::operator ^ (x);
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return sum;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // resize this vector

  void Rvector::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 = nilp;
	unlock ();
	return;
      }
      // do nothing if equal
      if (size == d_size) {
	unlock ();
	return;
      }
      // create a new array by size
      t_real* vtab = new t_real[size];
      // check for smaller size
      if (size < d_size) {
	for (long k = 0; k < size; k++) vtab[k] = p_vtab[k];
      } else {
	for (long k = 0;      k < d_size; k++) vtab[k] = p_vtab[k];
	for (long k = d_size; k < size;   k++) vtab[k] = 0.0;
      }
      d_size = size;
      delete [] p_vtab;
      p_vtab = vtab;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // clear this vector

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

  // set a vector by position

  void Rvector::set (const t_long pos, const t_real val) {
    wrlock ();
    try {
      if ((pos < 0) || (pos >= d_size)) {
	throw Exception ("index-error", "invalid vector position");
      }
      p_vtab[pos] = val;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a vector by position

  t_real Rvector::get (const t_long pos) const {
    rdlock ();
    try {
      if ((pos < 0) || (pos >= d_size)) {
	throw Exception ("index-error", "invalid vector position");
      }
      t_real result = p_vtab[pos];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // compare two vectors by precision

  bool Rvector::cmp (const Rvi& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size first
      if (d_size != x.getsize ()) {
        throw Exception ("vector-error",
                         "incompatible vector size with compare");
      }
      // initialize result
      bool result = true;
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) {
	  t_real ti = p_vtab[i];
	  t_real xi = xp->p_vtab[i];
	  if (Math::acmp (ti, xi) == false) {
	    result = false;
	    break;
	  }
	}
      } else {
	result = Rvi::cmp (x);
      }
      // unlock and return
      unlock ();
      x.unlock ();
      return result;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // compute the vector norm
  
  t_real Rvector::norm (void) const {
    rdlock ();
    try {
      // compute vector norm with kahan algorithm
      t_real        sum = 0.0;
      volatile t_real c = 0.0;
      for (t_long i = 0; i < d_size; i++) {
        volatile t_real xi = p_vtab[i];
        volatile t_real  y = (xi * xi) - c;
        volatile t_real  t = sum + y;
        c = (t - sum) - y;
        sum = t;
      }
      t_real result = Math::sqrt (sum);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // copy a vector into this one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = xp->p_vtab[i];
      } else {
	Rvi::cpy (x);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with a scalar

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = xp->p_vtab[i] + s;
      } else {
	Rvi::add (x, s);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with another one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      const Rvector* yp = dynamic_cast <const Rvector*> (&y);
      if ((xp != nilp) && (yp != nilp)) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] = xp->p_vtab[i] + yp->p_vtab[i];
	}
      } else {
	Rvi::add (x, y);
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // add a vector with another scaled one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      const Rvector* yp = dynamic_cast <const Rvector*> (&y);
      if ((xp != nilp) && (yp != nilp)) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] = xp->p_vtab[i] + (yp->p_vtab[i] * s);
	}
      } else {
	Rvi::add (x, y, s);
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // substract a vector with a scalar

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = xp->p_vtab[i] - s;
      } else {
	Rvi::sub (x, s);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // substract a vector with another one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      const Rvector* yp = dynamic_cast <const Rvector*> (&y);
      if ((xp != nilp) && (yp != nilp)) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] = xp->p_vtab[i] - yp->p_vtab[i];
	}
      } else {
	Rvi::sub (x, y);
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // multiply a vector with a scalar

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) p_vtab[i] = (xp->p_vtab[i] * s);
      } else {
	Rvi::mul (x, s);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // multiply a vector with another one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      const Rvector* yp = dynamic_cast <const Rvector*> (&y);
      if ((xp != nilp) && (yp != nilp)) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] = xp->p_vtab[i] * yp->p_vtab[i];
	}
      } else {
	Rvi::mul (x, y);
      }
      unlock ();
      x.unlock ();
      y.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      y.unlock ();
      throw;
    }
  }

  // add a vector with another one

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] += xp->p_vtab[i];
	}
      } else {
	Rvi::aeq (x);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // add a vector with a scaled vector

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] += (s * xp->p_vtab[i]);
	}
      } else {
	Rvi::aeq (x, s);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

  // rescale equal with a vector

  Rvi& Rvector::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");
      }
      // try to map the argument vector to a real vector
      const Rvector* xp = dynamic_cast <const Rvector*> (&x);
      if (xp != nilp) {
	for (t_long i = 0; i < d_size; i++) {
	  p_vtab[i] = (s * p_vtab[i]) + xp->p_vtab[i];
	}
      } else {
	Rvi::req (x, s);
      }
      unlock ();
      x.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      x.unlock ();
      throw;
    }
  }

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

  // create a new object in a generic way

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

  // operate this vector with another object

  Object* Rvector::oper (t_oper type, Object* object) {
    Real*    dobj = dynamic_cast <Real*>    (object);
    Rvector* vobj = dynamic_cast <Rvector*> (object);
    switch (type) {
    case Object::ADD:
      if (vobj != nilp) return new Rvector (*this + *vobj);
      if (dobj != nilp) return new Rvector (*this + dobj->toreal ());
      break;
    case Object::SUB:
      if (vobj != nilp) return new Rvector (*this - *vobj);
      if (dobj != nilp) return new Rvector (*this - dobj->toreal ());
      break;
    case Object::MUL:
      if (dobj != nilp) return new Rvector (*this * dobj->toreal ());
      break;
    case Object::DIV:
      if (dobj != nilp) return new Rvector (*this / dobj->toreal ());
      break;
    default:
      throw Exception ("vector-error", "invalid operator with r-vector",
		       Object::repr (object));
      break;
    }
    throw Exception ("type-error", "invalid operand with r-vector",
		     Object::repr (object));
  }
}

