/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *
 *  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., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 */

#ifndef _KJS_OBJECT_H_
#define _KJS_OBJECT_H_

#include <stdlib.h>

#include "ustring.h"

/**
 * @short Main namespace
 */
namespace KJS {

  /**
   * Types of classes derived from KJSO
   */
  enum Type { // main types
              AbstractType = 1,
              UndefinedType,
	      NullType,
	      BooleanType,
	      NumberType,
	      StringType,
	      ObjectType,
	      HostType,
	      ReferenceType,
              CompletionType,
	      // extended types
	      FunctionType,
	      InternalFunctionType,
	      DeclaredFunctionType,
	      AnonymousFunctionType,
	      ConstructorType,
	      ActivationType
  };

  /**
   * Property attributes.
   */
  enum Attribute { None       = 0,
		   ReadOnly   = 1 << 1,
		   DontEnum   = 1 << 2,
		   DontDelete = 1 << 3,
		   Internal   = 1 << 4 };

  /**
   * Types of classes derived from @ref Object.
   */
  enum Class { UndefClass,
	       ArrayClass,
	       StringClass,
	       BooleanClass,
	       NumberClass,
	       ObjectClass,
	       DateClass,
	       RegExpClass,
	       ErrorClass,
	       FunctionClass };

  /**
   * Completion types.
   */
  enum Compl { Normal, Break, Continue, ReturnValue, Throw };

  /**
   * Error codes.
   */
  enum ErrorType { NoError = 0,
		   GeneralError,
		   EvalError,
		   RangeError,
		   ReferenceError,
		   SyntaxError,
		   TypeError,
		   URIError };

  extern const double NaN;
  extern const double Inf;

  // forward declarations
  class Imp;
  class Boolean;
  class Number;
  class String;
  class Object;
  struct Property;
  class PropList;
  class List;

  /**
   * @short Type information.
   */
  struct TypeInfo {
    /**
     * A string denoting the type name. Example: "Number".
     */
    const char *name;
    /**
     * One of the @ref KJS::Type enums.
     */
    Type type;
    /**
     * Pointer to the type information of the base class.
     * NULL if there is none.
     */
    const TypeInfo *base;
    /**
     * Additional specifier for your own use.
     */
    int extra;
    /**
     * Reserved for future extensions (internal).
     */
    void *dummy;
  };

  /**
   * @short Main base class for every KJS object.
   */
  class KJSO {
    friend class ElementNode;
  public:
    /**
     * Constructor.
     */
    KJSO();
    /**
     * @internal
     */
    KJSO(Imp *d);
    /**
     * Copy constructor.
     */
    KJSO(const KJSO &);
    /*
     * Assignment operator
     */
    KJSO& operator=(const KJSO &);
    /**
     * Destructor.
     */
    virtual ~KJSO();
    /**
     * @return True if this object is null, i.e. if there is no data attached
     * to this object. Don't confuse this with the Null object.
     */
    bool isNull() const;
    /**
     * @return True if this objects is of any other value than Undefined.
     */
    bool isDefined() const;
    /**
     * @return the type of the object. One of the @ref KJS::Type enums.
     */
    Type type() const;
    /**
     * Check whether object is of a certain type
     * @param t type to check for
     */
    bool isA(Type t) const { return (type() == t); }
    /**
     * Check whether object is of a certain type. Allows checking of
     * host objects, too.
     * @param type name (Number, Boolean etc.)
     */
    bool isA(const char *s) const;
    /**
     * Use this method when checking for objects. It's safer than checking
     * for a single object type with @ref isA().
     */
    bool isObject() const;
    /**
     * Examine the inheritance structure of this object.
     * @param t Name of the base class.
     * @return True if object is of type t or a derived from such a type.
     */
    bool derivedFrom(const char *s) const;

    /**
     * @return Conversion to primitive type (Undefined, Boolean, Number
     * or String)
     * @param preferred Optional hint. Either StringType or NumberType.
     */
    KJSO toPrimitive(Type preferred = UndefinedType) const; // ECMA 9.1
    /**
     * @return Conversion to Boolean type.
     */
    Boolean toBoolean() const; // ECMA 9.2
    /**
     * @return Conversion to Number type.
     */
    Number toNumber() const; // ECMA 9.3
    /**
     * @return Conversion to double. 0.0 if conversion failed.
     */
    double round() const;
    /**
     * @return Conversion to Number type containing an integer value.
     */
    Number toInteger() const; // ECMA 9.4
    /**
     * @return Conversion to signed integer value.
     */
    int toInt32() const; // ECMA 9.5
    /**
     * @return Conversion to unsigned integer value.
     */
    unsigned int toUInt32() const; // ECMA 9.6
    /**
     * @return Conversion to unsigned short value.
     */
    unsigned short toUInt16() const; // ECMA 9.7
    /**
     * @return Conversion to String type.
     */
    String toString() const; // ECMA 9.8
    /**
     * @return Conversion to Object type.
     */
    Object toObject() const; // ECMA 9.9

    // Properties
    /**
     * Set the internal [[prototype]] property of this object.
     * @param p A prototype object.
     */
    void setPrototype(const KJSO& p);
    /**
     * @return The internal [[prototype]] property.
     */
    KJSO prototype() const;
    /**
     * The internal [[Get]] method.
     * @return The value of property p.
     */
    KJSO get(const UString &p) const;
    /**
     * The internal [[HasProperty]] method.
     * @param p Property name.
     * @param recursive Indicates whether prototypes are searched as well.
     * @return Boolean value indicating whether the object already has a
     * member with the given name p.
     */
    bool hasProperty(const UString &p, bool recursive = true) const;
    /**
     * The internal [[Put]] method. Sets the specified property to the value v.
     * @param p Property name.
     * @param v Value.
     */
    void put(const UString &p, const KJSO& v);
    /**
     * The internal [[CanPut]] method.
     * @param p Property name.
     * @return A boolean value indicating whether a [[Put]] operation with
     * p succeed.
     */
    bool canPut(const UString &p) const;
    /**
     * The internal [[Delete]] method. Removes the specified property from
     * the object.
     * @param p Property name.
     * @return True if the property was deleted successfully or didn't exist
     * in the first place. False if the DontDelete attribute was set.
     */
    bool deleteProperty(const UString &p);
    /**
     * Same as above put() method except the additional attribute. Right now,
     * this only works with native types as Host Objects don't reimplement
     * this method.
     * @param attr One of @ref KJS::Attribute.
     */
    void put(const UString &p, const KJSO& v, int attr);
    /**
     * Convenience function for adding a Number property constructed from
     * a double value.
     */
    void put(const UString &p, double d, int attr = None);
    /**
     * Convenience function for adding a Number property constructed from
     * an integer value.
     */
    void put(const UString &p, int i, int attr = None);
    /**
     * Convenience function for adding a Number property constructed from
     * an unsigned integer value.
     */
    void put(const UString &p, unsigned int u, int attr = None);

    /**
     * Reference method.
     * @return Reference base if object is a reference. Throws
     * a ReferenceError otherwise.
     */
    KJSO getBase() const;
    /**
     * Reference method.
     * @return Property name of a reference. Null string if object is not
     * a reference.
     */
    UString getPropertyName() const;
    /**
     * Reference method.
     * @return Referenced value. This object if no reference.
     */
    KJSO getValue();
    /**
     * Reference method. Set referenced value to v.
     */
    ErrorType putValue(const KJSO& v);

    /**
     * @return True if object supports @ref executeCall() method. That's the
     * case for all objects derived from FunctionType.
     */
    bool implementsCall() const;
    /**
     * Execute function implemented via the @ref Function::execute() method.
     *
     * Note: check availability via @ref implementsCall() beforehand.
     * @param thisV Object serving as the 'this' value.
     * @param args Pointer to the list of arguments or null.
     * @return Result of the function call.
     */
    KJSO executeCall(const KJSO &thisV, const List *args);

    /**
     * Set this object's constructor.
     */
    void setConstructor(KJSO c);

    /**
     * @return A Pointer to the internal implementation.
     */
    Imp *imp() const { return rep; }

#ifdef KJS_DEBUG_MEM
    /**
     * @internal
     */
    static int count;
#endif
  protected:
    /**
     * Pointer to the internal implementation.
     */
    Imp *rep;

  private:
    void putArrayElement(const UString &p, const KJSO &v);
  }; // end of KJSO

  /**
   * @short Base for all implementation classes.
   */
  class Imp {
    friend class KJSO;
    friend class Collector;
    friend class ForInNode;
  public:
    Imp();
  public:
    virtual KJSO toPrimitive(Type preferred = UndefinedType) const; // ECMA 9.1
    virtual Boolean toBoolean() const; // ECMA 9.2
    virtual Number toNumber() const; // ECMA 9.3
    virtual String toString() const; // ECMA 9.8
    virtual Object toObject() const; // ECMA 9.9

    // properties
    virtual KJSO get(const UString &p) const;
    virtual bool hasProperty(const UString &p, bool recursive = true) const;
    virtual void put(const UString &p, const KJSO& v);
    void put(const UString &p, const KJSO& v, int attr);
    virtual bool canPut(const UString &p) const;
    virtual bool deleteProperty(const UString &p);
    virtual KJSO defaultValue(Type hint) const;

    bool implementsCall() const;

    /**
     * @internal Reserved for mark & sweep garbage collection
     */
    virtual void mark(Imp *imp = 0L);

    Type type() const { return typeInfo()->type; }
    /**
     * @return The TypeInfo struct describing this object.
     */
    virtual const TypeInfo* typeInfo() const { return &info; }

    void setPrototype(const KJSO& p);
    void setConstructor(const KJSO& c);

    void* operator new(size_t);
    void operator delete(void*);
    /**
     * @deprecated
     */
    void operator delete(void*, size_t);

#ifdef KJS_DEBUG_MEM
    /**
     * @internal
     */
    static int count;
#endif
  protected:
    virtual ~Imp();
  private:
    Imp(const Imp&);
    Imp& operator=(const Imp&);
    void putArrayElement(const UString &p, const KJSO& v);

    /**
     * Get the property names for this object. To be used by for .. in loops
     * @return The (pointer to the) first element of a PropList, to be deleted
     * by the caller, or 0 if there are no enumerable properties
     */
    PropList *getPropList(PropList *first = 0L, PropList *last = 0L) const;

    // reference counting mechanism
    inline Imp* ref() { refcount++; return this; }
    inline bool deref() { return (!--refcount); }
  public:
    unsigned int refcount;

  private:
    Property *prop;
    Imp *proto;
    static const TypeInfo info;

    // reserved for memory managment
    Imp *prev, *next;
    // for future extensions
    class ImpInternal;
    ImpInternal *internal;
  };

  /**
   * @short General implementation class for Objects
   */
  class ObjectImp : public Imp {
    friend class Object;
  public:
    ObjectImp(Class c);
    ObjectImp(Class c, const KJSO &v);
    ObjectImp(Class c, const KJSO &v, const KJSO &p);
    virtual ~ObjectImp();
    virtual KJSO toPrimitive(Type preferred = UndefinedType) const;
    virtual Boolean toBoolean() const;
    virtual Number toNumber() const;
    virtual String toString() const;
    virtual Object toObject() const;

    virtual const TypeInfo* typeInfo() const { return &info; }
    static const TypeInfo info;
    /**
     * @internal Reimplemenation of @ref Imp::mark().
     */
    virtual void mark(Imp *imp = 0L);
  private:
    Class cl;
    Imp *val;
  };

  /**
   * @short Object class encapsulating an internal value.
   */
  class Object : public KJSO {
  public:
    Object(Imp *d);
    Object(Class c = UndefClass);
    Object(Class c, const KJSO& v);
    Object(Class c, const KJSO& v, const Object& p);
    virtual ~Object();
    void setClass(Class c);
    Class getClass() const;
    void setInternalValue(const KJSO& v);
    KJSO internalValue();
    static Object create(Class c);
    static Object create(Class c, const KJSO& val);
    static Object create(Class c, const KJSO& val, const Object &p);
    static Object dynamicCast(const KJSO &obj);
  };

  /**
   * @short Implementation base class for Host Objects.
   */
  class HostImp : public Imp {
  public:
    HostImp();
    virtual ~HostImp();
    virtual const TypeInfo* typeInfo() const { return &info; }

    virtual Boolean toBoolean() const;

    static const TypeInfo info;
  };

  class KJScriptImp;
  /**
   * The Global object represents the global namespace. It holds the native
   * objects like String and functions like eval().
   *
   * It also serves as a container for variables created by the user, i.e.
   * the statement
   * <pre>
   *   var i = 2;
   * </pre>
   * will basically perform a Global::current().put("i", Number(2)); operation.
   *
   * @short Unique global object containing initial native properties.
   */
  class Global : public Object {
    friend class KJScriptImp;
  public:
    /**
     * Constructs a Global object. This is done by the interpreter once and
     * there should be no need to create an instance on your own. Usually,
     * you'll just want to access the current instance.
     * For example like this:
     * <pre>
     * Global global(Global::current());
     * KJSO proto = global.objectPrototype();
     * </pre>
     */
    Global();
    /**
     * Destruct the Global object.
     */
    virtual ~Global();
    /**
     * @return A reference to the Global object belonging to the current
     * interpreter instance.
     */
    static Global current();
    /**
     * @return A handle to Object.prototype.
     */
    KJSO objectPrototype() const;
    /**
     * @return A handle to Function.prototype.
     */
    KJSO functionPrototype() const;
    /**
     * Set a filter object that will intercept all put() and get() calls
     * to the global object. If this object returns Undefined on get() the
     * request will be passed on the global object.
     */
    void setFilter(const KJSO &f);
    /**
     * Return a handle to the filter object (see @ref setFilter()).
     * Null if no filter has been installed.
     */
    KJSO filter() const;
  private:
    Global(void *);
    void init();
  };

  /**
   * @short Factory methods for error objects.
   */
  class Error {
  public:
    /**
     * Factory method for error objects. The error will be registered globally
     * and the execution will continue as if a "throw" statement was
     * encountered.
     * @param e Type of error.
     * @param m Optional error message.
     * @param l Optional line number.
     */
    static KJSO create(ErrorType e, const char *m = 0, int l = -1);
    /**
     * Same as above except the different return type (which is not casted
     * here).
     */
    static Object createObject(ErrorType e, const char *m = 0, int l = -1);
  };

}; // namespace

#endif
