// ---------------------------------------------------------------------------
// - Rmatrix.cpp                                                             -
// - afnix:mth module - real matrix 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-2012 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Real.hpp"
#include "Math.hpp"
#include "Vector.hpp"
#include "Utility.hpp"
#include "Rmatrix.hpp"
#include "Algebra.hpp"
#include "Exception.hpp"
 
namespace afnix {

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

  // generate a square random matrix by size

  Rmatrix Rmatrix::random (const t_long size, const bool   ddom,
			   const t_real rmin, const t_real rmax) {
    // create a matrix by size
    Rmatrix result (size);
    // fill the matrix
    Algebra::random (result, ddom, rmin, rmax);
    // here it is
    return result;
  }

  // add a matrix with another one

  Rmatrix operator + (const Rmatrix& mx, const Rmatrix& my) {
    mx.rdlock ();
    my.rdlock ();
    try {
      // create a result matrix
      Rmatrix mr (mx.getrsiz (), my.getcsiz ());
      // add the matrices
      Algebra::add (mr, mx, my);
      // unlock and return
      mx.unlock ();
      my.unlock ();
      return mr;
    } catch (...) {
      mx.unlock ();
      my.unlock ();
      throw;
    }
  }

  // substract a matrix with another one

  Rmatrix operator - (const Rmatrix& mx, const Rmatrix& my) {
    mx.rdlock ();
    my.rdlock ();
    try {
      // create a result matrix
      Rmatrix mr (mx.getrsiz (), my.getcsiz ());
      // substract the matrices
      Algebra::sub (mr, mx, my);
      // unlock and return
      mx.unlock ();
      my.unlock ();
      return mr;
    } catch (...) {
      mx.unlock ();
      my.unlock ();
      throw;
    }
  }

  // multiply a matrix with a vector

  Rvector operator * (const Rmatrix& m, const Rvector& x) {
    m.rdlock ();
    x.rdlock ();
    try {
      // extract operating size
      t_long rows = m.getrsiz ();
      // create result vector
      Rvector r (rows);
      // perform the operation
      m.mul (r, x, 1.0);
      // unlock and return
      m.unlock ();
      x.unlock ();
      return r;
    } catch (...) {
      m.unlock ();
      x.unlock ();
      throw;
    }
  }

  // multiply two matrices

  Rmatrix operator * (const Rmatrix& mx, const Rmatrix& my) {
    mx.rdlock ();
    my.rdlock ();
    try {
      // create a result matrix
      Rmatrix mr (mx.getrsiz (), my.getcsiz ());
      // muliply the matrices
      Algebra::mul (mr, mx, my);
      // unlock and return
      mx.unlock ();
      my.unlock ();
      return mr;
    } catch (...) {
      mx.unlock ();
      my.unlock ();
      throw;
    }
  }

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

  // create a default matrix

  Rmatrix::Rmatrix (void) {
    p_rtab = nilp;
  }

  // create a square matrix by size

  Rmatrix::Rmatrix (const t_long size) : Rmi (size) {
    p_rtab = new t_real* [d_rsiz];
    for (t_long i = 0; i < d_rsiz; i++) p_rtab[i] = new t_real[d_csiz];
    clear ();
  }

  // create a matrix by size

  Rmatrix::Rmatrix (const t_long rsiz, const t_long csiz) : Rmi (rsiz, csiz) {
    p_rtab = new t_real* [d_rsiz];
    for (t_long i = 0; i < d_rsiz; i++) p_rtab[i] = new t_real[d_csiz];
    clear ();
  }

  // copy construct this matrix

  Rmatrix::Rmatrix (const Rmatrix& that) {
    that.rdlock ();
    try {
      // copy the size and allocate
      d_rsiz = that.d_rsiz;
      d_csiz = that.d_csiz;
      p_rtab = (d_rsiz == 0) ? nilp : new t_real*[d_rsiz];
      // copy the matrix
      for (t_long i = 0; i< d_rsiz; i++) {
	p_rtab[i] = (d_csiz == 0) ? nilp : new t_real[d_csiz];
	for (t_long j = 0; j < d_csiz; j++) p_rtab[i][j] = that.p_rtab[i][j];
      }
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }
	
  // destroy this matrix

  Rmatrix::~Rmatrix (void) {
    for (t_long i = 0; i < d_rsiz; i++) delete [] p_rtab[i];
    delete [] p_rtab;
  }

  // return the class name

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

  // return a clone of this object

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

  // assign a matrix to this one

  Rmatrix& Rmatrix::operator = (const Rmatrix& that) {
    // check for self-assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      // delete the old matrix
      for (t_long i = 0; i < d_rsiz; i++) delete [] p_rtab[i];
      delete [] p_rtab;
      // copy the size and allocate
      d_rsiz = that.d_rsiz;
      d_csiz = that.d_csiz;
      p_rtab = (d_rsiz == 0) ? nilp : new t_real*[d_rsiz];
      // copy the matrix
      for (t_long i = 0; i< d_rsiz; i++) {
	p_rtab[i] = (d_csiz == 0) ? nilp : new t_real[d_csiz];
	for (t_long j = 0; j < d_csiz; j++) p_rtab[i][j] = that.p_rtab[i][j];
      }
      // unlock and return
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // set a matrix by position

  void Rmatrix::set (const t_long row, const t_long col, const t_real val) {
    wrlock ();
    try {
      if ((row < 0) || (row > d_rsiz) ||
	  (col < 0) || (col > d_csiz)) {
	throw Exception ("index-error", "invalid matrix position");
      }
      p_rtab[row][col] = val;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get a matrix value by position

  t_real Rmatrix::get (const t_long row, const t_long col) const {
    rdlock ();
    try {
      if ((row < 0) || (row > d_rsiz) ||
	  (col < 0) || (col > d_csiz)) {
	throw Exception ("index-error", "invalid matrix position");
      }
      t_real result = (p_rtab == nilp) ? 0.0 : p_rtab[row][col];
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // compare two matrices by precision

  bool Rmatrix::cmp (const Rmi& mx) const {
    rdlock ();
    mx.rdlock ();
    try {
      // check size first
      if ((d_rsiz != mx.d_rsiz) || (d_csiz != mx.d_csiz)) {
	throw Exception ("matrix-error",
			 "incompatible matrix size with compare");
      }
      // initialize result
      bool result = true;
      // try to map the argument matrix to a real matrix
      const Rmatrix* mp = dynamic_cast <const Rmatrix*> (&mx);
      if (mp != nilp) {
	for (t_long i = 0; i < d_rsiz; i++) {
	  for (t_long j = 0; j < d_csiz; j++) {
	    t_real tij = p_rtab[i][j];
	    t_real mij = mp->p_rtab[i][j];
	    if (Math::acmp (tij, mij) == false) {
	      result = false;
	      break;
	    }
	  }
	}
      } else {
	result = Rmi::cmp (mx);
      }
      // unlock and return
      unlock ();
      mx.unlock ();
      return result;
    } catch (...) {
      unlock ();
      mx.unlock ();
      throw;
    }
  }

  // compute the matrix frobenius norm
  
  t_real Rmatrix::norm (void) const {
    rdlock ();
    try {
      t_real sum = 0.0;
      for (t_long i = 0; i < d_rsiz; i++) {
	for (t_long j = 0; j < d_csiz; j++) {
	  t_real mij = p_rtab[i][j];
	  sum += (mij * mij);
	}
      }
      t_real result = Math::sqrt (sum);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // multiply a matrix with a vector and a sclaing factor

  Rvi& Rmatrix::mul (Rvi& r, const Rvi& x, const t_real s) const {
    rdlock ();
    x.rdlock ();
    r.wrlock ();
    try {
      // extract operating size
      t_long size = r.getsize ();
      t_long rows = getrsiz ();
      t_long cols = getcsiz ();
      // check target size
      if ((size != rows) || (x.getsize () != cols)) {
	throw Exception ("matrix-error", "incompatible size in matrix mul");
      }
      // try to map the operating vectors
      const Rvector* xv = dynamic_cast <const Rvector*> (&x);
      Rvector*       rv = dynamic_cast <Rvector*> (&r);
      if ((xv != nilp) && (rv != nilp)) {
	imul (*rv, *xv, s);
      } else {
	Rmi::mul (r, x, s);
      }
      // success
      unlock   ();
      x.unlock ();
      r.unlock ();
      return r;
    } catch (...) {
      unlock ();
      x.unlock ();
      r.unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - protected section                                                     -
  // -------------------------------------------------------------------------

   // internally multiply a matrix-vector - no verification - no lock

  Rvector& Rmatrix::imul (Rvector& rv, const Rvector& xv, 
			  const t_real s) const {
    // get the row a column size
    t_long rows = getrsiz ();
    t_long cols = getcsiz ();
    // perform the operation
    for (t_long i = 0; i < rows; i++) {
      t_real v = 0.0;
      for (t_long j = 0; j < cols; j++) v += p_rtab[i][j] * xv.p_vtab [j];
      rv.p_vtab[i] = s*v;
    }
    return rv;
  }

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

  // create a new object in a generic way

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

  // operate this matrix with another object

  Object* Rmatrix::oper (t_oper type, Object* object) {
    Rvector* vobj = dynamic_cast <Rvector*> (object);
    Rmatrix* mobj = dynamic_cast <Rmatrix*> (object);
    switch (type) {
    case Object::ADD:
      if (mobj != nilp) return new Rmatrix (*this + *mobj);
      break;
    case Object::SUB:
      if (mobj != nilp) return new Rmatrix (*this - *mobj);
      break;
    case Object::MUL:
      if (vobj != nilp) return new Rvector (*this * *vobj);
      if (mobj != nilp) return new Rmatrix (*this * *mobj);
      break;
    default:
      throw Exception ("matrix-error", "invalid operator with rmatrix",
		       Object::repr (object));
      break;
    }
    throw Exception ("type-error", "invalid operand with rmatrix",
		     Object::repr (object));
  }
}

