// ---------------------------------------------------------------------------
// - Rtrace.cpp                                                              -
// - afnix:mth module - real trace 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 "Rtrace.hpp"
#include "Algebra.hpp"
#include "Utility.hpp"
#include "Exception.hpp"
 
namespace afnix {

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

  // add a trace with a scalar

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

  // add a trace with another one

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

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

  // substract a trace with another one

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

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

  // divide a trace with a scalar

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

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

  // create a trace by size

  Rtrace::Rtrace (const t_long size) : Rti (size) {
    p_vtab = nullptr;
    preset ();
  }
  
  // copy construct this trace

  Rtrace::Rtrace (const Rtrace& that) {
    that.rdlock ();
    try {
      // copy base class
      Rti::operator = (that);
      // copy locally
      p_vtab = nullptr;
      if (d_size > 0) {
	p_vtab = 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 trace

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

  // assign a trace to this one

  Rtrace& Rtrace::operator = (const Rtrace& 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
      Rti::operator = (that);
      // assign locally
      if (d_size > 0) {
	p_vtab = new t_real[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 (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // compare two traces

  bool Rtrace::operator == (const Rti& x) const {
    rdlock ();
    x.rdlock ();
    try {
      // check size first
      if (d_size != x.getsize ()) {
	throw Exception ("trace-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;
    }
  }

  // return the class name

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

  // return a clone of this object

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

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

  // return the serial sid

  t_word Rtrace::getsid (void) const {
    return SRL_RTRC_SID;
  }
  
  // resize this trace

  void Rtrace::resize (const t_long size) {
    wrlock ();
    try {
      if (size < 0) {
	throw Exception ("rtrace-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
      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 == nullptr) ? 0.0 : p_vtab[k];
	}
      } else {
	for (long k = 0; k < d_size; k++) {
	  vtab[k] = (p_vtab == nullptr) ? 0.0 : 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;
    }
  }

  // reset this trace

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

  // clear this trace

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

  // preset this trace

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

  // copy a trace into this one

  Rti& Rtrace::cpy (const Rti& x) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with a scalar

  Rti& Rtrace::add (const Rti& x, const t_real s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another one

  Rti& Rtrace::add (const Rti& x, const Rti& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another scaled one

  Rti& Rtrace::add (const Rti& x, const Rti& 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 ("trace-error", "incompatible size in trace 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 trace with a scalar

  Rti& Rtrace::sub (const Rti& x, const t_real s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another one

  Rti& Rtrace::sub (const Rti& x, const Rti& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with a scalar

  Rti& Rtrace::mul (const Rti& x, const t_real s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another one

  Rti& Rtrace::mul (const Rti& x, const Rti& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another one

  Rti& Rtrace::div (const Rti& x, const Rti& y) {
    wrlock ();
    x.rdlock ();
    y.rdlock ();
    try {
      // check target size
      if ((d_size != x.getsize ()) || (d_size != y.getsize ())) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with another one

  Rti& Rtrace::aeq (const Rti& x) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace with a scaled trace

  Rti& Rtrace::aeq (const Rti& x, const t_real s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace

  Rti& Rtrace::req (const Rti& x, const t_real s) {
    wrlock ();
    x.rdlock ();
    try {
      // check target size
      if (d_size != x.getsize ()) {
	throw Exception ("trace-error", "incompatible size in trace 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 trace

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

  // reverse permutate this trace

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

  // get the viewable size

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

  // get the viewable data

  t_byte* Rtrace::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* Rtrace::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 trace by position

  void Rtrace::nlset (const t_long pos, const t_real val) {
    if (p_vtab != nullptr) p_vtab[pos] = val;
  }

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

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

  // create a new object in a generic way

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

  // operate this trace with another object

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

