/*
 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebScriptCallFrame.h"

#include "COMEnumVariant.h"
#include "WebKitDLL.h"

#include <JavaScriptCore/Completion.h>
#include <JavaScriptCore/JSFunction.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSLock.h>
#include <JavaScriptCore/JSStringRefBSTR.h>
#include <JavaScriptCore/JSValueRef.h>
#include <JavaScriptCore/PropertyNameArray.h>

#pragma warning(push, 0)
#include <WebCore/BString.h>
#include <WebCore/PlatformString.h>
#pragma warning(pop)

#include <wtf/Assertions.h>

using namespace JSC;

UString WebScriptCallFrame::jsValueToString(JSC::ExecState* state, JSValuePtr jsvalue)
{
    if (!jsvalue)
        return "undefined";

    if (jsvalue->isString())
        return jsvalue->getString();
    else if (jsvalue->isNumber())
        return UString::from(jsvalue->uncheckedGetNumber());
    else if (jsvalue->isBoolean())
        return jsvalue->getBoolean() ? "True" : "False";
    else if (jsvalue->isObject()) {
        jsvalue = jsvalue->getObject()->defaultValue(state, PreferString);
        return jsvalue->getString();
    }

    return "undefined";
}

// WebScriptCallFrame -----------------------------------------------------------

static ExecState* callingFunctionOrGlobalExecState(ExecState* exec)
{
#if 0
    for (ExecState* current = exec; current; current = current->callingExecState())
        if (current->codeType() == FunctionCode || current->codeType() == GlobalCode)
            return current;
#endif
    return 0;
}

WebScriptCallFrame::WebScriptCallFrame(ExecState* state)
    : m_refCount(0)
    , m_state(callingFunctionOrGlobalExecState(state))
{
    ASSERT_ARG(state, state);
    ASSERT(m_state);
    gClassCount++;
    gClassNameCount.add("WebScriptCallFrame");
}

WebScriptCallFrame::~WebScriptCallFrame()
{
    gClassCount--;
    gClassNameCount.remove("WebScriptCallFrame");
}

WebScriptCallFrame* WebScriptCallFrame::createInstance(ExecState* state)
{
    WebScriptCallFrame* instance = new WebScriptCallFrame(state);
    instance->AddRef();
    return instance;
}

// IUnknown ------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::QueryInterface(REFIID riid, void** ppvObject)
{
    *ppvObject = 0;
    if (IsEqualGUID(riid, IID_IUnknown))
        *ppvObject = static_cast<IWebScriptCallFrame*>(this);
    else if (IsEqualGUID(riid, IID_IWebScriptCallFrame))
        *ppvObject = static_cast<IWebScriptCallFrame*>(this);
    else
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

ULONG STDMETHODCALLTYPE WebScriptCallFrame::AddRef()
{
    return ++m_refCount;
}

ULONG STDMETHODCALLTYPE WebScriptCallFrame::Release()
{
    ULONG newRef = --m_refCount;
    if (!newRef)
        delete(this);

    return newRef;
}

// IWebScriptCallFrame -----------------------------------------------------------

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::caller(
    /* [out, retval] */ IWebScriptCallFrame** callFrame)
{
    if (!callFrame)
        return E_POINTER;
#if 0
    *callFrame = m_state->callingExecState() ? WebScriptCallFrame::createInstance(m_state->callingExecState()) : 0;
#endif
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::functionName(
    /* [out, retval] */ BSTR* funcName)
{
    if (!funcName)
        return E_POINTER;

    *funcName = 0;
#if 0
    if (!m_state->scopeNode())
        return S_OK;

    JSFunction* func = m_state->function();
    if (!func)
        return E_FAIL;

    const Identifier& funcIdent = func->functionName();
    if (!funcIdent.isEmpty())
        *funcName = WebCore::BString(funcIdent).release();
#endif
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::stringByEvaluatingJavaScriptFromString(
    /* [in] */ BSTR script,
    /* [out, retval] */ BSTR* result)
{
    if (!script)
        return E_FAIL;

    if (!result)
        return E_POINTER;

    *result = 0;

    JSLock lock(false);

    JSValuePtr scriptExecutionResult = valueByEvaluatingJavaScriptFromString(script);
    *result = WebCore::BString(jsValueToString(m_state, scriptExecutionResult)).release();

    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::variableNames(
    /* [out, retval] */ IEnumVARIANT** variableNames)
{
    if (!variableNames)
        return E_POINTER;

    *variableNames = 0;

    PropertyNameArray propertyNames(m_state);
#if 0
    m_state->scopeChain().top()->getPropertyNames(m_state, propertyNames);
    // FIXME: It would be more efficient to use ::adopt here, but PropertyNameArray doesn't have a swap function.
    *variableNames = COMEnumVariant<PropertyNameArray>::createInstance(propertyNames);

#endif
    return S_OK;
}

HRESULT STDMETHODCALLTYPE WebScriptCallFrame::valueForVariable(
    /* [in] */ BSTR key,
    /* [out, retval] */ BSTR* value)
{
    if (!key)
        return E_FAIL;

    if (!value)
        return E_POINTER;

    *value = 0;

    Identifier identKey(m_state, reinterpret_cast<UChar*>(key), SysStringLen(key));

#if 0
    JSValuePtr jsvalue = noValue();
    ScopeChain scopeChain = m_state->scopeChain();
    for (ScopeChainIterator it = scopeChain.begin(); it != scopeChain.end() && !jsvalue; ++it)
        jsvalue = (*it)->get(m_state, identKey);
    *value = WebCore::BString(jsValueToString(m_state, jsvalue)).release();
#endif

    return S_OK;
}

JSValuePtr WebScriptCallFrame::valueByEvaluatingJavaScriptFromString(BSTR script)
{
#if 0
    ExecState* state = m_state;
    JSGlobalObject* globObj = state->dynamicGlobalObject();

    // find "eval"
    JSObject* eval = 0;
    if (state->scopeNode()) {  // "eval" won't work without context (i.e. at global scope)
        JSValuePtr v = globObj->get(state, "eval");
        if (v->isObject() && asObject(v)->implementsCall())
            eval = asObject(v);
        else
            // no "eval" - fallback operates on global exec state
            state = globObj->globalExec();
    }

    JSValuePtr savedException = state->exception();
    state->clearException();

    UString code(reinterpret_cast<UChar*>(script), SysStringLen(script));

    // evaluate
    JSValuePtr scriptExecutionResult;
    if (eval) {
        ArgList args;
        args.append(jsString(state, code));
        scriptExecutionResult = eval->call(state, 0, args);
    } else
        // no "eval", or no context (i.e. global scope) - use global fallback
        scriptExecutionResult = JSC::evaluate(state, UString(), 0, code.data(), code.size(), globObj).value();

    if (state->hadException())
        scriptExecutionResult = state->exception();    // (may be redundant depending on which eval path was used)
    state->setException(savedException);

    return scriptExecutionResult;
#else
    return jsNull();
#endif
}

template<> struct COMVariantSetter<Identifier>
{
    static void setVariant(VARIANT* variant, const Identifier& value)
    {
        ASSERT(V_VT(variant) == VT_EMPTY);

        V_VT(variant) = VT_BSTR;
        V_BSTR(variant) = WebCore::BString(reinterpret_cast<const wchar_t*>(value.data()), value.size()).release();
    }
};
