/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
 *  Copyright (C) 2007 Cameron Zwarich (cwzwarich@uwaterloo.ca)
 *  Copyright (C) 2007 Maks Orlovich
 *
 *  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.
 *
 */

#ifndef Arguments_h
#define Arguments_h

#include "CodeOrigin.h"
#include "JSActivation.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "Interpreter.h"
#include "ObjectConstructor.h"

namespace JSC {

    class Arguments : public JSNonFinalObject {
        friend class JIT;
        friend class DFG::SpeculativeJIT;
    public:
        typedef JSNonFinalObject Base;

        static Arguments* create(JSGlobalData& globalData, CallFrame* callFrame)
        {
            Arguments* arguments = new (NotNull, allocateCell<Arguments>(globalData.heap)) Arguments(callFrame);
            arguments->finishCreation(callFrame);
            return arguments;
        }
        
        static Arguments* create(JSGlobalData& globalData, CallFrame* callFrame, InlineCallFrame* inlineCallFrame)
        {
            Arguments* arguments = new (NotNull, allocateCell<Arguments>(globalData.heap)) Arguments(callFrame);
            arguments->finishCreation(callFrame, inlineCallFrame);
            return arguments;
        }

        enum { MaxArguments = 0x10000 };

    private:
        enum NoParametersType { NoParameters };
        
        Arguments(CallFrame*);
        Arguments(CallFrame*, NoParametersType);
        
        void tearOffForInlineCallFrame(JSGlobalData& globalData, Register*, InlineCallFrame*);

    public:
        static const ClassInfo s_info;

        static void visitChildren(JSCell*, SlotVisitor&);

        void fillArgList(ExecState*, MarkedArgumentBuffer&);

        uint32_t length(ExecState* exec) const 
        {
            if (UNLIKELY(m_overrodeLength))
                return get(exec, exec->propertyNames().length).toUInt32(exec);
            return m_numArguments; 
        }
        
        void copyToArguments(ExecState*, CallFrame*, uint32_t length);
        void tearOff(CallFrame*);
        void tearOff(CallFrame*, InlineCallFrame*);
        bool isTornOff() const { return m_registerArray; }
        void didTearOffActivation(ExecState*, JSActivation*);

        static Structure* createStructure(JSGlobalData& globalData, JSGlobalObject* globalObject, JSValue prototype) 
        { 
            return Structure::create(globalData, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), &s_info); 
        }
        
    protected:
        static const unsigned StructureFlags = OverridesGetOwnPropertySlot | OverridesVisitChildren | OverridesGetPropertyNames | JSObject::StructureFlags;

        void finishCreation(CallFrame*);
        void finishCreation(CallFrame*, InlineCallFrame*);

    private:
        static void destroy(JSCell*);
        static bool getOwnPropertySlot(JSCell*, ExecState*, PropertyName, PropertySlot&);
        static bool getOwnPropertySlotByIndex(JSCell*, ExecState*, unsigned propertyName, PropertySlot&);
        static bool getOwnPropertyDescriptor(JSObject*, ExecState*, PropertyName, PropertyDescriptor&);
        static void getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode);
        static void put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
        static void putByIndex(JSCell*, ExecState*, unsigned propertyName, JSValue, bool shouldThrow);
        static bool deleteProperty(JSCell*, ExecState*, PropertyName);
        static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned propertyName);
        static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, PropertyDescriptor&, bool shouldThrow);
        void createStrictModeCallerIfNecessary(ExecState*);
        void createStrictModeCalleeIfNecessary(ExecState*);

        bool isArgument(size_t);
        bool trySetArgument(JSGlobalData&, size_t argument, JSValue);
        JSValue tryGetArgument(size_t argument);
        bool isDeletedArgument(size_t);
        bool tryDeleteArgument(size_t);
        WriteBarrierBase<Unknown>& argument(size_t);
        void allocateSlowArguments();

        void init(CallFrame*);

        WriteBarrier<JSActivation> m_activation;

        unsigned m_numArguments;

        // We make these full byte booleans to make them easy to test from the JIT,
        // and because even if they were single-bit booleans we still wouldn't save
        // any space.
        bool m_overrodeLength; 
        bool m_overrodeCallee;
        bool m_overrodeCaller;
        bool m_isStrictMode;

        WriteBarrierBase<Unknown>* m_registers;
        OwnArrayPtr<WriteBarrier<Unknown> > m_registerArray;

        OwnArrayPtr<SlowArgument> m_slowArguments;

        WriteBarrier<JSFunction> m_callee;
    };

    Arguments* asArguments(JSValue);

    inline Arguments* asArguments(JSValue value)
    {
        ASSERT(asObject(value)->inherits(&Arguments::s_info));
        return static_cast<Arguments*>(asObject(value));
    }

    inline Arguments::Arguments(CallFrame* callFrame)
        : JSNonFinalObject(callFrame->globalData(), callFrame->lexicalGlobalObject()->argumentsStructure())
    {
    }

    inline Arguments::Arguments(CallFrame* callFrame, NoParametersType)
        : JSNonFinalObject(callFrame->globalData(), callFrame->lexicalGlobalObject()->argumentsStructure())
    {
    }

    inline void Arguments::allocateSlowArguments()
    {
        if (m_slowArguments)
            return;
        m_slowArguments = adoptArrayPtr(new SlowArgument[m_numArguments]);
    }

    inline bool Arguments::tryDeleteArgument(size_t argument)
    {
        if (!isArgument(argument))
            return false;
        allocateSlowArguments();
        m_slowArguments[argument].status = SlowArgument::Deleted;
        return true;
    }

    inline bool Arguments::trySetArgument(JSGlobalData& globalData, size_t argument, JSValue value)
    {
        if (!isArgument(argument))
            return false;
        this->argument(argument).set(globalData, this, value);
        return true;
    }

    inline JSValue Arguments::tryGetArgument(size_t argument)
    {
        if (!isArgument(argument))
            return JSValue();
        return this->argument(argument).get();
    }

    inline bool Arguments::isDeletedArgument(size_t argument)
    {
        if (argument >= m_numArguments)
            return false;
        if (!m_slowArguments)
            return false;
        if (m_slowArguments[argument].status != SlowArgument::Deleted)
            return false;
        return true;
    }

    inline bool Arguments::isArgument(size_t argument)
    {
        if (argument >= m_numArguments)
            return false;
        if (m_slowArguments && m_slowArguments[argument].status == SlowArgument::Deleted)
            return false;
        return true;
    }

    inline WriteBarrierBase<Unknown>& Arguments::argument(size_t argument)
    {
        ASSERT(isArgument(argument));
        if (!m_slowArguments || m_slowArguments[argument].status == SlowArgument::Normal)
            return m_registers[CallFrame::argumentOffset(argument)];

        ASSERT(m_slowArguments[argument].status == SlowArgument::Captured);
        if (!m_activation)
            return m_registers[m_slowArguments[argument].indexIfCaptured];

        return m_activation->registerAt(m_slowArguments[argument].indexIfCaptured);
    }

    inline void Arguments::finishCreation(CallFrame* callFrame)
    {
        Base::finishCreation(callFrame->globalData());
        ASSERT(inherits(&s_info));

        JSFunction* callee = jsCast<JSFunction*>(callFrame->callee());
        m_numArguments = callFrame->argumentCount();
        m_registers = reinterpret_cast<WriteBarrierBase<Unknown>*>(callFrame->registers());
        m_callee.set(callFrame->globalData(), this, callee);
        m_overrodeLength = false;
        m_overrodeCallee = false;
        m_overrodeCaller = false;
        m_isStrictMode = callFrame->codeBlock()->isStrictMode();

        // The bytecode generator omits op_tear_off_activation in cases of no
        // declared parameters, so we need to tear off immediately.
        if (m_isStrictMode || !callee->jsExecutable()->parameterCount())
            tearOff(callFrame);
    }

    inline void Arguments::finishCreation(CallFrame* callFrame, InlineCallFrame* inlineCallFrame)
    {
        Base::finishCreation(callFrame->globalData());
        ASSERT(inherits(&s_info));

        JSFunction* callee = inlineCallFrame->callee.get();
        m_numArguments = inlineCallFrame->arguments.size() - 1;
        m_registers = reinterpret_cast<WriteBarrierBase<Unknown>*>(callFrame->registers()) + inlineCallFrame->stackOffset;
        m_callee.set(callFrame->globalData(), this, callee);
        m_overrodeLength = false;
        m_overrodeCallee = false;
        m_overrodeCaller = false;
        m_isStrictMode = jsCast<FunctionExecutable*>(inlineCallFrame->executable.get())->isStrictMode();

        // The bytecode generator omits op_tear_off_activation in cases of no
        // declared parameters, so we need to tear off immediately.
        if (m_isStrictMode || !callee->jsExecutable()->parameterCount())
            tearOff(callFrame, inlineCallFrame);
    }

} // namespace JSC

#endif // Arguments_h
