/*
 * The builtin Math object.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /home/kde/kdesupport/js/b_math.c,v $
 * $Id: b_math.c,v 1.1.1.1 1998/08/09 21:05:34 kulow Exp $
 */

#include "jsint.h"
#include "rentrant.h"

/*
 * Types and definitions.
 */

#ifndef M_E
#define M_E		2.71828182845904523536028747135266250
#endif

#ifndef M_LN10
#define M_LN10		2.302585092994046
#endif

#ifndef M_LN2
#define M_LN2		0.693147180559945309417232121458176568
#endif

#ifndef M_LOG10E
#define M_LOG10E	0.434294481903251827651128918916605082
#endif

#ifndef M_LOG2E
#define M_LOG2E		1.44269504088896340735992468100189214
#endif

#ifndef M_PI
#define M_PI		3.14159265358979323846264338327950288
#endif

#ifndef M_SQRT1_2
#define M_SQRT1_2	0.707106781186547524400844362104849039
#endif

#ifndef M_SQRT2
#define M_SQRT2		1.41421356237309504880168872420969808
#endif


#define ONE_ARG()			\
  do {					\
    if (args->u.vinteger != 1)		\
      goto argument_error;		\
					\
    if (args[1].type == JS_INTEGER)	\
      d = (double) args[1].u.vinteger;	\
    else if (args[1].type == JS_FLOAT)	\
      d = args[1].u.vfloat;		\
    else if (args[1].type == JS_NAN)	\
      {					\
	result_return->type = JS_NAN;	\
	goto done;			\
      }					\
    else				\
      goto argument_type_error;		\
					\
  } while (0)

#define TWO_ARGS()			\
  do {					\
    if (args->u.vinteger != 2)		\
      goto argument_error;		\
					\
    if (args[1].type == JS_INTEGER)	\
      d = (double) args[1].u.vinteger;	\
    else if (args[1].type == JS_FLOAT)	\
      d = args[1].u.vfloat;		\
    else if (args[1].type == JS_NAN)	\
      {					\
	result_return->type = JS_NAN;	\
	goto done;			\
      }					\
    else				\
      goto argument_type_error;		\
					\
    if (args[2].type == JS_INTEGER)	\
      d2 = (double) args[1].u.vinteger; \
    else if (args[2].type == JS_FLOAT)	\
      d2 = args[2].u.vfloat;		\
    else if (args[2].type == JS_NAN)	\
      {					\
	result_return->type = JS_NAN;	\
	goto done;			\
      }					\
    else				\
      goto argument_type_error;		\
					\
  } while (0)

/* Class context. */
struct math_ctx_st
{
  JSSymbol s_abs;
  JSSymbol s_acos;
  JSSymbol s_asin;
  JSSymbol s_atan;
  JSSymbol s_atan2;
  JSSymbol s_ceil;
  JSSymbol s_cos;
  JSSymbol s_exp;
  JSSymbol s_floor;
  JSSymbol s_log;
  JSSymbol s_max;
  JSSymbol s_min;
  JSSymbol s_pow;
  JSSymbol s_random;
  JSSymbol s_round;
  JSSymbol s_seed;
  JSSymbol s_sin;
  JSSymbol s_sqrt;
  JSSymbol s_tan;

  JSSymbol s_E;
  JSSymbol s_LN10;
  JSSymbol s_LN2;
  JSSymbol s_LOG10E;
  JSSymbol s_LOG2E;
  JSSymbol s_PI;
  JSSymbol s_SQRT1_2;
  JSSymbol s_SQRT2;

  void *drand48_context;
};

typedef struct math_ctx_st MathCtx;

/*
 * Static functions.
 */

/* Method proc. */
static int
method (JSVirtualMachine *vm, JSBuiltinInfo *builtin_info,
	void *instance_context, JSSymbol method, JSNode *result_return,
	JSNode *args)
{
  MathCtx *ctx = builtin_info->obj_context;
  double d, d2;

  /* The default return value. */
  result_return->type = JS_FLOAT;

  if (method == ctx->s_abs)
    {
      ONE_ARG ();
      result_return->u.vfloat = fabs (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_acos)
    {
      ONE_ARG ();
      result_return->u.vfloat = acos (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_asin)
    {
      ONE_ARG ();
      result_return->u.vfloat = asin (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_atan)
    {
      ONE_ARG ();
      result_return->u.vfloat = atan (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_atan2)
    {
      TWO_ARGS ();
      result_return->u.vfloat = atan2 (d, d2);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_ceil)
    {
      ONE_ARG ();
      result_return->u.vfloat = ceil (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_cos)
    {
      ONE_ARG ();
      result_return->u.vfloat = cos (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_exp)
    {
      ONE_ARG ();
      result_return->u.vfloat = exp (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_floor)
    {
      ONE_ARG ();
      result_return->u.vfloat = floor (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_log)
    {
      ONE_ARG ();
      result_return->u.vfloat = log (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_max)
    {
      if (args->u.vinteger != 2)
	goto argument_error;

      if (args[1].type == JS_INTEGER)
	{
	  if (args[2].type == JS_INTEGER)
	    {
	      result_return->type = JS_INTEGER;
	      result_return->u.vinteger
		= (args[1].u.vinteger > args[2].u.vinteger
		   ? args[1].u.vinteger : args[2].u.vinteger);
	    }
	  else if (args[2].type == JS_FLOAT)
	    {
	      d = (double) args[1].u.vinteger;
	      result_return->u.vfloat = (d > args[2].u.vfloat
					 ? d : args[2].u.vfloat);
	    }
	  else if (args[2].type == JS_NAN)
	    result_return->type = JS_NAN;
	  else
	    goto argument_type_error;
	}
      else if (args[1].type == JS_FLOAT)
	{
	  if (args[2].type == JS_INTEGER)
	    {
	      d = (double) args[2].u.vinteger;
	      result_return->u.vfloat = (args[1].u.vfloat > d
					 ? args[1].u.vfloat : d);
	    }
	  else if (args[2].type == JS_FLOAT)
	    {
	      result_return->u.vfloat
		= (args[1].u.vfloat > args[2].u.vfloat
		   ? args[1].u.vfloat : args[2].u.vfloat);
	    }
	  else if (args[2].type == JS_NAN)
	    result_return->type = JS_NAN;
	  else
	    goto argument_type_error;
	}
      else if (args[1].type == JS_NAN)
	result_return->type = JS_NAN;
      else
	goto argument_type_error;
    }
  /* ********************************************************************** */
  else if (method == ctx->s_min)
    {
      if (args->u.vinteger != 2)
	goto argument_error;

      if (args[1].type == JS_INTEGER)
	{
	  if (args[2].type == JS_INTEGER)
	    {
	      result_return->type = JS_INTEGER;
	      result_return->u.vinteger
		= (args[1].u.vinteger < args[2].u.vinteger
		   ? args[1].u.vinteger : args[2].u.vinteger);
	    }
	  else if (args[2].type == JS_FLOAT)
	    {
	      d = (double) args[1].u.vinteger;
	      result_return->u.vfloat = (d < args[2].u.vfloat
					 ? d : args[2].u.vfloat);
	    }
	  else if (args[2].type == JS_NAN)
	    result_return->type = JS_NAN;
	  else
	    goto argument_type_error;
	}
      else if (args[1].type == JS_FLOAT)
	{
	  if (args[2].type == JS_INTEGER)
	    {
	      d = (double) args[2].u.vinteger;
	      result_return->u.vfloat = (args[1].u.vfloat < d
					 ? args[1].u.vfloat : d);
	    }
	  else if (args[2].type == JS_FLOAT)
	    {
	      result_return->u.vfloat
		= (args[1].u.vfloat < args[2].u.vfloat
		   ? args[1].u.vfloat : args[2].u.vfloat);
	    }
	  else if (args[2].type == JS_NAN)
	    result_return->type = JS_NAN;
	  else
	    goto argument_type_error;
	}
      else if (args[1].type == JS_NAN)
	result_return->type = JS_NAN;
      else
	goto argument_type_error;
    }
  /* ********************************************************************** */
  else if (method == ctx->s_pow)
    {
      TWO_ARGS ();
      result_return->u.vfloat = pow (d, d2);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_random)
    {
      if (args->u.vinteger != 0)
	goto argument_error;

      /* js_srand48 (ctx->drand48_context, time (NULL)); */
      js_drand48 (ctx->drand48_context, &result_return->u.vfloat);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_round)
    {
      ONE_ARG ();
      result_return->type = JS_INTEGER;
      result_return->u.vinteger = (long) (d + 0.5);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_seed)
    {
      ONE_ARG ();
      js_srand48 (ctx->drand48_context, (long) d);
      result_return->type = JS_UNDEFINED;
    }
  /* ********************************************************************** */
  else if (method == ctx->s_sin)
    {
      ONE_ARG ();
      result_return->u.vfloat = sin (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_sqrt)
    {
      ONE_ARG ();
      result_return->u.vfloat = sqrt (d);
    }
  /* ********************************************************************** */
  else if (method == ctx->s_tan)
    {
      ONE_ARG ();
      result_return->u.vfloat = tan (d);
    }
  /* ********************************************************************** */
  else if (method == vm->syms.s_toString)
    {
      js_vm_make_static_string (vm, result_return, "Math", 4);
    }
  /* ********************************************************************** */
  else
    return JS_PROPERTY_UNKNOWN;

 done:

  return JS_PROPERTY_FOUND;


  /*
   * Error handling.
   */

 argument_error:
  sprintf (vm->error, "Math.%s(): illegal amount of arguments",
	   js_vm_symname (vm, method));
  js_vm_error (vm);

 argument_type_error:
  sprintf (vm->error, "Math.%s(): illegal argument",
	   js_vm_symname (vm, method));
  js_vm_error (vm);

  /* NOTREACHED. */
  return 0;
}

/* Property proc. */
static int
property (JSVirtualMachine *vm, JSBuiltinInfo *builtin_info,
	  void *instance_context, JSSymbol property, int set, JSNode *node)
{
  MathCtx *ctx = builtin_info->obj_context;

  /* The default result is float. */
  node->type = JS_FLOAT;

  if (property == ctx->s_E)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_E;
    }
  else if (property == ctx->s_LN10)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_LN10;
    }
  else if (property == ctx->s_LN2)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_LN2;
    }
  else if (property == ctx->s_LOG10E)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_LOG10E;
    }
  else if (property == ctx->s_LOG2E)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_LOG2E;
    }
  else if (property == ctx->s_PI)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_PI;
    }
  else if (property == ctx->s_SQRT1_2)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_SQRT1_2;
    }
  else if (property == ctx->s_SQRT2)
    {
      if (set)
	goto immutable;

      node->u.vfloat = M_SQRT2;
    }
  else
    {
      if (!set)
	node->type = JS_UNDEFINED;

      return JS_PROPERTY_UNKNOWN;
    }

  return JS_PROPERTY_FOUND;


  /*
   * Error handling.
   */

 immutable:
  sprintf (vm->error, "Math.%s: immutable property",
	   js_vm_symname (vm, property));
  js_vm_error (vm);

  /* NOTREACHED. */
  return 0;
}


/*
 * Global functions.
 */

void
js_builtin_Math (JSVirtualMachine *vm)
{
  JSBuiltinInfo *info;
  MathCtx *ctx;
  JSNode *n;

  ctx = js_calloc (vm, 1, sizeof (*ctx));

  ctx->s_abs		= js_vm_intern (vm, "abs");
  ctx->s_acos		= js_vm_intern (vm, "acos");
  ctx->s_asin		= js_vm_intern (vm, "asin");
  ctx->s_atan		= js_vm_intern (vm, "atan");
  ctx->s_atan2		= js_vm_intern (vm, "atan2");
  ctx->s_ceil		= js_vm_intern (vm, "ceil");
  ctx->s_cos		= js_vm_intern (vm, "cos");
  ctx->s_exp		= js_vm_intern (vm, "exp");
  ctx->s_floor		= js_vm_intern (vm, "floor");
  ctx->s_log		= js_vm_intern (vm, "log");
  ctx->s_max		= js_vm_intern (vm, "max");
  ctx->s_min		= js_vm_intern (vm, "min");
  ctx->s_pow		= js_vm_intern (vm, "pow");
  ctx->s_random		= js_vm_intern (vm, "random");
  ctx->s_round		= js_vm_intern (vm, "round");
  ctx->s_seed		= js_vm_intern (vm, "seed");
  ctx->s_sin		= js_vm_intern (vm, "sin");
  ctx->s_sqrt		= js_vm_intern (vm, "sqrt");
  ctx->s_tan		= js_vm_intern (vm, "tan");

  ctx->s_E		= js_vm_intern (vm, "E");
  ctx->s_LN10		= js_vm_intern (vm, "LN10");
  ctx->s_LN2		= js_vm_intern (vm, "LN2");
  ctx->s_LOG10E		= js_vm_intern (vm, "LOG10E");
  ctx->s_LOG2E		= js_vm_intern (vm, "LOG2E");
  ctx->s_PI		= js_vm_intern (vm, "PI");
  ctx->s_SQRT1_2	= js_vm_intern (vm, "SQRT1_2");
  ctx->s_SQRT2		= js_vm_intern (vm, "SQRT2");

  ctx->drand48_context = js_drand48_create (vm);

  /* Object information. */

  info = js_vm_builtin_info_create (vm);

  info->method_proc		= method;
  info->property_proc		= property;
  info->obj_context		= ctx;
  info->obj_context_delete	= js_free;

  /* Define it. */
  n = &vm->globals[js_vm_intern (vm, "Math")];
  js_vm_builtin_create (vm, n, info, NULL);
}
