/*
 * Copyright (c) 2011, 2012, 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.reflect.Field;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

import sun.misc.Unsafe;

import javax.tools.annotation.GenerateNativeHeader;

/* No native methods here, but the constants are needed in the supporting JNI code */
@GenerateNativeHeader
public final class JObjCRuntime {
    static { System.loadLibrary("JObjC"); }

    @GenerateNativeHeader
    public static enum Arch{ ppc, i386, x86_64 };
    @GenerateNativeHeader
    public static enum Width{ W32, W64 };

    public static final Arch ARCH = getArch();
    public static final Width WIDTH = getWidth();

    private static Arch getArch(){
        String arch = System.getProperty("os.arch");
        if("ppc".equals(arch)) return Arch.ppc;
        if("i386".equals(arch)) return Arch.i386;
        if("x86_64".equals(arch)) return Arch.x86_64;
        if("amd64".equals(arch)) return Arch.x86_64;
        if("universal".equals(arch)) return Arch.x86_64;
        throw new RuntimeException("Did not recognize os.arch system property: '" + arch + "'");
    }

    private static Width getWidth(){
        String width = System.getProperty("sun.arch.data.model");
        if("32".equals(width)) return Width.W32;
        if("64".equals(width)) return Width.W64;
        throw new RuntimeException("Did not recognize sun.arch.data.model system property: '" + width + "'");
    }

    public static final boolean IS32 = System.getProperty("sun.arch.data.model").equals("32");
    public static final boolean IS64 = System.getProperty("sun.arch.data.model").equals("64");
    public static final int PTR_LEN = IS64 ? 8 : 4;
    public static final boolean IS_BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
    static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("JObjC.debug"));

    static void checkPermission(){
        final SecurityManager security = System.getSecurityManager();
        if (security != null) security.checkPermission(new RuntimePermission("canProcessApplicationEvents"));
    }

    public final void assertOK(){
        if(this != instance)
            throw new SecurityException("runtime");
    }

    private JObjCRuntime(){}

    private static JObjCRuntime instance;
    static JObjCRuntime inst() {
        if (instance == null) instance = new JObjCRuntime();
        return instance;
    }

    public static JObjCRuntime getInstance() {
        checkPermission();
        return inst();
    }

    public final NativeArgumentBuffer getThreadLocalState() {
        return NativeArgumentBuffer.getThreadLocalBuffer(this);
    }

    final Unsafe unsafe = getUnsafe();
    final Subclassing subclassing = new Subclassing(this);
    final List<String> registeredPackages = new ArrayList<String>();

    @SuppressWarnings("unchecked")
    Class<? extends ID> getClassForNativeClassName(final String className) {
        for (final String pkg : registeredPackages) {
            try {
                final Class<?> clazz = Class.forName(pkg + "." + className);
                if (clazz != null) return (Class<? extends ID>)clazz;
            } catch (final ClassNotFoundException e) { }
        }

        return null;
    }

    private final static Unsafe getUnsafe() {
        Unsafe inst = null;
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            inst = (Unsafe) f.get(null);
            if(inst == null) throw new NullPointerException("Unsafe.theUnsafe == null");
        } catch (Exception e) {
            throw new RuntimeException("Unable to get instance of Unsafe.", e);
        }
        return inst;
    }

    public void registerPackage(final String pkg) {
        registeredPackages.add(pkg);
    }

    /**
     * Register a subclass of NSObject to allow the native side to send
     * messages which in turn call java methods declared on the class.
     * If a native class by the same name already exists, registerClass
     * will simply return without doing anything.
     *
     * For a usage example, see the SubclassingTest.
     */
    public boolean registerUserClass(Class<? extends ID> clazz, Class<? extends NSClass> clazzClazz) {
        return subclassing.registerUserClass(clazz, clazzClazz);
    }

}
