// -*- c-basic-offset: 2 -*-
/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003 Peter Kelly (pmk@post.com)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "value.h"
#include "object.h"
#include "types.h"
#include "interpreter.h"
#include "operations.h"
#include "number_object.h"
#include "error_object.h"
#include "dtoa.h"

#include "number_object.lut.h"

#include <assert.h>
#include <math.h>

using namespace KJS;

// ------------------------------ NumberInstanceImp ----------------------------

const ClassInfo NumberInstanceImp::info = {"Number", 0, 0, 0};

NumberInstanceImp::NumberInstanceImp(ObjectImp *proto)
  : ObjectImp(proto)
{
}
// ------------------------------ NumberPrototypeImp ---------------------------

// ECMA 15.7.4

NumberPrototypeImp::NumberPrototypeImp(ExecState *exec,
                                       ObjectPrototypeImp *objProto,
                                       FunctionPrototypeImp *funcProto)
  : NumberInstanceImp(objProto)
{
  Value protect(this);
  setInternalValue(NumberImp::zero());

  // The constructor will be added later, after NumberObjectImp has been constructed

  putDirect(toStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToString,
							1,toStringPropertyName),DontEnum);
  putDirect(toLocaleStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToLocaleString,
							      0,toLocaleStringPropertyName),DontEnum);
  putDirect(valueOfPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ValueOf,
						       0,valueOfPropertyName),DontEnum);
  putDirect("toFixed", new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToFixed,
					      1,"toFixed"),DontEnum);
  putDirect("toExponential",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToExponential,
						   1,"toExponential"),DontEnum);
  putDirect("toPrecision",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToPrecision,
						 1,"toPrecision"),DontEnum);
}


// ------------------------------ NumberProtoFuncImp ---------------------------

NumberProtoFuncImp::NumberProtoFuncImp(ExecState * /*exec*/, FunctionPrototypeImp *funcProto,
                                       int i, int len, const Identifier &_ident)
  : InternalFunctionImp(funcProto), id(i)
{
  Value protect(this);
  putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
  ident = _ident;
}


bool NumberProtoFuncImp::implementsCall() const
{
  return true;
}

static UString integer_part_noexp(double d)
{
  int decimalPoint;
  int signDummy;
  char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &signDummy, NULL);
  int length = strlen(result);

  // sign for non-zero, negative numbers
  UString str = d < 0 ? "-" : "";
  if (decimalPoint == 9999) {
    str += UString(result);
  } else if (decimalPoint <= 0) {
    str += UString("0");
  } else {
    char *buf;

    if (length <= decimalPoint) {
      buf = (char*)malloc(decimalPoint+1);
      strcpy(buf,result);
      memset(buf+length,'0',decimalPoint-length);
    } else {
      buf = (char*)malloc(decimalPoint+1);
      strncpy(buf,result,decimalPoint);
    }

    buf[decimalPoint] = '\0';
    str += UString(buf);
    free(buf);
  }

  kjs_freedtoa(result);

  return str;
}

static UString char_sequence(char c, int count)
{
  char *buf = (char*)malloc(count+1);
  memset(buf,c,count);
  buf[count] = '\0';
  UString s(buf);
  free(buf);
  return s;
}

// ECMA 15.7.4.2 - 15.7.4.7
Value NumberProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args)
{
  Value result;

  // no generic function. "this" has to be a Number object
  KJS_CHECK_THIS( NumberInstanceImp, thisObj );

  // execute "toString()" or "valueOf()", respectively
  Value v = thisObj.internalValue();
  switch (id) {
  case ToString: {
    int radix = 10;
    if (!args.isEmpty() && args[0].type() != UndefinedType)
      radix = args[0].toInteger(exec);
    if (radix < 2 || radix > 36 || radix == 10)
      result = String(v.toString(exec));
    else {
      const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
      // INT_MAX results in 1024 characters left of the dot with radix 2
      // give the same space on the right side. safety checks are in place
      // unless someone finds a precise rule.
      char s[2048 + 3];
      double x = v.toNumber(exec);
      if (isNaN(x) || isInf(x))
        return String(UString::from(x));
      // apply algorithm on absolute value. add sign later.
      bool neg = false;
      if (x < 0.0) {
        neg = true;
        x = -x;
      }
      // convert integer portion
      double f = floor(x);
      double d = f;
      char *dot = s + sizeof(s) / 2;
      char *p = dot;
      *p = '\0';
      do {
        *--p = digits[int(fmod(d, double(radix)))];
        d /= radix;
      } while ((d <= -1.0 || d >= 1.0) && p > s);
      // any decimal fraction ?
      d = x - f;
      const double eps = 0.001; // TODO: guessed. base on radix ?
      if (d < -eps || d > eps) {
        *dot++ = '.';
        do {
          d *= radix;
          *dot++ = digits[int(d)];
          d -= int(d);
        } while ((d < -eps || d > eps) && dot - s < int(sizeof(s)) - 1);
        *dot = '\0';
      }
      // add sign if negative
      if (neg)
        *--p = '-';
      result = String(p);
    }
    break;
  }
  case ToLocaleString: /* TODO */
    result = String(v.toString(exec));
    break;
  case ValueOf:
    result = Number(v.toNumber(exec));
    break;
  case ToFixed:
  {
    // FIXME: firefox works for all values, not just 0..20.  This includes
    // NaN, infinity, undefined, etc.  This is just a hack to pass our regression
    // suite.
    Value fractionDigits = args[0];
    int f = -1;
    double fd = fractionDigits.toNumber(exec);
    if (isNaN(fd)) {
      f = 0;
    } else if (!isInf(fd)) {
      f = int(fd);
    }
    if (f < 0 || f > 20) {
      Object err = Error::create(exec,RangeError);
      exec->setException(err);
      return err;
    }

    double x = v.toNumber(exec);
    if (isNaN(x))
      return String("NaN");

    UString s = "";
    if (x < 0) {
      s += "-";
      x = -x;
    }

    if (x >= 1e21)
      return String(s+UString::from(x));

    double n = floor(x*pow(10.0,f));
    if (fabs(n/pow(10.0,f)-x) > fabs((n+1)/pow(10.0,f)-x))
      n++;

    UString m = integer_part_noexp(n);

    int k = m.size();
    if (k <= f) {
      UString z = "";
      for (int i = 0; i < f+1-k; i++)
	z += "0";
      m = z + m;
      k = f + 1;
      assert(k == m.size());
    }
    if (k-f < m.size())
      return String(s+m.substr(0,k-f)+"."+m.substr(k-f));
    else
      return String(s+m.substr(0,k-f));
  }
  case ToExponential: {
    double x = v.toNumber(exec);

    if (isNaN(x) || isInf(x))
      return String(UString::from(x));

    int f = 1;
    Value fractionDigits = args[0];
    if (args.size() > 0) {
      f = fractionDigits.toInteger(exec);
      if (f < 0 || f > 20) {
        Object err = Error::create(exec,RangeError);
        exec->setException(err);
        return err;
      }
    }

    int decimalAdjust = 0;
    if (!fractionDigits.isA(UndefinedType)) {
      double logx = floor(log10(fabs(x)));
      x /= pow(10.0,logx);
      double fx = floor(x*pow(10.0,f))/pow(10.0,f);
      double cx = ceil(x*pow(10.0,f))/pow(10.0,f);

      if (fabs(fx-x) < fabs(cx-x))
	x = fx;
      else
	x = cx;

      decimalAdjust = int(logx);
    }

    char buf[80];
    int decimalPoint;
    int sign;

    if (isNaN(x))
      return String("NaN");

    char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, NULL);
    int length = strlen(result);
    decimalPoint += decimalAdjust;

    int i = 0;
    if (sign) {
      buf[i++] = '-';
    }

    if (decimalPoint == 999) {
      strcpy(buf + i, result);
    } else {
      buf[i++] = result[0];

      if (fractionDigits.isA(UndefinedType))
	f = length-1;

      if (length > 1 && f > 0) {
	buf[i++] = '.';
	int haveFDigits = length-1;
	if (f < haveFDigits) {
	  strncpy(buf+i,result+1, f);
	  i += f;
	}
	else {
	  strcpy(buf+i,result+1);
	  i += length-1;
	  for (int j = 0; j < f-haveFDigits; j++)
	    buf[i++] = '0';
	}
      }

      buf[i++] = 'e';
      buf[i++] = (decimalPoint >= 0) ? '+' : '-';
      // decimalPoint can't be more than 3 digits decimal given the
      // nature of float representation
      int exponential = decimalPoint - 1;
      if (exponential < 0) {
	exponential = exponential * -1;
      }
      if (exponential >= 100) {
	buf[i++] = '0' + exponential / 100;
      }
      if (exponential >= 10) {
	buf[i++] = '0' + (exponential % 100) / 10;
      }
      buf[i++] = '0' + exponential % 10;
      buf[i++] = '\0';
    }

    assert(i <= 80);

    kjs_freedtoa(result);

    return String(UString(buf));
  }
  case ToPrecision:
  {
    int e = 0;
    UString m;

    int p = args[0].toInteger(exec);
    double x = v.toNumber(exec);
    if (args[0].isA(UndefinedType) || isNaN(x) || isInf(x))
      return String(v.toString(exec));

    UString s = "";
    if (x < 0) {
      s = "-";
      x = -x;
    }

    if (p < 1 || p > 21) {
      Object err = Error::create(exec, RangeError,
				 "toPrecision() argument must be between 1 and 21");
      exec->setException(err);
      return err;
    }

    if (x != 0) {
      // suggestions for a better algorithm welcome!
      e = int(log10(x));
      double n = floor(x/pow(10.0,e-p+1));
      if (n < pow(10.0,p-1)) {
	// first guess was not good
	e = e - 1;
	n = floor(x/pow(10.0,e-p+1));
	if (n >= pow(10.0,p)) {
	  // violated constraint. try something else.
	  n = pow(10.0,p-1);
	  e = int(log10(x/n)) + p - 1;
	}
      }

      if (fabs((n+1)*pow(10.0,e-p+1)-x) < fabs(n*pow(10.0,e-p+1)-x))
	n++;
      assert(pow(10.0,p-1) <= n);
      assert(n < pow(10.0,p));

      m = integer_part_noexp(n);
      if (e < -6 || e >= p) {
	if (m.size() > 1)
	  m = m.substr(0,1)+"."+m.substr(1);
	if (e >= 0)
	  return String(s+m+"e+"+UString::from(e));
	else
	  return String(s+m+"e-"+UString::from(-e));
      }
    }
    else {
      m = char_sequence('0',p);
      e = 0;
    }

    if (e == p-1) {
      return String(s+m);
    }
    else if (e >= 0) {
      if (e+1 < m.size())
	return String(s+m.substr(0,e+1)+"."+m.substr(e+1));
      else
	return String(s+m.substr(0,e+1));
    }
    else {
      return String(s+"0."+char_sequence('0',-(e+1))+m);
    }
  }
  }

  return result;
}

// ------------------------------ NumberObjectImp ------------------------------

const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, 0};

/* Source for number_object.lut.h
@begin numberTable 5
  NaN			NumberObjectImp::NaNValue	DontEnum|DontDelete|ReadOnly
  NEGATIVE_INFINITY	NumberObjectImp::NegInfinity	DontEnum|DontDelete|ReadOnly
  POSITIVE_INFINITY	NumberObjectImp::PosInfinity	DontEnum|DontDelete|ReadOnly
  MAX_VALUE		NumberObjectImp::MaxValue	DontEnum|DontDelete|ReadOnly
  MIN_VALUE		NumberObjectImp::MinValue	DontEnum|DontDelete|ReadOnly
@end
*/
NumberObjectImp::NumberObjectImp(ExecState * /*exec*/,
                                 FunctionPrototypeImp *funcProto,
                                 NumberPrototypeImp *numberProto)
  : InternalFunctionImp(funcProto)
{
  Value protect(this);
  // Number.Prototype
  putDirect(prototypePropertyName, numberProto, DontEnum|DontDelete|ReadOnly);

  // no. of arguments for constructor
  putDirect(lengthPropertyName, NumberImp::one(), ReadOnly|DontDelete|DontEnum);
}

Value NumberObjectImp::get(ExecState *exec, const Identifier &propertyName) const
{
  return lookupGetValue<NumberObjectImp, InternalFunctionImp>( exec, propertyName, &numberTable, this );
}

Value NumberObjectImp::getValueProperty(ExecState *, int token) const
{
  // ECMA 15.7.3
  switch(token) {
  case NaNValue:
    return Number(NaN);
  case NegInfinity:
    return Number(-Inf);
  case PosInfinity:
    return Number(Inf);
  case MaxValue:
    return Number(1.7976931348623157E+308);
  case MinValue:
    return Number(5E-324);
  }
  return Null();
}

bool NumberObjectImp::implementsConstruct() const
{
  return true;
}


// ECMA 15.7.1
Object NumberObjectImp::construct(ExecState *exec, const List &args)
{
  ObjectImp *proto = exec->lexicalInterpreter()->builtinNumberPrototype().imp();
  Object obj(new NumberInstanceImp(proto));

  Number n;
  if (args.isEmpty())
    n = Number(0);
  else
    n = args[0].toNumber(exec);

  obj.setInternalValue(n);

  return obj;
}

bool NumberObjectImp::implementsCall() const
{
  return true;
}

// ECMA 15.7.2
Value NumberObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
{
  if (args.isEmpty())
    return Number(0);
  else
    return Number(args[0].toNumber(exec));
}