// ---------------------------------------------------------------------------
// - 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Rmi.hpp"
#include "Real.hpp"
#include "Math.hpp"
#include "Mthsid.hxx"
#include "Vector.hpp"
#include "Utility.hpp"
#include "Algebra.hpp"
#include "Boolean.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "OutputStream.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;
  }

  // return the anonymous rmi serial code

  t_byte Rmi::serialid (void) const {
    return SERIAL_ARMI_ID;
  }

  // serialize this object
  
  void Rmi::wrstream (OutputStream& os) const {
    rdlock ();
    try {
      // write the matric row/col size
      mth_wrlong (d_rsiz, os);
      mth_wrlong (d_csiz, os);
      // write the matrix data
      for (t_long row = 0; row < d_rsiz; row++) {
	for (t_long col = 0; col < d_csiz; col++) {
	  // get the matrix value
	  t_real val = get (row, col);
	  if (val == 0.0) continue;
	  // write by position
	  mth_wrlong (row, os);
	  mth_wrlong (col, os);
	  mth_wrreal (val, os);
	}
      }
      // write marker for end
      mth_wrlong (-1, os);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // deserialize this object

  void Rmi::rdstream (InputStream& is) {
    wrlock ();
    try {
      // get the matrix row/col size
      t_long rsiz = mth_rdlong (is);
      t_long csiz = mth_rdlong (is);
      // resize the matrix
      resize (rsiz, csiz);
      // get the matrix data by position
      for (t_long i = 0; i < rsiz; i++) {
	for (t_long j = 0; j < csiz; j++) {
	  t_long row = mth_rdlong (is);
	  // check for marker
	  if (row == -1) {
	    unlock ();
	    return;
	  }
	  t_long col = mth_rdlong (is);
	  t_real val = mth_rdreal (is);
	  set (row, col, val);
	}
      }
      // get the remaining marker
      if (mth_rdlong (is) != -1) {
	throw Exception ("rmi-error", "inconsistent serialized matrix");
      }
      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;
    }
  }

  // clear this matrix

  void Rmi::clear (void) {
    wrlock ();
    try {
      for (t_long row = 0; row < d_rsiz; row++) {
	for (t_long col = 0; col < d_csiz; col++) {
	  set (row, col, 0.0);
	}
      }
      unlock ();
    } 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;
    }
  }

  // make this matrix diagonal dominant
  
  void Rmi::toddom (const t_real df) {
    wrlock ();
    try {
      Algebra::toddom (*this, df);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // fill this matrix with random values
  
  void Rmi::random (const t_real rmin, const t_real rmax) {
    wrlock ();
    try {
      Algebra::random (*this, rmin, rmax);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // fill this matrix with sparse random values
  
  void Rmi::sparse (const t_real rmin, const t_real rmax, const t_long nzsz) {
    wrlock ();
    try {
      Algebra::sparse (*this, rmin, rmax, nzsz);
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // dump the matrix to an output stream

  void Rmi::dump (OutputStream& os) const {
    rdlock ();
    try {
      for (long i = 0; i < d_rsiz; i++) {
	for (long j = 0; j < d_csiz; j++) {
	  os << Utility::tostring (get (i,j), true);
	  if (j < (d_csiz - 1)) os << ",...";
	  if ((j == (d_csiz - 1)) && (i < (d_rsiz - 1))) os << ';';
	  os << eolc;
	}
      }
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

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

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 11;
  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_DUMP    = zone.intern ("dump");
  static const long QUARK_NORM    = zone.intern ("norm");
  static const long QUARK_CLEAR   = zone.intern ("clear");
  static const long QUARK_TODDOM  = zone.intern ("to-diagonal-dominant");
  static const long QUARK_RANDOM  = zone.intern ("fill-random");
  static const long QUARK_SPARSE  = zone.intern ("fill-sparse");
  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;
      }
      if (quark == QUARK_TODDOM) {
	toddom (0.0);
	return nilp;
      }
      if (quark == QUARK_DUMP) {
        OutputStream* os = robj->getos ();
        dump (*os);
        return nilp;
      }
    }
    // dispatch 1 argument
    if (argc == 1) {
      if (quark == QUARK_TODDOM) {
	t_real df = argv->getreal (0);
	toddom (df);
	return nilp;
      }
      if (quark == QUARK_RANDOM) {
	t_real rmax = argv->getreal (0);
	random (0.0, rmax);
	return nilp;
      }
      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_DUMP) {
        Object*      obj = argv->get (0);
        OutputStream* os = dynamic_cast <OutputStream*> (obj);
	if (os == nilp) {
	  throw Exception ("type-error", "invalid object with format",
			   Object::repr (obj));
	}
	dump (*os);
	return nilp;
      }
    }
    // dispatch 2 argument
    if (argc == 2) {
      if (quark == QUARK_GET) {
        t_long row = argv->getlong (0);
        t_long col = argv->getlong (1);
        return new Real (get (row, col));
      }
      if (quark == QUARK_RANDOM) {
	t_real rmin = argv->getreal (0);
	t_real rmax = argv->getreal (1);
	random (rmin, rmax);
	return nilp;
      }
      if (quark == QUARK_SPARSE) {
	t_real rmax = argv->getreal (0);
	t_long nzsz = argv->getlong (1);
	sparse (0.0, rmax, nzsz);
	return nilp;
      }
    }
    // dispatch 3 arguments
    if (argc == 3) {
      if (quark == QUARK_SET) {
        t_long row = argv->getlong (0);
        t_long col = argv->getlong (1);
        t_real val = argv->getrint (2);
	set (row, col, val);
        return nilp;
      }
      if (quark == QUARK_SPARSE) {
	t_real rmin = argv->getreal (0);
	t_real rmax = argv->getreal (1);
	t_long nzsz = argv->getlong (2);
	sparse (rmin, rmax, nzsz);
	return nilp;
      }
    }
    // call the object
    return Object::apply (robj, nset, quark, argv);
  }
}

