/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.es;

import java.util.Random;

/**
 * JavaScript object
 */
class NativeMath extends Native {
  static final int ABS = 1;
  static final int ACOS = 2;
  static final int ASIN = 3;
  static final int ATAN = 4;
  static final int ATAN2 = 5;
  static final int CEIL = 6;
  static final int COS = 7;
  static final int EXP = 8;
  static final int FLOOR = 9;
  static final int LOG = 10;
  static final int MAX = 11;
  static final int MIN = 12;
  static final int POW = 13;
  static final int RANDOM = 14;
  static final int ROUND = 15;
  static final int SET_SEED = 16;
  static final int SIN = 17;
  static final int SQRT = 18;
  static final int TAN = 19;

  Random random; // XXX: s/b in global?

  /**
   * Create a new object based on a prototype
   */
  private NativeMath(String name, int n, int len)
  {
    super(name, len);

    this.n = n;
  }
  
  /**
   * Creates the native Object object
   */
  static ESObject create(Global resin)
  {
    ESObject math = new ESObject("Math", null);

    put(math, "abs", ABS, 1);
    put(math, "asin", ASIN, 1);
    put(math, "acos", ACOS, 1);
    put(math, "atan", ATAN, 1);
    put(math, "atan2", ATAN2, 2);
    put(math, "ceil", CEIL, 1);
    put(math, "cos", COS, 1);
    put(math, "exp", EXP, 1);
    put(math, "floor", FLOOR, 1);
    put(math, "log", LOG, 1);
    put(math, "max", MAX, 2);
    put(math, "min", MIN, 2);
    put(math, "pow", POW, 2);
    put(math, "round", ROUND, 1);
    put(math, "sin", SIN, 1);
    put(math, "sqrt", SQRT, 1);
    put(math, "tan", TAN, 1);
    put(math, "random", RANDOM, 0);
    put(math, "setSeed", SET_SEED, 1);

    int flags = DONT_ENUM|DONT_DELETE|READ_ONLY;
    math.put("E", ESNumber.create(Math.E), flags);
    math.put("LN10", ESNumber.create(Math.log(10.0)), flags);
    math.put("LN2", ESNumber.create(Math.log(2.0)), flags);
    math.put("LOG2E", ESNumber.create(1.0 / Math.log(2.0)), flags);
    math.put("LOG10E", ESNumber.create(1.0 / Math.log(10.0)), flags);
    math.put("PI", ESNumber.create(Math.PI), flags);
    math.put("SQRT1_2", ESNumber.create(Math.sqrt(0.5)), flags);
    math.put("SQRT2", ESNumber.create(Math.sqrt(2.0)), flags);
    // XXX: potential problems.
    Random random = new Random();
    try {
      ((NativeMath) math.getProperty("random")).random = random;
      ((NativeMath) math.getProperty("setSeed")).random = random;
    } catch (Throwable e) {
    }

    math.setClean();

    return math;
  }

  private static void put(ESObject obj, String name, int n, int length)
  {
    obj.put(name, new NativeMath(name, n, length), DONT_ENUM);
  }

  public ESBase call(Call eval, int length) throws Throwable
  {
    double arg;
    double value;

    switch (n) {
    case ABS:
      if (length == 0)
	return esUndefined;
      arg = eval.getArg(0).toNum();
      return ESNumber.create(arg == 0 ? 0 : (arg < 0 ? -arg : arg));

    case ACOS:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.acos(eval.getArg(0).toNum()));

    case ASIN:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.asin(eval.getArg(0).toNum()));

    case ATAN:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.atan(eval.getArg(0).toNum()));

    case ATAN2:
      if (length < 2)
	return esUndefined;
      return ESNumber.create(Math.atan2(eval.getArg(0).toNum(),
					eval.getArg(1).toNum()));

    case CEIL:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.ceil(eval.getArg(0).toNum()));

    case COS:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.cos(eval.getArg(0).toNum()));

    case EXP:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.exp(eval.getArg(0).toNum()));

    case FLOOR:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.floor(eval.getArg(0).toNum()));

    case LOG:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.log(eval.getArg(0).toNum()));

    case MAX:
      arg = -1.0/0.0;
      for (int i = 0; i < length; i++) {
	double v = eval.getArg(i).toNum();

	arg = Math.max(v, arg);
      }
      return ESNumber.create(arg);

    case MIN:
      arg = 1.0/0.0;
      for (int i = 0; i < length; i++) {
	double v = eval.getArg(i).toNum();

	arg = Math.min(v, arg);
      }
      return ESNumber.create(arg);

    case POW:
      if (length < 2)
	return esUndefined;
      return ESNumber.create(Math.pow(eval.getArg(0).toNum(),
				      eval.getArg(1).toNum()));

    case RANDOM:
      {
	if (length > 1) {
	  int n = eval.getArg(0).toInt32();
	  if (n > 0)
	    return ESNumber.create((int) (random.nextDouble() * n));
	} else {
	  return ESNumber.create(random.nextDouble());
	}
      }

    case ROUND:
      if (length == 0)
	return esUndefined;

      arg = eval.getArg(0).toNum();
      if (arg >= -0.5 && arg < 0.5)
	return ESNumber.create(Math.rint(arg));
      else
	return ESNumber.create(Math.floor(arg + 0.5));

    case SET_SEED:
      if (length != 0)
	random.setSeed((long) eval.getArg(0).toNum());
      return esUndefined;

    case SIN:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.sin(eval.getArg(0).toNum()));

    case SQRT:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.sqrt(eval.getArg(0).toNum()));

    case TAN:
      if (length == 0)
	return esUndefined;
      return ESNumber.create(Math.tan(eval.getArg(0).toNum()));

    default:
      throw new ESException("Undefined math function");
    }
  }
}
