/* *****************************************************************************
 * Copyright 2018 Dynamic Analysis Group, Università della Svizzera Italiana (USI)
 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * *****************************************************************************/
package ch.usi.inf.nodeprof.utils;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.builtins.JSUserObject;
import com.oracle.truffle.js.runtime.objects.JSObject;

/**
 *
 * To store global objects such as the global instance, Array constructor and so on We need a
 * dynamic object in order to get the JSContext and further get these global objects
 *
 */
public class GlobalObjectCache extends Node {
    @CompilationFinal private DynamicObject global = null;
    @CompilationFinal private JSContext jscontext = null;
    @CompilationFinal private DynamicObject arrayConstructor = null;
    @CompilationFinal private DynamicObject emptyWrappedException = null;

    private static GlobalObjectCache cache = new GlobalObjectCache();

    public static GlobalObjectCache getInstance() {
        return cache;
    }

    public static void reset() {
        CompilerDirectives.transferToInterpreterAndInvalidate();
        cache = new GlobalObjectCache();
    }

    public DynamicObject getGlobal() {
        return global;
    }

    public DynamicObject getArrayConstructor(DynamicObject option) {
        if (jscontext == null) {
            addDynamicObject(option);
        }
        return arrayConstructor;
    }

    public JSContext getJSContext() {
        return jscontext;
    }

    /**
     *
     * @param object the dynamic object which could be used to find out the JSContext
     * @return The js context
     *
     */
    public JSContext getJSContext(DynamicObject object) {
        if (jscontext == null) {
            addDynamicObject(object);
        }
        return jscontext;
    }

    /**
     * @param someObj a dynamic object which could tell us the jsContext information
     */
    public void addDynamicObject(DynamicObject someObj) {
        if (jscontext == null) {
            CompilerDirectives.transferToInterpreterAndInvalidate();
            jscontext = JSObject.getJSContext(someObj);
            if (global == null) {
                global = jscontext.getRealm().getGlobalObject();
            }
            if (arrayConstructor == null) {
                arrayConstructor = jscontext.getRealm().getArrayConstructor();
            }
        }
    }

    /**
     * @param someObj an object which could tell us the jsContext information
     */
    public void addObject(Object someObj) {
        if (someObj instanceof DynamicObject) {
            addDynamicObject((DynamicObject) someObj);
        }
    }

    /**
     * @return Singleton object for empty wrapped exception
     */
    public Object getEmptyWrappedException() {
        if (this.emptyWrappedException == null) {
            assert (jscontext != null);
            CompilerDirectives.transferToInterpreterAndInvalidate();
            this.emptyWrappedException = JSUserObject.create(jscontext);
        }
        return this.emptyWrappedException;
    }
}