/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
 *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
 *  Copyright (C) 2003 Apple Computer, Inc.
 *
 *  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; see the file COPYING.LIB.  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 "lookup.h"
#include "reference_list.h"

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

#include "internal.h"
#include "collector.h"
#include "operations.h"
#include "error_object.h"
#include "nodes.h"

using namespace KJS;

// ------------------------------ Object ---------------------------------------

Object Object::dynamicCast(const Value &v)
{
  if (!v.isValid() || v.type() != ObjectType)
    return Object(0);

  return Object(static_cast<ObjectImp*>(v.imp()));
}

Value Object::call(ExecState *exec, Object &thisObj, const List &args)
{
#if KJS_MAX_STACK > 0
  static int depth = 0; // sum of all concurrent interpreters
  if (++depth > KJS_MAX_STACK) {
#ifndef NDEBUG
    fprintf(stderr, "Exceeded maximum function call depth\n");
#endif
    int saveDepth = depth - 1;
    Object err = Error::create(exec, RangeError,
                               "Exceeded maximum function call depth.");
    depth = depth - 10; //Give some room for the debugger to operate,
                        //so if it tries to examine things we don't get here again
    exec->setException(err);
    depth         = saveDepth;
    return err;
  }
#endif

  Value ret = static_cast<ObjectImp*>(rep)->call(exec,thisObj,args);

#if KJS_MAX_STACK > 0
  --depth;
#endif

  return ret;
}

// ------------------------------ ObjectImp ------------------------------------

ObjectImp::ObjectImp(const Object &proto)
  : _proto(static_cast<ObjectImp*>(proto.imp())), _internalValue(0L)
{
  //fprintf(stderr,"ObjectImp::ObjectImp %p\n",(void*)this);
}

ObjectImp::ObjectImp(ObjectImp *proto)
  : _proto(proto), _internalValue(0L)
{
}

ObjectImp::ObjectImp()
{
  //fprintf(stderr,"ObjectImp::ObjectImp %p\n",(void*)this);
  _proto = NullImp::staticNull;
  _internalValue = 0L;
}

ObjectImp::~ObjectImp()
{
  //fprintf(stderr,"ObjectImp::~ObjectImp %p\n",(void*)this);
}

void ObjectImp::mark()
{
  //fprintf(stderr,"ObjectImp::mark() %p\n",(void*)this);
  ValueImp::mark();

  if (_proto && !_proto->marked())
    _proto->mark();

  _prop.mark();

  if (_internalValue && !_internalValue->marked())
    _internalValue->mark();

  _scope.mark();
}

const ClassInfo *ObjectImp::classInfo() const
{
  return 0;
}

bool ObjectImp::inherits(const ClassInfo *info) const
{
  if (!info)
    return false;

  const ClassInfo *ci = classInfo();
  if (!ci)
    return false;

  while (ci && ci != info)
    ci = ci->parentClass;

  return (ci == info);
}

Type ObjectImp::type() const
{
  return ObjectType;
}

Value ObjectImp::prototype() const
{
  return Value(_proto);
}

void ObjectImp::setPrototype(const Value &proto)
{
  _proto = proto.imp();
}

UString ObjectImp::className() const
{
  const ClassInfo *ci = classInfo();
  if ( ci )
    return ci->className;
  return "Object";
}

Value ObjectImp::get(ExecState *exec, const Identifier &propertyName) const
{
  ValueImp *imp = getDirect(propertyName);
  if (imp)
    return Value(imp);

  Object proto = Object::dynamicCast(prototype());

  // non-standard netscape extension
  if (propertyName == specialPrototypePropertyName) {
    if (!proto.isValid())
      return Null();
    else
      return Value(proto);
  }

  if (!proto.isValid())
    return Undefined();

  return proto.get(exec,propertyName);
}

Value ObjectImp::getPropertyByIndex(ExecState *exec,
				    unsigned propertyName) const
{
  return get(exec, Identifier::from(propertyName));
}

// ECMA 8.6.2.2
void ObjectImp::put(ExecState *exec, const Identifier &propertyName,
                     const Value &value, int attr)
{
  assert(value.isValid());

  // non-standard netscape extension
  if (propertyName == specialPrototypePropertyName) {
    setPrototype(value);
    return;
  }

  /* TODO: check for write permissions directly w/o this call */
  /* Doesn't look very easy with the PropertyMap API - David */
  // putValue() is used for JS assignemnts. It passes no attribute.
  // Assume that a C++ implementation knows what it is doing
  // and let it override the canPut() check.
  if ((attr == None || attr == DontDelete) && !canPut(exec,propertyName)) {
#ifdef KJS_VERBOSE
    fprintf( stderr, "[kjs-object] WARNING: canPut %s said NO\n", propertyName.ascii() );
#endif
    return;
  }

  _prop.put(propertyName,value.imp(),attr);
}

// delme
void ObjectImp::putPropertyByIndex(ExecState *exec, unsigned propertyName,
                     const Value &value, int attr)
{
  put(exec, Identifier::from(propertyName), value, attr);
}

// ECMA 8.6.2.3
bool ObjectImp::canPut(ExecState *, const Identifier &propertyName) const
{
  int attributes;
  ValueImp *v = _prop.get(propertyName, attributes);
  if (v)
    return!(attributes & ReadOnly);

  // Look in the static hashtable of properties
  const HashEntry* e = findPropertyHashEntry(propertyName);
  if (e)
    return !(e->attr & ReadOnly);

  // Don't look in the prototype here. We can always put an override
  // in the object, even if the prototype has a ReadOnly property.
  return true;
}

// ECMA 8.6.2.4
bool ObjectImp::hasProperty(ExecState *exec, const Identifier &propertyName) const
{
  if (_prop.get(propertyName))
    return true;

  // Look in the static hashtable of properties
  if (findPropertyHashEntry(propertyName))
      return true;

  // non-standard netscape extension
  if (propertyName == specialPrototypePropertyName)
    return true;

  // Look in the prototype
  Object proto = Object::dynamicCast(prototype());
  return proto.isValid() && proto.hasProperty(exec,propertyName);
}

bool ObjectImp::hasPropertyByIndex(ExecState *exec, unsigned propertyName) const
{
  return hasProperty(exec, Identifier::from(propertyName));
}

// ECMA 8.6.2.5
bool ObjectImp::deleteProperty(ExecState * /*exec*/, const Identifier &propertyName)
{
  int attributes;
  ValueImp *v = _prop.get(propertyName, attributes);
  if (v) {
    if ((attributes & DontDelete))
      return false;
    _prop.remove(propertyName);
    return true;
  }

  // Look in the static hashtable of properties
  const HashEntry* entry = findPropertyHashEntry(propertyName);
  if (entry && entry->attr & DontDelete)
    return false; // this builtin property can't be deleted
  return true;
}

bool ObjectImp::deletePropertyByIndex(ExecState *exec, unsigned propertyName)
{
  return deleteProperty(exec, Identifier::from(propertyName));
}

void ObjectImp::deleteAllProperties( ExecState * )
{
  _prop.clear();
}

// ECMA 8.6.2.6
Value ObjectImp::defaultValue(ExecState *exec, Type hint) const
{
  if (hint != StringType && hint != NumberType) {
    /* Prefer String for Date objects */
    if (_proto == exec->lexicalInterpreter()->builtinDatePrototype().imp())
      hint = StringType;
    else
      hint = NumberType;
  }

  Value v;
  if (hint == StringType)
    v = get(exec,toStringPropertyName);
  else
    v = get(exec,valueOfPropertyName);

  if (v.type() == ObjectType) {
    Object o = Object(static_cast<ObjectImp*>(v.imp()));
    if (o.implementsCall()) { // spec says "not primitive type" but ...
      Object thisObj = Object(const_cast<ObjectImp*>(this));
      Value def = o.call(exec,thisObj,List::empty());
      Type defType = def.type();
      if (defType == UnspecifiedType || defType == UndefinedType ||
          defType == NullType || defType == BooleanType ||
          defType == StringType || defType == NumberType) {
        return def;
      }
    }
  }

  if (hint == StringType)
    v = get(exec,valueOfPropertyName);
  else
    v = get(exec,toStringPropertyName);

  if (v.type() == ObjectType) {
    Object o = Object(static_cast<ObjectImp*>(v.imp()));
    if (o.implementsCall()) { // spec says "not primitive type" but ...
      Object thisObj = Object(const_cast<ObjectImp*>(this));
      Value def = o.call(exec,thisObj,List::empty());
      Type defType = def.type();
      if (defType == UnspecifiedType || defType == UndefinedType ||
          defType == NullType || defType == BooleanType ||
          defType == StringType || defType == NumberType) {
        return def;
      }
    }
  }

  Object err = Error::create(exec, TypeError, I18N_NOOP("No default value"));
  exec->setException(err);
  return err;
}

const HashEntry* ObjectImp::findPropertyHashEntry( const Identifier& propertyName ) const
{
  const ClassInfo *info = classInfo();
  while (info) {
    if (info->propHashTable) {
      const HashEntry *e = Lookup::findEntry(info->propHashTable, propertyName);
      if (e)
        return e;
    }
    info = info->parentClass;
  }
  return 0L;
}

bool ObjectImp::implementsConstruct() const
{
  return false;
}

Object ObjectImp::construct(ExecState* /*exec*/, const List &/*args*/)
{
  assert(false);
  return Object(0);
}

bool ObjectImp::implementsCall() const
{
  return false;
}

Value ObjectImp::call(ExecState* /*exec*/, Object &/*thisObj*/, const List &/*args*/)
{
  assert(false);
  return Object(0);
}

bool ObjectImp::implementsHasInstance() const
{
  return false;
}

Boolean ObjectImp::hasInstance(ExecState* /*exec*/, const Value &/*value*/)
{
  assert(false);
  return Boolean(false);
}

ReferenceList ObjectImp::propList(ExecState *exec, bool recursive)
{
  ReferenceList list;
  if (_proto && _proto->dispatchType() == ObjectType && recursive)
    list = static_cast<ObjectImp*>(_proto)->propList(exec,recursive);

  _prop.addEnumerablesToReferenceList(list, Object(this));

  // Add properties from the static hashtable of properties
  const ClassInfo *info = classInfo();
  while (info) {
    if (info->propHashTable) {
      int size = info->propHashTable->size;
      const HashEntry *e = info->propHashTable->entries;
      for (int i = 0; i < size; ++i, ++e) {
        if ( e->soffset && !(e->attr & DontEnum) )
          list.append(Reference(this, &info->propHashTable->sbase[e->soffset])); /// ######### check for duplicates with the propertymap
      }
    }
    info = info->parentClass;
  }

  return list;
}

Value ObjectImp::internalValue() const
{
  return Value(_internalValue);
}

void ObjectImp::setInternalValue(const Value &v)
{
  _internalValue = v.imp();
}

void ObjectImp::setInternalValue(ValueImp *v)
{
  v->setGcAllowed();
  _internalValue = v;
}

Value ObjectImp::toPrimitive(ExecState *exec, Type preferredType) const
{
  return defaultValue(exec,preferredType);
}

bool ObjectImp::toBoolean(ExecState* /*exec*/) const
{
  return true;
}

double ObjectImp::toNumber(ExecState *exec) const
{
  Value prim = toPrimitive(exec,NumberType);
  if (exec->hadException()) // should be picked up soon in nodes.cpp
    return 0.0;
  return prim.toNumber(exec);
}

UString ObjectImp::toString(ExecState *exec) const
{
  Value prim = toPrimitive(exec,StringType);
  if (exec->hadException()) // should be picked up soon in nodes.cpp
    return "";
  return prim.toString(exec);
}

Object ObjectImp::toObject(ExecState * /*exec*/) const
{
  return Object(const_cast<ObjectImp*>(this));
}

void ObjectImp::putDirect(const Identifier &propertyName, ValueImp *value, int attr)
{
  value->setGcAllowed();
  _prop.put(propertyName, value, attr);
}

void ObjectImp::putDirect(const Identifier &propertyName, int value, int attr)
{
  _prop.put(propertyName, NumberImp::create(value), attr);
}

void ObjectImp::setFunctionName(const Identifier &propertyName)
{
  if (inherits(&InternalFunctionImp::info))
    static_cast<InternalFunctionImp*>(this)->setName(propertyName);
}

// ------------------------------ Error ----------------------------------------

const char * const errorNamesArr[] = {
  I18N_NOOP("Error"), // GeneralError
  I18N_NOOP("Evaluation error"), // EvalError
  I18N_NOOP("Range error"), // RangeError
  I18N_NOOP("Reference error"), // ReferenceError
  I18N_NOOP("Syntax error"), // SyntaxError
  I18N_NOOP("Type error"), // TypeError
  I18N_NOOP("URI error"), // URIError
};

const char * const * const Error::errorNames = errorNamesArr;

Object Error::create(ExecState *exec, ErrorType errtype, const char *message,
                     int lineno, int sourceId)
{
#ifdef KJS_VERBOSE
  // message could be 0L. Don't enable this on Solaris ;)
  fprintf(stderr, "WARNING: KJS %s: %s\n", errorNames[errtype], message);
#endif

  Object cons;

  switch (errtype) {
  case EvalError:
    cons = exec->lexicalInterpreter()->builtinEvalError();
    break;
  case RangeError:
    cons = exec->lexicalInterpreter()->builtinRangeError();
    break;
  case ReferenceError:
    cons = exec->lexicalInterpreter()->builtinReferenceError();
    break;
  case SyntaxError:
    cons = exec->lexicalInterpreter()->builtinSyntaxError();
    break;
  case TypeError:
    cons = exec->lexicalInterpreter()->builtinTypeError();
    break;
  case URIError:
    cons = exec->lexicalInterpreter()->builtinURIError();
    break;
  default:
    cons = exec->lexicalInterpreter()->builtinError();
    break;
  }

  if (!message)
    message = errorNames[errtype];
  List args;
  args.append(String(message));
  Object err = Object::dynamicCast(cons.construct(exec,args));

  if (lineno != -1)
    err.put(exec, "line", Number(lineno));
  if (sourceId != -1)
    err.put(exec, "sourceId", Number(sourceId));

  return err;

/*
#ifndef NDEBUG
  const char *msg = err.get(messagePropertyName).toString().value().ascii();
  if (l >= 0)
      fprintf(stderr, "KJS: %s at line %d. %s\n", estr, l, msg);
  else
      fprintf(stderr, "KJS: %s. %s\n", estr, msg);
#endif

  return err;
*/
}

