/*
* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/


package javax.microedition.m3g;

import javax.microedition.lcdui.Graphics;
import java.util.Hashtable;
import java.util.Vector;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.qt.graphics.*;
import org.eclipse.swt.internal.qt.GCData;
import com.nokia.mj.impl.rt.support.ShutdownListener;
import com.nokia.mj.impl.rt.support.ApplicationUtils;
import com.nokia.mj.impl.nokialcdui.LCDUIInvoker;


public class Graphics3D
{
    //------------------------------------------------------------------
    // Static data
    //------------------------------------------------------------------

    public static final int ANTIALIAS   = 2;
    public static final int DITHER      = 4;
    public static final int TRUE_COLOR  = 8;

    // M3G 1.1
    public static final int OVERWRITE   = 16;

    // Singleton instances
    static Graphics3D s_instance = null;

    //------------------------------------------------------------------
    // Instance data
    //------------------------------------------------------------------

    int handle;
    int iSurfaceHandle;

    private Camera camera = null;
    private Vector lights = new Vector();

    private java.lang.Object currentTarget = null;
    private int offsetX, offsetY, hints = 0;
    private boolean depthEnabled = true;
    private Destroyer destroyer;
    private Interface iInterface;

    // this flag is for identification of image target types
    // - True for mutable off-screen images
    // - False for canvas/GameCanvas framebuffer
    private boolean iIsImageTarget;

    // this flag is for identification if MBX HW accelerator is present
    // - True - MBX is NOT present
    // - False - MBX is present
    private boolean iIsProperRenderer;

    private boolean iNativeInitialized = false;

    // Shutdown listener
    private class Destroyer implements ShutdownListener
    {
        Graphics3D target;

        Destroyer(Graphics3D g3d)
        {
            target = g3d;
            ApplicationUtils appUtils = ApplicationUtils.getInstance();
            appUtils.addShutdownListener(this);
        }

        // This method gets called when application is shuttingdown
        public void shuttingDown()
        {

            // Finalize native peer
            Platform.finalizeObject(target.handle, target.iInterface);

            // signal shutdown (set shutdown flag)
            // and remove references
            target.iInterface.signalShutdown();
            target.iInterface = null;
            target.camera = null;
            //target.s_instance = null;

            // All done, Call gc() and finalization to collect
            // remaining objects, thus zeroying liveObjects count
            // in interface instance

        }
    }

    //------------------------------------------------------------------
    // Constructor(s)
    //------------------------------------------------------------------
    public static final Graphics3D getInstance()
    {

        if (s_instance == null)
        {
            s_instance = new Graphics3D();
        }
        return s_instance;
    }

    private Graphics3D()
    {
        iInterface = Interface.getInstance();
        initNativePeer();

        // setup listener for singleton teardown
        destroyer = new Destroyer(this);
    }

    //------------------------------------------------------------------
    // Public methods
    //------------------------------------------------------------------

    /**
     */
    public void bindTarget(java.lang.Object target)
    {
        bindTarget(target, true, 0);
    }

    /**
     *
     */
    public void bindTarget(java.lang.Object target, boolean depth, int flags)
    {
        integrityCheck();
        if (currentTarget != null)
        {
            throw new IllegalStateException();
        }

        if (target == null)
        {
            throw new NullPointerException();
        }

        final int finalFlags = flags;
        final boolean finalDepth = depth;

        if (target instanceof org.eclipse.swt.graphics.GC)
        {
            Rectangle clip = ((org.eclipse.swt.graphics.GC)target).getClipping();
            final int clipW = clip.width;
            final int clipH = clip.height;
            final int clipX = clip.x;
            final int clipY = clip.y;

            if (clipW > Defs.MAX_VIEWPORT_WIDTH ||
                    clipH > Defs.MAX_VIEWPORT_HEIGHT)
            {
                throw new IllegalArgumentException();
            }

            final Object finalTarget = target;
            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    GCData gcData = ((org.eclipse.swt.graphics.GC)finalTarget).getGCData();
                    iSurfaceHandle = gcData.internalGc.getWindowSurface().getHandle();
                    iIsImageTarget = _bindGraphics(
                                         handle,
                                         iSurfaceHandle,
                                         clipX, clipY,
                                         clipW, clipH,
                                         finalDepth, finalFlags,
                                         iIsProperRenderer);
                }
            });
            currentTarget = target;
        }

        else if (target instanceof Graphics)
        {

            Graphics g = (Graphics) target;
            //Platform.sync(g);

            if (g.getClipWidth() > Defs.MAX_VIEWPORT_WIDTH ||
                    g.getClipHeight() > Defs.MAX_VIEWPORT_HEIGHT)
            {
                throw new IllegalArgumentException();
            }

            offsetX = g.getTranslateX();
            offsetY = g.getTranslateY();

            final Graphics finalG = g;

            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    LCDUIInvoker.startExternalRendering( finalG );
                    iSurfaceHandle = LCDUIInvoker.getWindowSurface(finalG).getHandle();
                    iIsImageTarget = _bindGraphics(
                                         handle,
                                         iSurfaceHandle,
                                         finalG.getClipX() + offsetX, finalG.getClipY() + offsetY,
                                         finalG.getClipWidth(), finalG.getClipHeight(),
                                         finalDepth, finalFlags,
                                         iIsProperRenderer);
                }
            });
            currentTarget = g;
        }
        else if (target instanceof Image2D)
        {
            Image2D img = (Image2D) target;

            offsetX = offsetY = 0;
            final int imageHandle = img.handle;

            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    _bindImage(handle, imageHandle, finalDepth, finalFlags);
                }
            });
            currentTarget = img;
        }
        else
        {
            throw new IllegalArgumentException();
        }

        hints = flags;
        depthEnabled = depth;
    }

    /**
     *
     */
    public void releaseTarget()
    {
        integrityCheck();
        if (currentTarget == null)
        {
            return;
        }

        if (currentTarget instanceof org.eclipse.swt.graphics.GC)
        {
            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    _releaseGraphics(handle,
                                     iSurfaceHandle, iIsImageTarget, iIsProperRenderer);
                }
            });
        }
        else if (currentTarget instanceof Graphics)
        {
                final Graphics finalG = (Graphics)currentTarget;
                Platform.executeInUIThread(
                    new M3gRunnable()
                {
                    public void doRun()
                    {
                        _releaseGraphics(handle,
                                         iSurfaceHandle, iIsImageTarget, iIsProperRenderer);
                        LCDUIInvoker.endExternalRendering( finalG );
                    }
                });
            /*
            Graphics g = (Graphics) currentTarget;

            //ToolkitInvoker invoker = ToolkitInvoker.getToolkitInvoker();

            Platform.getUIThread().syncExec(
                    new Runnable() {
                        public void run() {
                                        _releaseGraphics( handle,
                                        invoker.graphicsGetHandle(g), iIsImageTarget, iIsProperRenderer );
                                }
                        });
                        */
        }
        else if (currentTarget instanceof Image2D)
        {
            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    _releaseImage(handle);
                }
            });
        }
        else
        {
            throw new Error();
        }
        currentTarget = null;
        iSurfaceHandle = 0;
    }

    /**
     *
     */
    public void setViewport(int x, int y, int width, int height)
    {
        integrityCheck();
        if (width <= 0 || height <= 0
                || width  > Defs.MAX_VIEWPORT_DIMENSION
                || height > Defs.MAX_VIEWPORT_DIMENSION)
        {
            throw new IllegalArgumentException();
        }
        _setViewport(handle, x + offsetX, y + offsetY, width, height);
    }

    /**
     *
     */
    public void clear(Background background)
    {
        integrityCheck();
        final Background finalBackground = background;
        Platform.executeInUIThread(
            new M3gRunnable()
        {
            public void doRun()
            {
                _clear(handle, finalBackground != null ? finalBackground.handle : 0);
            }
        });
    }

    /**
     *
     */
    public void render(World world)
    {
        integrityCheck();
        final World finalWorld = world;
        Platform.executeInUIThread(
            new M3gRunnable()
        {
            public void doRun()
            {
                _renderWorld(handle, finalWorld.handle);
            }
        });
    }

    /**
     *
     */
    public void render(VertexBuffer vertices,
                       IndexBuffer primitives,
                       Appearance appearance,
                       Transform transform)
    {
        // Call rendering method with default visibility
        integrityCheck();
        render(vertices, primitives, appearance, transform, -1);
    }

    /**
     *
     */
    public void render(VertexBuffer vertices,
                       IndexBuffer primitives,
                       Appearance appearance,
                       Transform transform,
                       int scope)
    {

        // null pointer exceptions thrown automatically below
        integrityCheck();

        final VertexBuffer finalVertices = vertices;
        final IndexBuffer finalPrimitives = primitives;
        final Appearance finalAppearance = appearance;
        final Transform finalTransform = transform;
        final int finalScope = scope;

        Platform.executeInUIThread(
            new M3gRunnable()
        {
            public void doRun()
            {
                _render(handle,
                        finalVertices.handle,
                        finalPrimitives.handle,
                        finalAppearance.handle,
                        finalTransform != null ? finalTransform.matrix : null,
                        finalScope);
            }
        });
    }

    /**
     *
     */
    public void render(Node node, Transform transform)
    {
        if (!(node instanceof Mesh
                || node instanceof Sprite3D
                || node instanceof Group)
                && node != null)
        {
            throw new IllegalArgumentException();
        }
        integrityCheck();

        final Node finalNode = node;
        final Transform finalTransform = transform;

        Platform.executeInUIThread(
            new M3gRunnable()
        {
            public void doRun()
            {
                _renderNode(handle,
                            finalNode.handle,
                            finalTransform != null ? finalTransform.matrix : null);
            }
        });
    }


    public void setCamera(Camera camera, Transform transform)
    {
        integrityCheck();
        _setCamera(handle,
                   camera != null ? camera.handle : 0,
                   transform != null ? transform.matrix : null);

        this.camera = camera;
    }

    /**
     */
    public int addLight(Light light, Transform transform)
    {
        integrityCheck();
        int index = _addLight(handle,
                              light.handle,
                              transform != null ? transform.matrix : null);
        if (lights.size() < index + 1)
        {
            lights.setSize(index + 1);
        }
        lights.setElementAt(light, index);
        return index;
    }

    /**
     *
     */
    public void setLight(int index, Light light, Transform transform)
    {
        integrityCheck();
        _setLight(handle,
                  index,
                  light != null ? light.handle : 0,
                  transform != null ? transform.matrix : null);
        lights.setElementAt(light, index);
    }

    /**
     */
    public void resetLights()
    {
        integrityCheck();
        _resetLights(handle);
        lights.removeAllElements();
    }

    /**
     *
     */
    public static final Hashtable getProperties()
    {
        Hashtable props = new Hashtable();

        props.put("supportAntialiasing",          new java.lang.Boolean(
                      _isAASupported(Interface.getHandle())));
        props.put("supportTrueColor",             new java.lang.Boolean(Defs.supportTrueColor));
        props.put("supportDithering",             new java.lang.Boolean(Defs.supportDithering));
        props.put("supportMipmapping",            new java.lang.Boolean(Defs.supportMipmapping));
        props.put("supportPerspectiveCorrection", new java.lang.Boolean(Defs.supportPerspectiveCorrection));
        props.put("supportLocalCameraLighting",   new java.lang.Boolean(Defs.supportLocalCameraLighting));
        props.put("maxLights",                    new java.lang.Integer(Defs.MAX_LIGHTS));
        props.put("maxViewportWidth",             new java.lang.Integer(Defs.MAX_VIEWPORT_WIDTH));
        props.put("maxViewportHeight",            new java.lang.Integer(Defs.MAX_VIEWPORT_HEIGHT));
        props.put("maxViewportDimension",         new java.lang.Integer(Defs.MAX_VIEWPORT_DIMENSION));
        props.put("maxTextureDimension",          new java.lang.Integer(Defs.MAX_TEXTURE_DIMENSION));
        props.put("maxSpriteCropDimension",       new java.lang.Integer(Defs.MAX_TEXTURE_DIMENSION));
        props.put("numTextureUnits",              new java.lang.Integer(Defs.NUM_TEXTURE_UNITS));
        props.put("maxTransformsPerVertex",       new java.lang.Integer(Defs.MAX_TRANSFORMS_PER_VERTEX));

        // Extra properties
        props.put("m3gRelease",                   new java.lang.String("04_wk49"));

        return props;
    }

    /**
     *
     */
    public void setDepthRange(float near, float far)
    {
        integrityCheck();
        _setDepthRange(handle, near, far);
    }

    // M3G 1.1

    public Camera getCamera(Transform transform)
    {
        integrityCheck();
        if (transform != null)
        {
            _getViewTransform(handle, transform.matrix);
        }

        return (Camera) Object3D.getInstance(_getCamera(handle));
    }

    public float getDepthRangeFar()
    {
        integrityCheck();
        return _getDepthRangeFar(handle);
    }

    public float getDepthRangeNear()
    {
        integrityCheck();
        return _getDepthRangeNear(handle);
    }

    public Light getLight(int index, Transform transform)
    {
        integrityCheck();
        if (index < 0 || index >= _getLightCount(handle))
        {
            throw new IndexOutOfBoundsException();
        }

        return (Light) Object3D.getInstance(_getLightTransform(handle,
                                            index,
                                            transform != null ? transform.matrix : null));
    }

    public int getLightCount()
    {
        integrityCheck();
        return _getLightCount(handle);
    }

    public java.lang.Object getTarget()
    {
        return currentTarget;
    }

    public int getViewportHeight()
    {
        integrityCheck();
        return _getViewportHeight(handle);
    }

    public int getViewportWidth()
    {
        integrityCheck();
        return _getViewportWidth(handle);
    }

    public int getViewportX()
    {
        integrityCheck();
        return _getViewportX(handle) - offsetX;
    }

    public int getViewportY()
    {
        integrityCheck();
        return _getViewportY(handle) - offsetY;
    }

    public int getHints()
    {
        return hints;
    }

    public boolean isDepthBufferEnabled()
    {
        return depthEnabled;
    }

    // M3G 1.1 getters END

    //------------------------------------------------------------------
    // Private methods
    //------------------------------------------------------------------

    private void integrityCheck()
    {
        if (iInterface == null)
        {
            throw new RuntimeException("Graphics3D closed");
        }
        if (!iNativeInitialized)
        {
            // If native interface cannot be initialized we cannot recover from it
            if (!initNativePeer())
            {
                throw new Error("UI thread not available");
            }
        }
    }

    /**
     * Initializes native peer
     * @return true if native interface was succesfully inialized otherwise false
     */
    private boolean initNativePeer()
    {
        if (iNativeInitialized)
        {
            return true;
        }
        if (iInterface.isFullyInitialized() && Platform.uiThreadAvailable())
        {
            handle = _ctor(iInterface.getHandle());
            _addRef(handle);

            Platform.executeInUIThread(
                new M3gRunnable()
            {
                public void doRun()
                {
                    iIsProperRenderer = _isProperRenderer();
                }
            });
            iNativeInitialized = true;
            return true;
        }
        else
        {
            return false;
        }
    }

    //------------------------------------------------------------------
    // Native implementation methods
    //------------------------------------------------------------------
    private native static int _ctor(int hInterface);
    private native static void _addRef(int hObject);
    private native static int _addLight(int handle,
                                        int hLight,
                                        byte[] transform);
    private native static boolean _bindGraphics(int handle,
            int surfaceHandle,
            int clipX, int clipY,
            int clipW, int clipH,
            boolean depth,
            int hintBits,
            boolean aIsProperRenderer);
    private native static void _bindImage(int handle, int imgHandle, boolean depth, int hintBits);
    private native static void _releaseGraphics(int handle,
            int surfaceHandle,
            boolean aIsImageTarget,
            boolean aIsProperRenderer);
    private native static void _releaseImage(int handle);
    private native static void _resetLights(int handle);
    private native static void _clear(int handle, int hBackground);
    private native static void _render(int handle,
                                       int hVtxBuffer,
                                       int hIdxBuffer,
                                       int hAppearance,
                                       byte[] transform,
                                       int scope);
    private native static void _renderNode(int handle, int hNode, byte[] transform);
    private native static void _renderWorld(int handle, int hWorld);
    private native static void _setCamera(int handle,
                                          int hCamera,
                                          byte[] transform);
    private native static void _setViewport(int handle,
                                            int x, int y,
                                            int width, int height);
    private native static void _setLight(int handle,
                                         int index,
                                         int hLight,
                                         byte[] transform);
    private native static void _setDepthRange(int handle,
            float near,
            float far);

    // M3G 1.1
    // Maintenance release getters

    private native static void _getViewTransform(int handle,
            byte[] transform);
    private native static int _getCamera(int handle);
    private native static int _getLightTransform(int handle,
            int index,
            byte[] transform);
    private native static int _getLightCount(int handle);
    private native static float _getDepthRangeNear(int handle);
    private native static float _getDepthRangeFar(int handle);
    private native static int _getViewportX(int handle);
    private native static int _getViewportY(int handle);
    private native static int _getViewportWidth(int handle);
    private native static int _getViewportHeight(int handle);

    /* Statistics support, MUST be disabled in official releases! */
    /*
        public native static int getStatistics(int[] statistics);
    */
    private native static boolean _isAASupported(int handle);
    private native static boolean _isProperRenderer();
}
