// ---------------------------------------------------------------------------
// - Real.cpp                                                                -
// - standard object library - real class 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-2009 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Real.hpp"
#include "Stdsid.hxx"
#include "Vector.hpp"
#include "Utility.hpp"
#include "Boolean.hpp"
#include "Runnable.hpp"
#include "Character.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
#include "InputStream.hpp"
#include "OutputStream.hpp"
#include "cmth.hpp"
#include "ccnv.hpp"

namespace afnix {

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

  // the default precision
  t_real Real::d_eps = 0.00001;

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

  // create a new default real

  Real::Real (void) {
    d_value = 0.0;
  }

  // create a new real from a native real

  Real::Real (const t_real value) {
    d_value = value;
  }

  // create a new real from an Integer

  Real::Real (const Integer& value) {
    d_value = value.tointeger ();
  }

  // create a new real from a string

  Real::Real (const String& value) {
    bool status = false;
    const char* data = value.tochar ();
    d_value = c_atod (data, status);
    delete [] data;
    if (status == false) {
      throw Exception ("literal-error", "illegal string real number", value);
    }
  }
  
  // copy constructor for this real

  Real::Real (const Real& that) {
    that.rdlock ();
    d_value = that.d_value;
    that.unlock ();
  }

  // return the class name

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

  // return a literal representation of this real

  String Real::toliteral (void) const {
    return tostring ();
  }

  // return a string representation of this real

  String Real::tostring (void) const {
    rdlock ();
    char* buffer = c_dtoa (d_value);
    unlock ();
    String result (buffer);
    delete [] buffer;
    return result;
  }

  // return a clone of this real

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

  // return the real serial code

  t_byte Real::serialid (void) const {
    return SERIAL_REAL_ID;
  }

  // serialize this real

  void Real::wrstream (OutputStream& os) const {
    rdlock ();
    String sval = tostring ();
    sval.wrstream (os);
    unlock ();
  }

  // deserialize this real

  void Real::rdstream (InputStream& is) {
    wrlock ();
    String sval;
    sval.rdstream (is);
    *this = sval;
    unlock ();
  }
  
  // set an real with a value

  Real& Real::operator = (const t_real value) {
    wrlock ();
    d_value = value;
    unlock ();
    return *this;
  }

  // set an real with a value

  Real& Real::operator = (const Real& value) {
    wrlock ();
    value.rdlock ();
    d_value = value.d_value;
    value.unlock ();
    unlock ();
    return *this;
  }

  // compute the opposite of a real

  Real operator - (const Real& x) {
    x.rdlock ();
    Real result = -x.d_value;
    x.unlock ();
    return result;
  }

  // add two double reals together

  Real operator + (const Real& x, const Real& y) {
    x.rdlock ();
    y.rdlock ();
    Real result = x.d_value + y.d_value;
    y.unlock ();
    x.unlock ();
    return result;
  }

  // subtract two double reals

  Real operator - (const Real& x, const Real& y) {
    x.rdlock ();
    y.rdlock ();
    Real result = x.d_value - y.d_value;
    y.unlock ();
    x.unlock ();
    return result;
  }

  // multiply two double reals

  Real operator * (const Real& x, const Real& y) {
    x.rdlock ();
    y.rdlock ();
    Real result = x.d_value * y.d_value;
    y.unlock ();
    x.unlock ();
    return result;
  }

  // divide two double reals 

  Real operator / (const Real& x, const Real& y) {
    x.rdlock ();
    y.rdlock ();
    Real result = x.d_value / y.d_value;
    y.unlock ();
    x.unlock ();
    return result;
  }

  // add a real to this one

  Real& Real::operator += (const Real& x) {
    wrlock   ();
    x.rdlock ();
    d_value += x.d_value;
    x.unlock ();
    unlock   ();
    return *this;
  }

  // subtract a real to this one

  Real& Real::operator -= (const Real& x) {
    wrlock   ();
    x.rdlock ();
    d_value -= x.d_value;
    x.unlock ();
    unlock   ();
    return *this;
  }

  // multiply a real with this one

  Real& Real::operator *= (const Real& x) {
    wrlock   ();
    x.rdlock ();
    d_value *= x.d_value;
    x.unlock ();
    unlock   ();
    return *this;
  }

  // multiply a real with this one

  Real& Real::operator /= (const Real& x) {
    wrlock   ();
    x.rdlock ();
    d_value /= x.d_value;
    x.unlock ();
    unlock   ();
    return *this;
  }

  // prefix add one to the real

  Real& Real::operator ++ (void) {
    wrlock ();
    ++d_value;
    unlock ();
    return *this;
  }

  // postfix add one to the real

  Real Real::operator ++ (int) {
    wrlock ();
    Real result = *this;
    d_value++;
    unlock ();
    return result;
  }

  // prefix sub one to the real

  Real& Real::operator -- (void) {
    wrlock ();
    --d_value;
    unlock ();
    return *this;
  }

  // postfix sub one to the real

  Real Real::operator -- (int) {
    wrlock ();
    Real result = *this;
    d_value--;
    unlock ();
    return result;
  }

  // compare an real with a native value
  
  bool Real::operator == (const t_long value) const {
    rdlock ();
    bool result = (d_value == (t_real) value);
    unlock ();
    return result;
  }
  
  // compare an real with a native value
  
  bool Real::operator != (const t_long value) const {
    rdlock ();
    bool result = (d_value != (t_real) value);
    unlock ();
    return result;
  }

  // compare an real with a double value
  
  bool Real::operator == (const t_real value) const {
    rdlock ();
    bool result = (d_value == value);
    unlock ();
    return result;
  }
  
  // compare an real with a native value
  
  bool Real::operator != (const t_real value) const {
    rdlock ();
    bool result = (d_value != value);
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator == (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value == value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator != (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value != value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator < (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value < value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator <= (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value <= value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator > (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value > value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }

  // compare two reals

  bool Real::operator >= (const Real& value) const {
    rdlock ();
    value.rdlock ();
    bool result = (d_value >= value.d_value);
    value.unlock ();
    unlock ();
    return result;
  }


  // return true if the number is 0

  bool Real::iszero (void) const {
    rdlock ();
    bool result = (d_value == 0.0);
    unlock ();
    return result;
  }

  // compare with a real upto to epsilon

  bool Real::cmp (const t_real value) const {
    rdlock ();
    t_real delta  = (d_value <= value) ? value - d_value : d_value - value;
    bool   result = (delta <= Real::d_eps);
    unlock ();
    return result;
  }

  // return true if the number is nan

  bool Real::isnan (void) const {
    rdlock ();
    bool result = c_isnan (d_value);
    unlock ();
    return result;
  }

  // return the ceiling of this number

  Real Real::ceiling (void) const {
    rdlock ();
    Real result = c_ceiling (d_value);
    unlock ();
    return result;
  }

  // return the floor of this number

  Real Real::floor (void) const {
    rdlock ();
    Real result = c_floor (d_value);
    unlock ();
    return result;
  }

  // return the absolute value of this number
  
  Real Real::abs (void) const {
    rdlock ();
    Real result = c_abs (d_value);
    unlock ();
    return result;
  }
  
  // return the remainder of this number with its argument
  
  Real Real::mod (const Real& x) const {
    rdlock   ();
    x.rdlock ();
    Real result = c_mod (d_value, x.d_value);
    x.unlock ();
    unlock   ();
    return result;
  }
  
  // return the square root of this real
  
  Real Real::sqrt (void) const {
    rdlock ();
    bool status = false;
    Real result = c_sqrt (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with sqrt call");
    }
    unlock ();
    return result;
  }
  
  // return the natural logarithm of this real
  
  Real Real::log (void) const {
    rdlock ();
    bool status = false;
    Real result = c_log (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with log call");
    }
    unlock ();
    return result;
  }
  
  // return the exponential of this number
  
  Real Real::exp (void) const {
    rdlock ();
    Real result = c_exp (d_value);
    unlock ();
    return result;
  }
  
  // return the power of this number with the argument
  
  Real Real::pow (const Real& x) const {
    rdlock   ();
    x.rdlock ();
    Real result = c_pow (d_value, x.d_value);
    x.unlock ();
    unlock   ();
    return result;
  }
  
  // return the sine of this number
  
  Real Real::sin (void) const {
    rdlock ();
    Real result = c_sin (d_value);
    unlock ();
    return result;
  }
  
  // return the cosine of this number
  
  Real Real::cos (void) const {
    rdlock ();
    Real result = c_cos (d_value);
    unlock ();
    return result;
  }
  
  // return the tangent of this number
  
  Real Real::tan (void) const {
    rdlock ();
    Real result = c_tan (d_value);
    unlock ();
    return result;
  }
  
  // return the arc sine of this number
  
  Real Real::asin (void) const {
    rdlock ();
    bool status = false;
    Real result = c_asin (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with asin call");
    }
    unlock ();
    return result;
  }
  
  // return the arc cosine of this number
  
  Real Real::acos (void) const {
    rdlock ();
    bool status = false;
    Real result = c_acos (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with acos call");
    }
    unlock ();
    return result;
  }
  
  // return the arc tangent of this number
  
  Real Real::atan (void) const {
    rdlock ();
    bool status = false;
    Real result = c_atan (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with atan call");
    }
    unlock ();
    return result;
  }
  
  // return the hyperbolic sine of this number
  
  Real Real::sinh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_sinh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with sinh call");
    }
    unlock ();
    return result;
  }
  
  // return the hyperbolic cosine of this number
  
  Real Real::cosh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_cosh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with cosh call");
    }
    unlock ();
    return result;
  }

  // return the hyperbolic tangent of this number

  Real Real::tanh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_tanh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with tanh call");
    }
    unlock ();
    return result;
  }
  
  // return the hyperbolic arc sine of this number

  Real Real::asinh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_asinh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with asinh call");
    }
    unlock ();
    return result;
  }
  
  // return the hyperbolic arc cosine of this number
  
  Real Real::acosh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_acosh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with acosh call");
    }
    unlock ();
    return result;
  }

  // return the hyperbolic arc tangent of this number
  
  Real Real::atanh (void) const {
    rdlock ();
    bool status = false;
    Real result = c_atanh (d_value,status);
    if (status == false) {
      unlock ();
      throw Exception ("real-error", "math error with atanh call");
    }
    unlock ();
    return result;
  }

  // return a formatted string of this real with a precision

  String Real::format (const long precision) const {
    if (precision < 0) {
      throw Exception ("precision-error", 
		       "invalid negative precision with format");
    }
    rdlock ();
    try {
      // check for null precision
      if (precision == 0) {
	t_long data = (t_long) d_value;
	unlock ();
	return Utility::tostring (data);
      }
      // use base precision
      char* buffer = c_dtoap (d_value, precision);
      unlock ();
      String result (buffer);
      delete [] buffer;
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // return the double representation

  t_real Real::toreal (void) const {
    rdlock ();
    t_real result = d_value;
    unlock ();
    return result;
  }

  // get an integer from this real
  
  t_long Real::tointeger (void) const {
    rdlock ();
    t_long result = (t_long) d_value;
    unlock ();
    return result;
  }

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

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

  // the object supported quarks
  static const long QUARK_OPP      = zone.intern ("++");
  static const long QUARK_OMM      = zone.intern ("--");
  static const long QUARK_ADD      = zone.intern ("+");
  static const long QUARK_SUB      = zone.intern ("-");
  static const long QUARK_MUL      = zone.intern ("*");
  static const long QUARK_DIV      = zone.intern ("/");
  static const long QUARK_EQL      = zone.intern ("==");
  static const long QUARK_NEQ      = zone.intern ("!=");
  static const long QUARK_LTH      = zone.intern ("<");
  static const long QUARK_LEQ      = zone.intern ("<=");
  static const long QUARK_GTH      = zone.intern (">");
  static const long QUARK_GEQ      = zone.intern (">=");
  static const long QUARK_AEQ      = zone.intern ("+=");
  static const long QUARK_SEQ      = zone.intern ("-=");
  static const long QUARK_MEQ      = zone.intern ("*=");
  static const long QUARK_DEQ      = zone.intern ("/=");
  static const long QUARK_QEQ      = zone.intern ("?=");
  static const long QUARK_ABS      = zone.intern ("abs");
  static const long QUARK_POW      = zone.intern ("pow");  
  static const long QUARK_LOG      = zone.intern ("log");
  static const long QUARK_EXP      = zone.intern ("exp");
  static const long QUARK_SIN      = zone.intern ("sin");
  static const long QUARK_COS      = zone.intern ("cos");
  static const long QUARK_TAN      = zone.intern ("tan");
  static const long QUARK_SQRT     = zone.intern ("sqrt");
  static const long QUARK_NANP     = zone.intern ("nan-p");
  static const long QUARK_ASIN     = zone.intern ("asin");
  static const long QUARK_ACOS     = zone.intern ("acos");
  static const long QUARK_ATAN     = zone.intern ("atan");
  static const long QUARK_SINH     = zone.intern ("sinh");
  static const long QUARK_COSH     = zone.intern ("cosh");
  static const long QUARK_TANH     = zone.intern ("tanh");
  static const long QUARK_ASINH    = zone.intern ("asinh");
  static const long QUARK_ACOSH    = zone.intern ("acosh");
  static const long QUARK_ATANH    = zone.intern ("atanh");
  static const long QUARK_FLOOR    = zone.intern ("floor");
  static const long QUARK_ZEROP    = zone.intern ("zero-p");
  static const long QUARK_CEILING  = zone.intern ("ceiling");

  // evaluate an object to a real value

  t_real Real::evalto (Runnable* robj, Nameset* nset, Object* object) {
    Object* obj = (object == nilp) ? nilp : object->eval (robj, nset);
    Real*   val = dynamic_cast <Real*> (obj);
    if (val == nilp) throw Exception ("type-error", "nil object to evaluate");
    return val->toreal ();
  }

  // create a new real in a generic way

  Object* Real::mknew (Vector* argv) {
    if ((argv == nilp) || (argv->length () == 0)) return new Real;
    if (argv->length () != 1) 
      throw Exception ("argument-error", 
		       "too many argument with real constructor");
    // try to map the real argument
    Object* obj = argv->get (0);
    if (obj == nilp) return new Real;

    // try an integer object
    Integer* ival = dynamic_cast <Integer*> (obj);
    if (ival != nilp) return new Real (ival->tointeger ());

    // try a real object
    Real* rval = dynamic_cast <Real*> (obj);
    if (rval != nilp) return new Real (*rval);

    // try a character object
    Character* cval = dynamic_cast <Character*> (obj);
    if (cval != nilp) return new Real (cval->toquad ());

    // try a string object
    String* sval = dynamic_cast <String*> (obj);
    if (sval != nilp) return new Real (*sval);

    // illegal object
    throw Exception ("type-error", "illegal object with real constructor",
		     obj->repr ());
  }

  // return true if the given quark is defined

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

  // operate this real with another object

  Object* Real::oper (t_oper type, Object* object) {
    Integer* iobj = dynamic_cast <Integer*> (object);
    Real*    dobj = dynamic_cast <Real*>    (object);
    switch (type) {
    case Object::ADD:
      if (iobj != nilp) return new Real (*this + *iobj);
      if (dobj != nilp) return new Real (*this + *dobj);
      break;
    case Object::SUB:
      if (iobj != nilp) return new Real (*this - *iobj);
      if (dobj != nilp) return new Real (*this - *dobj);
      break;
    case Object::MUL:
      if (iobj != nilp) return new Real (*this * *iobj);
      if (dobj != nilp) return new Real (*this * *dobj);
      break;
    case Object::DIV:
      if (iobj != nilp) return new Real (*this / *iobj);
      if (dobj != nilp) return new Real (*this / *dobj);
      break;
    case Object::UMN:
      return new Real (-(*this));
      break;
    case Object::EQL:
      if (iobj != nilp) return new Boolean (*this == *iobj);
      if (dobj != nilp) return new Boolean (*this == *dobj);
      break;
    case Object::NEQ:
      if (iobj != nilp) return new Boolean (*this != *iobj);
      if (dobj != nilp) return new Boolean (*this != *dobj);
      break;
    case Object::GEQ:
      if (iobj != nilp) return new Boolean (*this >= *iobj);
      if (dobj != nilp) return new Boolean (*this >= *dobj);
      break;
    case Object::GTH:
      if (iobj != nilp) return new Boolean (*this > *iobj);
      if (dobj != nilp) return new Boolean (*this > *dobj);
      break;
    case Object::LEQ:
      if (iobj != nilp) return new Boolean (*this <= *iobj);
      if (dobj != nilp) return new Boolean (*this <= *dobj);
      break;
    case Object::LTH:
      if (iobj != nilp) return new Boolean (*this < *iobj);
      if (dobj != nilp) return new Boolean (*this < *dobj);
      break;
    }
    throw Exception ("type-error", "invalid operand with real",
		     Object::repr (object));
  }

  // set an object to this real

  Object* Real::vdef (Runnable* robj, Nameset* nset, Object* object) {
    Integer* iobj = dynamic_cast <Integer*> (object);
    if (iobj != nilp) {
      *this = *iobj;
      return this;
    }
    Real* dobj = dynamic_cast <Real*> (object);
    if (dobj != nilp) {
      *this = *dobj;
      return this;
    }
    throw Exception ("type-error", "invalid object with real vdef",
		     Object::repr (object));
  }

  // apply this object with a set of arguments and a quark

  Object* Real::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_ABS)      return new Real    (abs     ());
      if (quark == QUARK_LOG)      return new Real    (log     ());
      if (quark == QUARK_EXP)      return new Real    (exp     ());
      if (quark == QUARK_SIN)      return new Real    (sin     ());
      if (quark == QUARK_COS)      return new Real    (cos     ());
      if (quark == QUARK_TAN)      return new Real    (tan     ());
      if (quark == QUARK_SINH)     return new Real    (sinh    ());
      if (quark == QUARK_COSH)     return new Real    (cosh    ());
      if (quark == QUARK_TANH)     return new Real    (tanh    ());
      if (quark == QUARK_ASIN)     return new Real    (asin    ());
      if (quark == QUARK_ACOS)     return new Real    (acos    ());
      if (quark == QUARK_ATAN)     return new Real    (atan    ());
      if (quark == QUARK_SQRT)     return new Real    (sqrt    ());
      if (quark == QUARK_NANP)     return new Real    (isnan   ());
      if (quark == QUARK_ASINH)    return new Real    (asinh   ());
      if (quark == QUARK_ACOSH)    return new Real    (acosh   ());
      if (quark == QUARK_ATANH)    return new Real    (atanh   ());
      if (quark == QUARK_FLOOR)    return new Real    (floor   ());
      if (quark == QUARK_CEILING)  return new Real    (ceiling ());
      if (quark == QUARK_ZEROP)    return new Boolean (iszero  ());

      if (quark == QUARK_OPP) {
	++(*this);
	robj->post (this);
	return this;
      }
      if (quark == QUARK_OMM) {
	--(*this);
	robj->post (this);
	return this;
      }
    }

    if (argc == 1) {
      if (quark == QUARK_ADD) return oper (Object::ADD, argv->get (0));
      if (quark == QUARK_SUB) return oper (Object::SUB, argv->get (0)); 
      if (quark == QUARK_MUL) return oper (Object::MUL, argv->get (0)); 
      if (quark == QUARK_DIV) return oper (Object::DIV, argv->get (0)); 
      if (quark == QUARK_EQL) return oper (Object::EQL, argv->get (0)); 
      if (quark == QUARK_NEQ) return oper (Object::NEQ, argv->get (0)); 
      if (quark == QUARK_LTH) return oper (Object::LTH, argv->get (0)); 
      if (quark == QUARK_LEQ) return oper (Object::LEQ, argv->get (0)); 
      if (quark == QUARK_GTH) return oper (Object::GTH, argv->get (0)); 
      if (quark == QUARK_GEQ) return oper (Object::GEQ, argv->get (0)); 

      if (quark == QUARK_AEQ) {
	t_real val = argv->getireal (0);
	*this += val;
	robj->post (this);
	return this;
      }
      if (quark == QUARK_SEQ) {
	t_real val = argv->getireal (0);
	*this -= val;
	robj->post (this);
	return this;
      }
      if (quark == QUARK_MEQ) {
	t_real val = argv->getireal (0);
	*this *= val;
	robj->post (this);
	return this;
      }
      if (quark == QUARK_DEQ) {
	t_real val = argv->getireal (0);
	*this /= val;
	robj->post (this);
	return this;
      }
      if (quark == QUARK_QEQ) {
	t_real val = argv->getreal (0);
	return new Boolean (cmp (val));
      }
      if (quark == QUARK_POW) {
	t_real val = argv->getireal (0);
	return new Real (pow (val));
      }
    }

    // call the number method
    return Number::apply (robj, nset, quark, argv);
  }
}
