// arithmetic.cc -  definitions for pseudo-instructions for arithmetic
//
// ##Copyright##
// 
// Copyright 2000-2016 Peter Robinson  (pjr@itee.uq.edu.au)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.00 
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ##Copyright##
//
// $Id: arithmetic.cc,v 1.10 2006/01/31 23:17:49 qp Exp $

#include <math.h>
#ifdef WIN32
//#define M_PI       3.14159265358979323846
//#define M_E        2.71828182845904523536
        //We'll have to use our own function
        double round(double number)
        {
                return number < 0.0 ? ceil(number-0.5) : floor(number+0.5);
        }
#endif

#include "atom_table.h"
#include "thread_qp.h"

extern AtomTable *atoms;

#define ARITH_INTEGER_TYPE    0
#define ARITH_DOUBLE_TYPE 1


typedef struct
{ int   type;                           /* type of number */
  union { 
          qint64  i;                      /* integer */
          double d;                     /* double */
        } value;
} number;

#define IS_INT(x) (x.type == ARITH_INTEGER_TYPE)
#define BOTH_INTS(x,y) ((x.type == ARITH_INTEGER_TYPE) && (y.type == ARITH_INTEGER_TYPE)) 
#define GET_INT_VAL(x) (x.value.i)
#define GET_DOUBLE_VAL(x) ((x.type == ARITH_INTEGER_TYPE) ? (double)(x.value.i) : x.value.d)
#define MAKE_INT(x, v) {x.type = ARITH_INTEGER_TYPE; x.value.i = v;}
#define MAKE_DOUBLE(x, v) {x.type = ARITH_DOUBLE_TYPE; x.value.d = v;}  
/*
#define MAKE_DOUBLE(x, v)                                  	\
do {                                                       	\
  long n = (long)(v);						\
  if ((v) == (double)n)						\
    { x.type = ARITH_INTEGER_TYPE; x.value.i = n; }		\
  else								\
    {x.type = ARITH_DOUBLE_TYPE; x.value.d = (v);}		\
} while (0)
*/

#define IS_ZERO(x) ((x.type == ARITH_INTEGER_TYPE) && (x.value.i == 0))

number zero = {ARITH_INTEGER_TYPE, {0}};
//
// arithEvaluate is an auxilary function used by arithmetical pseudo
//instructions to carry out the evaluation of expressions.
//
number
arithEvaluate(PrologValue& val, Heap& heap, ErrorValue& error_value)
{
  if (val.getTerm()->isInteger())
    {
      number y;
      MAKE_INT(y, val.getTerm()->getInteger());
      return y;
    }
  else if (val.getTerm()->isDouble())
    {
      number y;
      MAKE_DOUBLE(y, val.getTerm()->getDouble());
      return y;
    }
  else if (val.getTerm()->isAtom())
    { 
      if (val.getTerm() == AtomTable::pi)
        {
          number y;
          MAKE_DOUBLE(y, M_PI); // PI
          return y;
        }
      if (val.getTerm() == AtomTable::e)
        {
          number y;
          MAKE_DOUBLE(y, M_E); // E
          return y;
        }
      else
	{
	  error_value = EV_TYPE;
	  return zero;
	}
    }      
  else if (val.getTerm()->isStructure())
    {
      number res1, res2;
      
      Structure *structure = OBJECT_CAST(Structure *, val.getTerm());
      
      size_t arity = structure->getArity();
      
      //
      // calcualte the values of the arguments
      //
      if (arity == 1)
	{
	  PrologValue val1(val.getSubstitutionBlockList(),
			   structure->getArgument(1));	  
	  heap.prologValueDereference(val1);
	  res1 = arithEvaluate(val1, heap, error_value);
	  
	  if (error_value != EV_NO_ERROR)
	    {
	      return zero;
	    }
	}
      else if (arity == 2)
	{
	  PrologValue val1(val.getSubstitutionBlockList(),
			   structure->getArgument(1));	  
	  heap.prologValueDereference(val1);
	  res1 = arithEvaluate(val1, heap, error_value);
	  PrologValue val2(val.getSubstitutionBlockList(),
			   structure->getArgument(2));	  
	  heap.prologValueDereference(val2);
	  res2 = arithEvaluate(val2, heap, error_value);
	  
	  if (error_value != EV_NO_ERROR)
	    {
	      return zero;
	    }
	}
      else // illegal number of arguments
	{
	  error_value = EV_TYPE;
	  return zero;
	}
      
      //
      // evaluate the expression according to the operator
      //
      PrologValue funpv(val.getSubstitutionBlockList(),
			structure->getFunctor());	  
      heap.prologValueDereference(funpv);
      Object* fun = funpv.getTerm();
      
      assert(fun->isAtom());
      
      Atom *op = OBJECT_CAST(Atom *, fun);
      if (op == AtomTable::plus)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) + GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      MAKE_DOUBLE(res, GET_DOUBLE_VAL(res1) + GET_DOUBLE_VAL(res2));
	      return res;
            }
	}
      else if (op == AtomTable::minus)
	{
	  if (arity == 1)
	    {
              if IS_INT(res1)
                {
	          number res;
                  MAKE_INT(res, - GET_INT_VAL(res1));
	          return res;
                }
              else
                {
	          number res;
	          MAKE_DOUBLE(res, - GET_DOUBLE_VAL(res1));
	          return res;
                }
            }
	  else if (arity == 2)
	    {
	      if BOTH_INTS(res1, res2)
                {
	          number res;
                  MAKE_INT(res, GET_INT_VAL(res1) - GET_INT_VAL(res2));
	          return res;
                }
              else
                {
	          number res;
	          MAKE_DOUBLE(res, GET_DOUBLE_VAL(res1) - GET_DOUBLE_VAL(res2));
	          return res;
                }
	    }
	  else
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	}
      else if (op == AtomTable::multiply)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) * GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      number res;
	      errno = 0;
	      MAKE_DOUBLE(res, GET_DOUBLE_VAL(res1) * GET_DOUBLE_VAL(res2));
	      if (errno != 0)
	        {
	          error_value = EV_RANGE;
	          return zero;
	        }
	      return res;
            }
	}
      else if (op == AtomTable::maxi)
	{
	  if (GET_DOUBLE_VAL(res1) > GET_DOUBLE_VAL(res2))
	    return res1;
	  else
	    return res2;
	}      
      else if (op == AtomTable::min)
	{
	  if (GET_DOUBLE_VAL(res1) < GET_DOUBLE_VAL(res2))
	    return res1;
	  else
	    return res2;
	}      
       else if (op == AtomTable::abs)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
          if IS_INT(res1)
            {
	      number res;
              MAKE_INT(res, abs(GET_INT_VAL(res1)));
	      return res;
            }
          else
            {
	      number res;
	      MAKE_DOUBLE(res, fabs(GET_DOUBLE_VAL(res1)));
	      return res;
            }
        }
      else if (op == AtomTable::round)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
          if IS_INT(res1)
            {
	      return res1;
            }
           else
            {
	      number res;
              MAKE_INT(res, (qint64)round(GET_DOUBLE_VAL(res1)));
	      return res;
            }
        }
      else if (op == AtomTable::floor)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
          if IS_INT(res1)
            {
	      return res1;
            }
           else
            {
	      number res;
              MAKE_INT(res, (qint64)floor(GET_DOUBLE_VAL(res1)));
	      return res;
            }
        }
      else if (op == AtomTable::truncate)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
          if IS_INT(res1)
            {
	      return res1;
            }
           else
            {
	      number res;
              double num = GET_DOUBLE_VAL(res1);
              if (num > 0) {
                MAKE_INT(res, (qint64)floor(num));
              }
              else {
                MAKE_INT(res, (qint64)ceil(num));
              }
	      return res;
            }
        }
       else if (op == AtomTable::ceiling)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
          if IS_INT(res1)
            {
	      return res1;
            }
           else
            {
	      number res;
              MAKE_INT(res, (qint64)ceil(GET_DOUBLE_VAL(res1)));
	      return res;
            }
        }
      else if (op == AtomTable::sqrt)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, sqrt(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
        }
      else if (op == AtomTable::sin)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  MAKE_DOUBLE(res, sin(GET_DOUBLE_VAL(res1)));
	  return res;
        }
      else if (op == AtomTable::cos)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  MAKE_DOUBLE(res, cos(GET_DOUBLE_VAL(res1)));
	  return res;
        }
      else if (op == AtomTable::tan)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, tan(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
        }
      else if (op == AtomTable::asin)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, asin(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
        }
      else if (op == AtomTable::acos)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, acos(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
        }
      else if (op == AtomTable::atan)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, atan(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
        }
      else if (op == AtomTable::log)
        {
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  number res;
	  errno = 0;
	  MAKE_DOUBLE(res, log(GET_DOUBLE_VAL(res1)));
	  if (errno != 0)
	    {
	      error_value = EV_RANGE;
	      return zero;
	    }
	  return res;
	}
      else if (op == AtomTable::divide)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if IS_ZERO(res2)
	    {
	      error_value = EV_ZERO_DIVIDE;
	      return zero;
	    }
	  number res;
          errno = 0;
          MAKE_DOUBLE(res, (double)GET_DOUBLE_VAL(res1) / (double)GET_DOUBLE_VAL(res2));
          if (errno != 0)
            {
	      error_value = EV_RANGE;
	      return zero;
            }
	  return res;
	}
      else if (op == AtomTable::intdivide)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if IS_ZERO(res2)
	    {
	      error_value = EV_ZERO_DIVIDE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) / GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::mod)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }	      
	  if IS_ZERO(res2)
	    {
	      error_value = EV_ZERO_DIVIDE;
	      return zero;
	    }

	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) % GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::power)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if (BOTH_INTS(res1, res2) && (GET_INT_VAL(res2) >= 0))
            {
	      number res;
              MAKE_INT(res, (qint64)::pow(GET_INT_VAL(res1), GET_INT_VAL(res2)));
	      return res;
            }
          else
            {
	      number res;
	      double x = GET_DOUBLE_VAL(res1);
	      double y = GET_DOUBLE_VAL(res2);
	      errno = 0;
	      double z = pow(x, y);
	      if (errno != 0)
	        {
	          error_value = EV_RANGE;
	          return zero;
	        }
	  
	      MAKE_DOUBLE(res, z);
	      return res;
            }
	}
      else if (op == AtomTable::bitwiseand)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) & GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::bitwiseor)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) | GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::shiftl)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) << GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::shiftr)
	{
	  if (arity != 2)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if BOTH_INTS(res1, res2)
            {
	      number res;
              MAKE_INT(res, GET_INT_VAL(res1) >> GET_INT_VAL(res2));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else if (op == AtomTable::bitneg)
	{
	  if (arity != 1)
	    {
	      error_value = EV_TYPE;
	      return zero;
	    }
	  if IS_INT(res1)
            {
	      number res;
              MAKE_INT(res, ~ GET_INT_VAL(res1));
	      return res;
            }
          else
            {
	      error_value = EV_TYPE;
	      return zero;
            }
	}
      else 
	{
	  error_value = EV_TYPE;
	  return zero;
	}
    }
  else if (val.getTerm()->isVariable())
    {
      error_value = EV_INST;
      return zero;
    }
  else
    {
      error_value = EV_TYPE;
      return zero;
    }

  return zero;
}

//
// psi_is(Out, Exp)
// mode(out,in)
//
Thread::ReturnValue
Thread::psi_is(Object *& object1, Object *& object2)
{
  // Each thread has an error_value member.
  error_value = EV_NO_ERROR;
  
  PrologValue val(object2);
  heap.prologValueDereference(val);
  number result = arithEvaluate(val, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 2);
    }
  else
    {
      if IS_INT(result)
        object1 = heap.newInteger(GET_INT_VAL(result));
      else
        object1 = heap.newDouble(GET_DOUBLE_VAL(result));

      return RV_SUCCESS;
    }
}

//
// psi_less(A, B)
// mode(in,in)
// true iff A < B
//
Thread::ReturnValue
Thread::psi_less(Object *& object1, Object *& object2)
{
  // Each thread has an error_value member.
  error_value = EV_NO_ERROR;

  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }
  PrologValue pval2(object2);
  heap.prologValueDereference(pval2);
  number res2 = arithEvaluate(pval2, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 2);
    }
  
  //
  // The two args evaluate correctly so test them
  //
  return BOOL_TO_RV(GET_DOUBLE_VAL(res1) < GET_DOUBLE_VAL(res2));
}

//
// psi_lesseq(A, B)
// mode(in,in)
// true iff A <= B
//
Thread::ReturnValue
Thread::psi_lesseq(Object *& object1, Object *& object2)
{
  // Each thread has an error_value member.
  error_value = EV_NO_ERROR;

  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }

  PrologValue pval2(object2);
  heap.prologValueDereference(pval2);
  number res2 = arithEvaluate(pval2, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 2);
    }
  
  //
  // The two args evaluate correctly so test them
  //
  return BOOL_TO_RV(GET_DOUBLE_VAL(res1) <= GET_DOUBLE_VAL(res2));

}


//
// psi_add(A,B,Sum)
// mode(in,in,out)
// Sum = A + B
//
Thread::ReturnValue
Thread::psi_add(Object *& object1, Object *& object2, Object *& object3)
{
  // Each thread has an error_value member.
  error_value = EV_NO_ERROR;

  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }

  PrologValue pval2(object2);
  heap.prologValueDereference(pval2);
  number res2 = arithEvaluate(pval2, heap, error_value);
 
  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 2);
    }

  //
  // The two args evaluate correctly so add them
  //
  if BOTH_INTS(res1, res2)
    {
      object3 = heap.newInteger(GET_INT_VAL(res1) + GET_INT_VAL(res2));
     }
   else
     {
      object3 = heap.newDouble(GET_DOUBLE_VAL(res1) + GET_DOUBLE_VAL(res2));
     }

  return RV_SUCCESS;
}

//
// psi_subtract(A,B,Diff)
// mode(in,in,out)
// Diff = A - B
//
Thread::ReturnValue
Thread::psi_subtract(Object *& object1, Object *& object2, Object *& object3)
{
  error_value = EV_NO_ERROR;

  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }

  PrologValue pval2(object2);
  heap.prologValueDereference(pval2);
  number res2 = arithEvaluate(pval2, heap, error_value);

  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 2);
    }

  //
  // The two args evaluate correctly so subtract them
  //
  if BOTH_INTS(res1, res2)
    {
      object3 = heap.newInteger(GET_INT_VAL(res1) - GET_INT_VAL(res2));
     }
   else
     {
      object3 = heap.newDouble(GET_DOUBLE_VAL(res1) - GET_DOUBLE_VAL(res2));
     }

  return RV_SUCCESS;
}


//
// psi_increment(A,B)
// mode(in,out)
// B = A + 1
//
Thread::ReturnValue
Thread::psi_increment(Object *& object1, Object *& object2)
{
  error_value = EV_NO_ERROR;
  
  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);
     
    if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }

  //
  // The arg evaluate correctly so increment it
  //
  if IS_INT(res1)
    {
      object2 = heap.newInteger(GET_INT_VAL(res1) + 1);
     }
   else
     {
      object2 = heap.newDouble(GET_DOUBLE_VAL(res1) + 1);
     }

  return RV_SUCCESS;
}

//
// psi_decrement(A,B)
// mode(in,out)
// B = A - 1
//
Thread::ReturnValue
Thread::psi_decrement(Object *& object1, Object *& object2)
{
  error_value = EV_NO_ERROR;

  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  number res1 = arithEvaluate(pval1, heap, error_value);
   
  if (error_value != EV_NO_ERROR)
    {
      PSI_ERROR_RETURN(error_value, 1);
    }

  //
  // The arg evaluate correctly so decrement it
  //
  if IS_INT(res1)
    {
      object2 = heap.newInteger(GET_INT_VAL(res1) - 1);
     }
   else
     {
      object2 = heap.newDouble(GET_DOUBLE_VAL(res1) - 1);
     }

  return RV_SUCCESS;
}

//
// psi_hash_double(A,B)
// mode(in,out)
// 
//
Thread::ReturnValue
Thread::psi_hash_double(Object *& object1, Object *& object2)
{
  PrologValue pval1(object1);
  heap.prologValueDereference(pval1);
  Object* dval = pval1.getTerm();

  if (!dval->isDouble())
    {
      PSI_ERROR_RETURN(EV_TYPE, 1);
    }

  double d = dval->getDouble();
  u_int x[2];
  memcpy(x, &d, sizeof(double));
  word32 v = (x[0] | x[1]) & ~(x[0] & x[1]);
  object2 = heap.newInteger(v);
  return RV_SUCCESS;
}
