/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Igor Bukanov
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript;

/**
 * This class implements the "arguments" object.
 *
 * See ECMA 10.1.8
 *
 * @see org.mozilla.javascript.NativeCall
 * @author Norris Boyd
 */
final class Arguments extends IdScriptableObject
{
    static final long serialVersionUID = 4275508002492040609L;

    private static final String FTAG = "Arguments";

    public Arguments(NativeCall activation)
    {
        this.activation = activation;

        Scriptable parent = activation.getParentScope();
        setParentScope(parent);
        setPrototype(ScriptableObject.getObjectPrototype(parent));

        args = activation.originalArgs;
        lengthObj = Integer.valueOf(args.length);

        NativeFunction f = activation.function;
        calleeObj = f;

        Scriptable topLevel = getTopLevelScope(parent);
        objectCtor = (BaseFunction) getProperty(topLevel, "Object");

        constructor = objectCtor;

        int version = f.getLanguageVersion();
        if (version <= Context.VERSION_1_3
            && version != Context.VERSION_DEFAULT)
        {
            callerObj = null;
        } else {
            callerObj = NOT_FOUND;
        }
    }

    @Override
    public String getClassName()
    {
        return FTAG;
    }

    private Object arg(int index) {
      if (index < 0 || args.length <= index) return NOT_FOUND;
      return args[index];
    }

    // the following helper methods assume that 0 < index < args.length
    
    private void putIntoActivation(int index, Object value) {
        String argName = activation.function.getParamOrVarName(index);
        activation.put(argName, activation, value);
    }

    private Object getFromActivation(int index) {
        String argName = activation.function.getParamOrVarName(index);
        return activation.get(argName, activation);
    }

    private void replaceArg(int index, Object value) {
      if (sharedWithActivation(index)) {
        putIntoActivation(index, value);
      } 
      synchronized (this) {
        if (args == activation.originalArgs) {
          args = args.clone();
        }
        args[index] = value;
      }
    }

    private void removeArg(int index) {
      synchronized (this) {
        if (args[index] != NOT_FOUND) {
          if (args == activation.originalArgs) {
            args = args.clone();
          }
          args[index] = NOT_FOUND;
        }
      }
    }

    // end helpers

    @Override
    public boolean has(int index, Scriptable start)
    {
        if (arg(index) != NOT_FOUND) {
          return true;
        }
        return super.has(index, start);
    }

    @Override
    public Object get(int index, Scriptable start)
    {
      final Object value = arg(index);
      if (value == NOT_FOUND) {
        return super.get(index, start);
      } else {
        if (sharedWithActivation(index)) {
          return getFromActivation(index);
        } else {
          return value;
        }
      }
    }

    private boolean sharedWithActivation(int index)
    {
        NativeFunction f = activation.function;
        int definedCount = f.getParamCount();
        if (index < definedCount) {
            // Check if argument is not hidden by later argument with the same
            // name as hidden arguments are not shared with activation
            if (index < definedCount - 1) {
                String argName = f.getParamOrVarName(index);
                for (int i = index + 1; i < definedCount; i++) {
                    if (argName.equals(f.getParamOrVarName(i))) {
                        return false;
                    }
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public void put(int index, Scriptable start, Object value)
    {
        if (arg(index) == NOT_FOUND) {
          super.put(index, start, value);
        } else {
          replaceArg(index, value); 
        }
    }

    @Override
    public void delete(int index)
    {
        if (0 <= index && index < args.length) {
          removeArg(index);
        } 
        super.delete(index);
    }

// #string_id_map#

    private static final int
        Id_callee           = 1,
        Id_length           = 2,
        Id_caller           = 3,
        Id_constructor      = 4,

        MAX_INSTANCE_ID     = Id_constructor;

    @Override
    protected int getMaxInstanceId()
    {
        return MAX_INSTANCE_ID;
    }

    @Override
    protected int findInstanceIdInfo(String s)
    {
        int id;
// #generated# Last update: 2010-01-06 05:48:21 ARST
        L0: { id = 0; String X = null; int c;
            int s_length = s.length();
            if (s_length==6) {
                c=s.charAt(5);
                if (c=='e') { X="callee";id=Id_callee; }
                else if (c=='h') { X="length";id=Id_length; }
                else if (c=='r') { X="caller";id=Id_caller; }
            }
            else if (s_length==11) { X="constructor";id=Id_constructor; }
            if (X!=null && X!=s && !X.equals(s)) id = 0;
            break L0;
        }
// #/generated#

        if (id == 0) return super.findInstanceIdInfo(s);

        int attr;
        switch (id) {
          case Id_callee:
          case Id_caller:
          case Id_length:
          case Id_constructor:
            attr = DONTENUM;
            break;
          default: throw new IllegalStateException();
        }
        return instanceIdInfo(attr, id);
    }

// #/string_id_map#

    @Override
    protected String getInstanceIdName(int id)
    {
        switch (id) {
            case Id_callee: return "callee";
            case Id_length: return "length";
            case Id_caller: return "caller";
            case Id_constructor: return "constructor";
        }
        return null;
    }

    @Override
    protected Object getInstanceIdValue(int id)
    {
        switch (id) {
            case Id_callee: return calleeObj;
            case Id_length: return lengthObj;
            case Id_caller: {
                Object value = callerObj;
                if (value == UniqueTag.NULL_VALUE) { value = null; }
                else if (value == null) {
                    NativeCall caller = activation.parentActivationCall;
                    if (caller != null) {
                        value = caller.get("arguments", caller);
                    }
                }
                return value;
            }
            case Id_constructor:
                return constructor;
        }
        return super.getInstanceIdValue(id);
    }

    @Override
    protected void setInstanceIdValue(int id, Object value)
    {
        switch (id) {
            case Id_callee: calleeObj = value; return;
            case Id_length: lengthObj = value; return;
            case Id_caller:
                callerObj = (value != null) ? value : UniqueTag.NULL_VALUE;
                return;
            case Id_constructor: constructor = value; return;
        }
        super.setInstanceIdValue(id, value);
    }

    @Override
    Object[] getIds(boolean getAll)
    {
        Object[] ids = super.getIds(getAll);
        if (args.length != 0) {
            boolean[] present = new boolean[args.length];
            int extraCount = args.length;
            for (int i = 0; i != ids.length; ++i) {
                Object id = ids[i];
                if (id instanceof Integer) {
                    int index = ((Integer)id).intValue();
                    if (0 <= index && index < args.length) {
                        if (!present[index]) {
                            present[index] = true;
                            extraCount--;
                        }
                    }
                }
            }
            if (!getAll) { // avoid adding args which were redefined to non-enumerable
              for (int i = 0; i < present.length; i++) {
                if (!present[i] && super.has(i, this)) { 
                  present[i] = true;
                  extraCount--;
                }
              }
            }
            if (extraCount != 0) {
                Object[] tmp = new Object[extraCount + ids.length];
                System.arraycopy(ids, 0, tmp, extraCount, ids.length);
                ids = tmp;
                int offset = 0;
                for (int i = 0; i != args.length; ++i) {
                    if (present == null || !present[i]) {
                        ids[offset] = Integer.valueOf(i);
                        ++offset;
                    }
                }
                if (offset != extraCount) Kit.codeBug();
            }
        }
        return ids;
    }

    @Override
    protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) {
      double d = ScriptRuntime.toNumber(id);
      int index = (int) d;
      if (d != index) {
        return super.getOwnPropertyDescriptor(cx, id);
      }
      Object value = arg(index);
      if (value == NOT_FOUND) {
        return super.getOwnPropertyDescriptor(cx, id);
      } 
      if (sharedWithActivation(index)) {
        value = getFromActivation(index);
      }
      if (super.has(index, this)) { // the descriptor has been redefined
        ScriptableObject desc = super.getOwnPropertyDescriptor(cx, id);
        desc.put("value", desc, value);
        return desc;
      } else {
        Scriptable scope = getParentScope();
        if (scope == null) scope = this;
        return buildDataDescriptor(scope, value, EMPTY);
      }
    }

    @Override
    public void defineOwnProperty(Context cx, Object id, ScriptableObject desc) {
      super.defineOwnProperty(cx, id, desc);

      double d = ScriptRuntime.toNumber(id);
      int index = (int) d;
      if (d != index) return;

      Object value = arg(index);
      if (value == NOT_FOUND) return;

      if (isAccessorDescriptor(desc)) {
        removeArg(index);
        return;
      }

      Object newValue = getProperty(desc, "value");
      if (newValue == NOT_FOUND) return;

      replaceArg(index, newValue);

      if (isFalse(getProperty(desc, "writable"))) {
        removeArg(index);
      }
    }

// Fields to hold caller, callee and length properties,
// where NOT_FOUND value tags deleted properties.
// In addition if callerObj == NULL_VALUE, it tags null for scripts, as
// initial callerObj == null means access to caller arguments available
// only in JS <= 1.3 scripts
    private Object callerObj;
    private Object calleeObj;
    private Object lengthObj;
    private Object constructor;

    private NativeCall activation;

    private BaseFunction objectCtor; 

// Initially args holds activation.getOriginalArgs(), but any modification
// of its elements triggers creation of a copy. If its element holds NOT_FOUND,
// it indicates deleted index, in which case super class is queried.
    private Object[] args;
}
