// ---------------------------------------------------------------------------
// - Rmi.cpp                                                                 -
// - afnix:mth module - real matrix interface 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-2011 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Rmi.hpp"
#include "Real.hpp"
#include "Math.hpp"
#include "Vector.hpp"
#include "Algebra.hpp"
#include "Boolean.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
 
namespace afnix {

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

  // create a default matrix

  Rmi::Rmi (void) {
    d_rsiz = 0;
    d_csiz = 0;
  }

  // create a square matrix by size

  Rmi::Rmi (const t_long size) {
    // check the size
    if (size < 0) {
      throw Exception ("size-error", "invalid real matrix size");
    }
    d_rsiz = size;
    d_csiz = size;
  }

  // create a matrix by size

  Rmi::Rmi (const t_long rsiz, const t_long csiz) {
    // check the size
    if ((rsiz < 0) || (csiz < 0)) {
      throw Exception ("size-error", "invalid real matrix size");
    }
    d_rsiz = rsiz;
    d_csiz = csiz;
  }

  // clear this matrix

  void Rmi::clear (void) {
    wrlock ();
    try {
      for (t_long i = 0; i < d_rsiz; i++) {
	for (t_long j = 0; j < d_csiz; j++) {
	  set (i, j, 0.0);
	}
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the matrix row size

  t_long Rmi::getrsiz (void) const {
    rdlock ();
    try {
      t_long result = d_rsiz;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // get the matrix column size

  t_long Rmi::getcsiz (void) const {
    rdlock ();
    try {
      t_long result = d_csiz;
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return true if the matrix is square

  bool Rmi::issquare (void) const {
    rdlock ();
    try {
      bool result = (d_rsiz == d_csiz);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // compare two matrices by precision

  bool Rmi::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;
      // loop in vector
      for (t_long i = 0; i < d_rsiz; i++) {
	for (t_long j = 0; j < d_csiz; j++) {
	  t_real tij = get (i, j);
	  t_real mij = mx.get (i, j);
	  if (Math::acmp (tij, mij) == false) {
	    result = false;
	    break;
	  }
	}
      }
      // unlock and return
      unlock ();
      mx.unlock ();
      return result;
    } catch (...) {
      unlock ();
      mx.unlock ();
      throw;
    }
  }

  // compute the matrix frobenius norm

  t_real Rmi::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 = get (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 scaling factor

  Rvi& Rmi::mul (Rvi& r, const Rvi& x, const t_real s) const {
    rdlock ();
    try {
      Algebra::mul (r, *this, x, s);
      unlock ();
      return r;
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 9;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the rmi supported quarks
  static const long QUARK_QEQ     = zone.intern ("?=");
  static const long QUARK_SET     = zone.intern ("set");
  static const long QUARK_GET     = zone.intern ("get");
  static const long QUARK_NORM    = zone.intern ("norm");
  static const long QUARK_CLEAR   = zone.intern ("clear");
  static const long QUARK_TORSO   = zone.intern ("to-row-sparse");
  static const long QUARK_GETCFM  = zone.intern ("get-cofactor");
  static const long QUARK_GETRSIZ = zone.intern ("get-row-size");
  static const long QUARK_GETCSIZ = zone.intern ("get-col-size");

  // return true if the given quark is defined

  bool Rmi::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true){
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }

  // apply this object with a set of arguments and a quark
  
  Object* Rmi::apply (Runnable* robj, Nameset* nset, const long quark,
                      Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();

    // dispatch 0 argument
    if (argc == 0) {
      if (quark == QUARK_NORM)    return new Real (norm ());
      if (quark == QUARK_GETRSIZ) return new Integer (getrsiz ());
      if (quark == QUARK_GETCSIZ) return new Integer (getcsiz ());
      if (quark == QUARK_CLEAR) {
	clear ();
	return nilp;
      }
    }
    
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_QEQ) {
	Object* obj = argv->get (0);
	Rmi*    rmo = dynamic_cast <Rmi*> (obj);
	if (rmo == nilp) {
	  throw Exception ("type-error", "invalid object for compare",
			   Object::repr (obj));
	}
	return new Boolean (cmp (*rmo));
      }
      if (quark == QUARK_TORSO) {
	t_long row = argv->getlong (0);
	return torso (row);
      }
    }
    // dispatch 2 argument
    if (argc == 2) {
      if (quark == QUARK_GET) {
        t_long lpos = argv->getlong (0);
        t_long cpos = argv->getlong (1);
        return new Real (get (lpos, cpos));
      }
      if (quark == QUARK_GETCFM) {
        t_long row = argv->getlong (0);
        t_long col = argv->getlong (1);
        return getcfm (row, col);
      }
    }
    // dispatch 3 arguments
    if (argc == 3) {
      if (quark == QUARK_SET) {
        t_long lpos = argv->getlong (0);
        t_long cpos = argv->getlong (1);
        t_real  val = argv->getreal (2);
	set (lpos, cpos, val);
        return nilp;
      }
    }
    // call the object
    return Object::apply (robj, nset, quark, argv);
  }
}

