/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.apple.jobjc;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.LinkedHashMap;
import java.util.Map;

public class ID extends Pointer<Void>{
    static native String getNativeDescription(final long objPtr);

    final JObjCRuntime runtime;

    static final Class[] CTOR_ARGS = { long.class, JObjCRuntime.class };
    protected ID(final long objPtr, final JObjCRuntime runtime) {
        super(objPtr);
        runtime.assertOK();
        this.runtime = runtime;
    }

    protected ID(final ID obj, final JObjCRuntime runtime) {
        this(obj.ptr, runtime);
    }

    @Override protected NativeObjectLifecycleManager getNativeObjectLifecycleManager() {
        return NativeObjectLifecycleManager.CFRetainRelease.INST;
    }

    protected final JObjCRuntime getRuntime() { return runtime; }

    @Override public String toString(){
        String s = super.toString();
        return s + " (ObjC: " + ptr + " / " + Long.toHexString(ptr) + ")";
    }

    //

    public static <T extends ID> T getInstance(final long ptr, final JObjCRuntime runtime){
        return (T) getObjCObjectFor(runtime, ptr);
    }

    static <T extends ID> T getObjCObjectFor(final JObjCRuntime runtime, final long objPtr){
        if (objPtr == 0) return null;

        final WeakReference cachedObj = objectCache.get().get(objPtr);
        if(cachedObj != null && cachedObj.get() != null) return (T) cachedObj.get();

        final long clsPtr = NSClass.getClass(objPtr);

        final T newObj = (T) (runtime.subclassing.isUserClass(clsPtr) ?
                Subclassing.getJObjectFromIVar(objPtr)
                : createNewObjCObjectFor(runtime, objPtr, clsPtr));

        objectCache.get().put(objPtr, new WeakReference(newObj));
        return newObj;
    }

    static <T extends ID> T createNewObjCObjectFor(final JObjCRuntime runtime, final long objPtr, final long clsPtr) {
        final Constructor<T> ctor = getConstructorForClassPtr(runtime, clsPtr);
        return (T) createNewObjCObjectForConstructor(ctor, objPtr, runtime);
    }

    @SuppressWarnings("unchecked")
    static <T extends ID> Constructor<T> getConstructorForClassPtr(final JObjCRuntime runtime, final long clazzPtr){
        final Constructor<T> cachedCtor = (Constructor<T>) constructorCache.get().get(clazzPtr);
        if(cachedCtor != null) return cachedCtor;

        final Class<T> clazz = getClassForClassPtr(runtime, clazzPtr);
        Constructor<T> ctor;
        try {
            ctor = clazz.getDeclaredConstructor(CTOR_ARGS);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        ctor.setAccessible(true);
        constructorCache.get().put(clazzPtr, (Constructor<ID>) ctor);
        return ctor;
    }

    @SuppressWarnings("unchecked")
    static <T extends ID> Class<T> getClassForClassPtr(final JObjCRuntime runtime, final long clazzPtr){
        final String className = NSClass.getClassNameOfClass(clazzPtr);
        final Class<T> clazz = (Class<T>) runtime.getClassForNativeClassName(className);
        if(clazz == null){
            final long superClazzPtr = NSClass.getSuperClassOfClass(clazzPtr);
            if(superClazzPtr != 0)
                return getClassForClassPtr(runtime, superClazzPtr);
        }
        return clazz;
    }

    static <T extends ID> T createNewObjCObjectForConstructor(final Constructor ctor, final long objPtr, final JObjCRuntime runtime) {
        try {
            final T newInstance = (T) ctor.newInstance(new Object[] { Long.valueOf(objPtr), runtime });
            objectCache.get().put(objPtr, new WeakReference(newInstance));
            return newInstance;
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    static <T extends ID> T createNewObjCObjectForClass(final Class<T> clazz, final long objPtr, final JObjCRuntime runtime) {
        try {
            final Constructor<T> constructor = clazz.getDeclaredConstructor(CTOR_ARGS);
            constructor.setAccessible(true);
            return (T) createNewObjCObjectForConstructor(constructor, objPtr, runtime);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    //

    static final ThreadLocal<LinkedHashMap<Long, Constructor>> constructorCache = new ThreadLocal<LinkedHashMap<Long, Constructor>>(){
        @Override protected LinkedHashMap<Long, Constructor> initialValue(){
            final int MAX_ENTRIES = 1000;
            final float LOAD_FACTOR = 0.75f;
            return new LinkedHashMap<Long, Constructor>((int) (MAX_ENTRIES/LOAD_FACTOR), LOAD_FACTOR, true) {
                @Override protected boolean removeEldestEntry(Map.Entry<Long, Constructor> eldest) {
                    return size() > MAX_ENTRIES;
                }
            };
        }
    };

    static final ThreadLocal<LinkedHashMap<Long, WeakReference>> objectCache = new ThreadLocal<LinkedHashMap<Long, WeakReference>>(){
        @Override protected LinkedHashMap<Long, WeakReference> initialValue(){
            final int MAX_ENTRIES = 1000;
            final float LOAD_FACTOR = 0.75f;
            return new LinkedHashMap<Long, WeakReference>((int) (MAX_ENTRIES/LOAD_FACTOR), LOAD_FACTOR, true) {
                @Override protected boolean removeEldestEntry(Map.Entry<Long, WeakReference> eldest) {
                    return size() > MAX_ENTRIES || eldest.getValue().get() == null;
                }
            };
        }
    };
}
