/*
 * Copyright (C) 2018 Igalia S.L.
 *
 * 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 "config.h"
#include "JSCWrapperMap.h"

#include "APICast.h"
#include "JSAPIWrapperGlobalObject.h"
#include "JSAPIWrapperObject.h"
#include "JSCClassPrivate.h"
#include "JSCContextPrivate.h"
#include "JSCGLibWrapperObject.h"
#include "JSCInlines.h"
#include "JSCValuePrivate.h"
#include "JSCallbackObject.h"
#include "JSContextRef.h"

namespace JSC {

WrapperMap::WrapperMap(JSGlobalContextRef jsContext)
    : m_cachedJSWrappers(makeUnique<JSC::WeakGCMap<gpointer, JSC::JSObject>>(toJS(jsContext)->vm()))
{
}

WrapperMap::~WrapperMap()
{
    for (const auto& jscClass : m_classMap.values())
        jscClassInvalidate(jscClass.get());
}

GRefPtr<JSCValue> WrapperMap::gobjectWrapper(JSCContext* jscContext, JSValueRef jsValue)
{
    auto* jsContext = jscContextGetJSContext(jscContext);
    JSC::JSLockHolder locker(toJS(jsContext));
    ASSERT(toJSGlobalObject(jsContext)->wrapperMap() == this);
    GRefPtr<JSCValue> value = m_cachedGObjectWrappers.get(jsValue);
    if (!value) {
        value = adoptGRef(jscValueCreate(jscContext, jsValue));
        m_cachedGObjectWrappers.set(jsValue, value.get());
    }
    return value;
}

void WrapperMap::unwrap(JSValueRef jsValue)
{
    ASSERT(m_cachedGObjectWrappers.contains(jsValue));
    m_cachedGObjectWrappers.remove(jsValue);
}

void WrapperMap::registerClass(JSCClass* jscClass)
{
    auto* jsClass = jscClassGetJSClass(jscClass);
    ASSERT(!m_classMap.contains(jsClass));
    m_classMap.set(jsClass, jscClass);
}

JSCClass* WrapperMap::registeredClass(JSClassRef jsClass) const
{
    return m_classMap.get(jsClass);
}

JSObject* WrapperMap::createJSWrapper(JSGlobalContextRef jsContext, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
{
    ASSERT(toJSGlobalObject(jsContext)->wrapperMap() == this);
    JSGlobalObject* globalObject = toJS(jsContext);
    VM& vm = globalObject->vm();
    JSLockHolder locker(vm);
    auto* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(globalObject, globalObject->glibWrapperObjectStructure(), jsClass, nullptr);
    if (wrappedObject) {
        object->setWrappedObject(new JSC::JSCGLibWrapperObject(wrappedObject, destroyFunction));
        m_cachedJSWrappers->set(wrappedObject, object);
    }
    if (prototype)
        JSObjectSetPrototype(jsContext, toRef(object), prototype);
    else if (auto* jsPrototype = jsClass->prototype(globalObject))
        object->setPrototypeDirect(vm, jsPrototype);
    return object;
}

JSGlobalContextRef WrapperMap::createContextWithJSWrapper(JSContextGroupRef jsGroup, JSClassRef jsClass, JSValueRef prototype, gpointer wrappedObject, GDestroyNotify destroyFunction)
{
    Ref<VM> vm(*toJS(jsGroup));
    JSLockHolder locker(vm.ptr());
    auto* globalObject = JSCallbackObject<JSAPIWrapperGlobalObject>::create(vm.get(), jsClass, JSCallbackObject<JSAPIWrapperGlobalObject>::createStructure(vm.get(), nullptr, jsNull()));
    if (wrappedObject) {
        globalObject->setWrappedObject(new JSC::JSCGLibWrapperObject(wrappedObject, destroyFunction));
        m_cachedJSWrappers->set(wrappedObject, globalObject);
    }
    if (prototype)
        globalObject->resetPrototype(vm.get(), toJS(globalObject, prototype));
    else if (auto jsPrototype = jsClass->prototype(globalObject))
        globalObject->resetPrototype(vm.get(), jsPrototype);
    else
        globalObject->resetPrototype(vm.get(), jsNull());

    return JSGlobalContextRetain(toGlobalRef(globalObject));
}

JSObject* WrapperMap::jsWrapper(gpointer wrappedObject) const
{
    if (!wrappedObject)
        return nullptr;
    return m_cachedJSWrappers->get(wrappedObject);
}

gpointer WrapperMap::wrappedObject(JSGlobalContextRef jsContext, JSObjectRef jsObject) const
{
    ASSERT(toJSGlobalObject(jsContext)->wrapperMap() == this);
    JSLockHolder locker(toJS(jsContext));
    VM& vm = toJS(jsContext)->vm();
    auto* object = toJS(jsObject);
    if (object->inherits(vm, JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::info())) {
        if (auto* wrapper = JSC::jsCast<JSC::JSAPIWrapperObject*>(object)->wrappedObject())
            return static_cast<JSC::JSCGLibWrapperObject*>(wrapper)->object();
    }
    if (object->inherits(vm, JSC::JSCallbackObject<JSC::JSAPIWrapperGlobalObject>::info())) {
        if (auto* wrapper = JSC::jsCast<JSC::JSAPIWrapperGlobalObject*>(object)->wrappedObject())
            return wrapper->object();
    }
    return nullptr;
}

} // namespace JSC
