/FileExtraction/Java_unseen/dlitz_resin/modules/quercus/src/com/caucho/quercus/lib/MathModule.java
/*
 * Copyright (c) 1998-2011 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 Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.quercus.lib;

import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.*;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.quercus.program.JavaClassDef;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;

import java.util.Iterator;
import java.math.BigInteger;

/**
 * PHP math routines.
 */
public class MathModule extends AbstractQuercusModule {
  private static final L10N L = new L10N(MathModule.class);

  public static final double M_PI = Math.PI;
  public static final double M_E = Math.E;

  public static final long RAND_MAX = Integer.MAX_VALUE;

  public static final double M_LOG2E = log2(Math.E);
  public static final double M_LOG10E = Math.log10(Math.E);
  public static final double M_LN2 = Math.log(2);
  public static final double M_LN10 = Math.log(10);
  public static final double M_PI_2 = Math.PI / 2;
  public static final double M_PI_4 = Math.PI / 4;
  public static final double M_1_PI = 1 / Math.PI;
  public static final double M_2_PI = 2 / Math.PI;
  public static final double M_SQRTPI = Math.sqrt(Math.PI);
  public static final double M_2_SQRTPI = 2 / Math.sqrt(Math.PI);
  public static final double M_SQRT2 = Math.sqrt(2);
  public static final double M_SQRT3 = Math.sqrt(3);
  public static final double M_SQRT1_2 = 1 / Math.sqrt(2);
  public static final double M_LNPI = Math.log(Math.PI);
  public static final double M_EULER = 0.57721566490153286061;

  private static double log2(double v)
  {
    return Math.log(v) / Math.log(2);
  }

  public static Value abs(Value value)
  {
    return value.abs();
  }

  public static double acos(double value)
  {
    return Math.acos(value);
  }

  public static double acosh(Env env, double value)
  {
    return Math.log(value + Math.sqrt(value * value - 1));
  }

  public static Value asin(Value value)
  {
    return new DoubleValue(Math.asin(value.toDouble()));
  }

  public static double asinh(double value)
  {
    return Math.log(value + Math.sqrt(value * value + 1));
  }

  public static double atan2(double yV, double xV)
  {
    return Math.atan2(yV, xV);
  }

  public static double atan(double value)
  {
    return Math.atan(value);
  }

  public static double atanh(double value)
  {
    return 0.5 * Math.log((1 + value) / (1 - value));
  }

  /**
   * Convert a number between arbitrary bases
   *
   * @param number A string represeantion of an binary number.
   * @param fromBase The base of the number parameter.
   * @param toBase The base of convert to.
   * @return the number as a Value, either a LongValue or a DoubleValue.
   */
  public static Value base_convert(Env env,
                                   StringValue str,
                                   int fromBase,
                                   int toBase)
  {
    if (fromBase < 2 || fromBase > 36) {
      env.warning(L.l("invalid `{0}' ({1})", "from base", fromBase));
      return BooleanValue.FALSE;
    }

    if (toBase < 2 || toBase > 36) {
      env.warning(L.l("invalid `{0}' ({1})", "to base", toBase));
      return BooleanValue.FALSE;
    }
    
    Number num = baseToInt(env, str, fromBase);
    
    if (num instanceof BigInteger)
      return intToBase(env, (BigInteger) num, toBase);
    else
      return intToBase(env, num.longValue(), toBase);
  }
  
  private static Number baseToInt(Env env, StringValue str, int base)
  {
    long result = 0L;

    boolean isLong = true;
    
    int len = str.length();
    
    for (int i = 0; i < len; i++) {
      int ch = str.charAt(i);
      
      int d;
      
      if ('0' <= ch && ch <= '9')
        d = ch - '0';
      else if ('a' <= ch && ch <= 'z')
        d = ch - 'a' + 10;
      else if ('A' <= ch && ch <= 'Z')
        d = ch - 'A' + 10;
      else
        continue;
      
      if (base <= d)
        continue;
      
      if (result * base + d < result) {
        isLong = false;
        break;
      }
      
      result = result * base + d;
    }
    
    if (isLong)
      return Long.valueOf(result);
    else
      return new BigInteger(str.toString(), base);
  }
  
  private static StringValue intToBase(Env env, long num, int base)
  {
    if (num == 0)
      return env.createString((char) '0');
    
    // ignore sign
    if (num < 0)
      num = num ^ Long.MAX_VALUE + 1;
    
    int bufLen = 64;
    char []buffer = new char[bufLen];
    
    int i = bufLen;
    while (num != 0 && i > 0) {
      int d = (int) (num % base);

      if (d < 10)
        buffer[--i] = (char) (d + '0');
      else
        buffer[--i] = (char) (d + 'a' - 10);

      num = num / base;
    }
    
    for (int j = i; j < bufLen; j++) {
      buffer[j - i] = buffer[j];
    }

    return env.createString(buffer, bufLen - i);
  }
  
  private static StringValue intToBase(Env env, BigInteger num, int base)
  {
    BigInteger toBaseBig = BigInteger.valueOf(base);
    BigInteger zero = BigInteger.valueOf(0);
    
    StringValue sb = env.createStringBuilder();
    
    do {
      BigInteger []resultArray = num.divideAndRemainder(toBaseBig);
      
      num = resultArray[0];
      int d = resultArray[1].intValue();
      
      if (d < 10)
        sb.append((char) (d + '0'));
      else
        sb.append((char) (d + 'a' - 10));
      
    } while (num.compareTo(zero) != 0);

    StringValue toReturn = env.createStringBuilder();
    
    int len = sb.length();
    for (int i = len - 1; i >= 0; i--) {
      toReturn.append(sb.charAt(i));
    }

    return toReturn;
  }

  /**
   * Returns the decimal equivalent of the binary number represented by the
   * binary string argument.
   *
   * @param bin A string representation of an binary number.
   * @return the decimal equivalent of the binary number
   */
  public static Value bindec(Env env, StringValue bin)
  {
     Number num = baseToInt(env, bin, 2);
     
     if (num instanceof Long)
       return LongValue.create(num.longValue());
     else
       return env.wrapJava(num);
  }

  public static double ceil(double value)
  {
    return Math.ceil(value);
  }

  public static double cos(double value)
  {
    return Math.cos(value);
  }

  public static double cosh(double value)
  {
    return Math.cosh(value);
  }

  /**
   * Returns a binary representation of a number.
   * @param value the number
   */
  public static StringValue decbin(Env env, long value)
  {
    return intToBase(env, value, 2);
    
    /*
    value = value & 037777777777L;
    
    if (value == 0)
      return env.createString("0");
    
    char []buffer = new char[32];

    int i = 32;
    while (value != 0 && i >= 0) {
      int d = (int) (value & 0x01);
      
      value = value >>> 1;
      
      buffer[--i] = (char) (d + '0');
    }

    for (int j = i; j < 32; j++) {
      buffer[j - i] = buffer[j];
    }

    return env.createString(buffer, 32 - i);
    */
  }

  /**
   * Returns a hexadecimal representation of a number.
   * @param value the number
   */
  public static StringValue dechex(Env env, long value)
  {
    return intToBase(env, value, 16);
    
    /*
    value = value & 037777777777L;
    
    if (value == 0)
      return env.createString("0");
    
    char []buffer = new char[16];

    int i = 16;
    while (value != 0) {
      int d = (int) (value & 0xf);
      
      value = value >>> 4;
      
      if (d < 10)
        buffer[--i] = (char) (d + '0');
      else
        buffer[--i] = (char) (d + 'a' - 10);
    }

    for (int j = i; j < 16; j++) {
      buffer[j - i] = buffer[j];
    }

    return env.createString(buffer, 16 - i);
    */
  }

  /**
   * Returns an octal representation of a number.
   * @param value the number
   */
  public static StringValue decoct(Env env, long value)
  {
    return intToBase(env, value, 8);
    
    /*
    value = value & 037777777777L;
    
    if (value == 0)
      return env.createString("0");
    
    char []buffer = new char[11];

    int i = 11;
    while (value != 0 && i > 0) {
      int d = (int) (value & 0x7);
      
      value = value >>> 3;
      
      buffer[--i] = (char) (d + '0');
    }

    for (int j = i; j < 11; j++) {
      buffer[j - i] = buffer[j];
    }

    return env.createString(buffer, 11 - i);
    */
  }

  public static double deg2rad(double value)
  {
    return value * Math.PI / 180;
  }

  public static Value exp(Value value)
  {
    return new DoubleValue(Math.exp(value.toDouble()));
  }

  public static Value expm1(Value value)
  {
    return new DoubleValue(Math.expm1(value.toDouble()));
  }

  public static Value floor(Value value)
  {
    return new DoubleValue(Math.floor(value.toDouble()));
  }

  public static double fmod(double xV, double yV)
  {
    return Math.IEEEremainder(xV, yV);
  }

  public static Value hexdec(Env env, StringValue s)
  {
    Number num = baseToInt(env, s, 16);
    
    if (num instanceof Long)
      return LongValue.create(num.longValue());
    else
      return env.wrapJava(num);
  }

  public static double hypot(double a, double b)
  {
    return Math.hypot(a, b);
  }

  public static boolean is_finite(Value value)
  {
    if (value instanceof LongValue)
      return true;
    else if (value instanceof DoubleValue) {
      double v = value.toDouble();

      return ! Double.isInfinite(v);
    }
    else
      return false;
  }

  public static Value is_infinite(Value value)
  {
    if (value instanceof LongValue)
      return BooleanValue.FALSE;
    else if (value instanceof DoubleValue) {
      double v = value.toDouble();

      return Double.isInfinite(v) ? BooleanValue.TRUE : BooleanValue.FALSE;
    }
    else
      return BooleanValue.FALSE;
  }

  public static Value is_nan(Value value)
  {
    if (value instanceof LongValue)
      return BooleanValue.FALSE;
    else if (value instanceof DoubleValue) {
      double v = value.toDouble();

      return Double.isNaN(v) ? BooleanValue.TRUE : BooleanValue.FALSE;
    }
    else
      return BooleanValue.FALSE;
  }

  public static double log(double value)
  {
    return Math.log(value);
  }

  public static double log10(double value)
  {
    return Math.log10(value);
  }

  public static double log1p(double value)
  {
    return Math.log1p(value);
  }

  public static Value getrandmax()
  {
    return mt_getrandmax();
  }

  public static Value max(Env env, Value []args)
  {
    if (args.length == 1 && args[0] instanceof ArrayValue) {
      Value array = args[0];
      Value max = NullValue.NULL;
      double maxValue = Double.MIN_VALUE;

      Iterator<Value> iter = array.getValueIterator(env);

      while (iter.hasNext()) {
        Value value = iter.next();

        double dValue = value.toDouble();

        if (maxValue < dValue) {
          maxValue = dValue;
          max = value;
        }
      }

      return max;
    }
    else {
      double maxValue = - Double.MAX_VALUE;
      Value max = NullValue.NULL;

      for (int i = 0; i < args.length; i++) {
        double value = args[i].toDouble();

        if (maxValue < value) {
          maxValue = value;
          max = args[i];
        }
      }

      return max;
    }
  }

  public static Value min(Env env, Value []args)
  {
    if (args.length == 1 && args[0] instanceof ArrayValue) {
      Value array = args[0];
      Value min = NullValue.NULL;
      double minValue = Double.MAX_VALUE;

      Iterator<Value> iter = array.getValueIterator(env);

      while (iter.hasNext()) {
        Value value = iter.next();

        double dValue = value.toDouble();

        if (dValue < minValue) {
          minValue = dValue;
          min = value;
        }
      }

      return min;
    }
    else {
      double minValue = Double.MAX_VALUE;
      Value min = NullValue.NULL;

      for (int i = 0; i < args.length; i++) {
        double value = args[i].toDouble();

        if (value < minValue) {
          minValue = value;
          min = args[i];
        }
      }

      return min;
    }
  }

  public static Value mt_getrandmax()
  {
    return new LongValue(RAND_MAX);
  }

  public static long mt_rand(@Optional("0") long min,
                             @Optional("RAND_MAX") long max)
  {
    long range = max - min + 1;

    if (range <= 0)
      return min;

    long value = RandomUtil.getRandomLong();
    if (value < 0)
      value = - value;

    return min + value % range;
  }

  public static Value mt_srand(@Optional long seed)
  {
    return NullValue.NULL;
  }
  
  public static double lcg_value()
  {
    return RandomUtil.nextDouble();
  }

  /**
   *  Returns the decimal equivalent of the octal number represented by the
   *  octal_string argument.
   *
   * @param oct A string represeantion of an octal number.
   * @return the decimal equivalent of the octal number
   */
  public static Value octdec(Env env, StringValue oct)
  {
    Number num = baseToInt(env, oct, 8);
    
    if (num instanceof Long)
      return LongValue.create(num.longValue());
    else
      return env.wrapJava(num);
  }

  public static double pi()
  {
    return M_PI;
  }

  /*
   * XXX: may return an integer if it can be represented
   *      by a system-dependent integer
   */
  public static double pow(double base, double exp)
  {
    return Math.pow(base, exp);
  }

  public static double rad2deg(double value)
  {
    return 180 * value / Math.PI;
  }

  public static long rand(@Optional int min,
                          @Optional("RAND_MAX") int max)
  {
    return mt_rand(min, max);
  }

  public static double round(double value, @Optional int precision)
  {
    if (precision > 0) {
      double exp = Math.pow(10, precision);

      return Math.round(value * exp) / exp;
    }
    else
      return Math.round(value);
  }

  public static double sin(double value)
  {
    return Math.sin(value);
  }

  public static Value sinh(Value value)
  {
    return new DoubleValue(Math.sinh(value.toDouble()));
  }

  public static double sqrt(double value)
  {
    return Math.sqrt(value);
  }

  public static Value srand(@Optional long seed)
  {
    return NullValue.NULL;
  }

  public static double tan(double value)
  {
    return Math.tan(value);
  }

  public static double tanh(double value)
  {
    return Math.tanh(value);
  }
}
