/*
 * Copyright (c) 1997, 2021, 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.
 */

/*
 * @author Charlton Innovations, Inc.
 */

package sun.java2d.loops;

import java.util.Comparator;
import java.util.Arrays;
import sun.java2d.SunGraphics2D;

/**
 *   GraphicsComponentMgr provides services to
 *   1. register primitives for later use
 *   2. locate an instance of a primitive based on characteristics
 */
public final class GraphicsPrimitiveMgr {

    private static final boolean debugTrace = false;
    private static GraphicsPrimitive[] primitives;
    private static boolean needssort = true;

    private static native void initIDs(Class<?> GP, Class<?> ST, Class<?> CT,
                                       Class<?> SG2D, Class<?> Color, Class<?> AT,
                                       Class<?> XORComp, Class<?> AlphaComp,
                                       Class<?> Path2D, Class<?> Path2DFloat,
                                       Class<?> SHints);
    private static native void registerNativeLoops();

    static {
        initIDs(GraphicsPrimitive.class,
                SurfaceType.class,
                CompositeType.class,
                SunGraphics2D.class,
                java.awt.Color.class,
                java.awt.geom.AffineTransform.class,
                XORComposite.class,
                java.awt.AlphaComposite.class,
                java.awt.geom.Path2D.class,
                java.awt.geom.Path2D.Float.class,
                sun.awt.SunHints.class);
        CustomComponent.register();
        GeneralRenderer.register();
        registerNativeLoops();
    }

    private static class PrimitiveSpec {
        public int uniqueID;
    }

    private static Comparator<GraphicsPrimitive> primSorter =
            new Comparator<GraphicsPrimitive>() {
        public int compare(GraphicsPrimitive o1, GraphicsPrimitive o2) {
            int id1 = o1.getUniqueID();
            int id2 = o2.getUniqueID();

            return Integer.compare(id1, id2);
        }
    };

    private static Comparator<Object> primFinder = new Comparator<Object>() {
        public int compare(Object o1, Object o2) {
            int id1 = ((GraphicsPrimitive) o1).getUniqueID();
            int id2 = ((PrimitiveSpec) o2).uniqueID;

            return Integer.compare(id1, id2);
        }
    };

    /**
     * Ensure that no one can instantiate this class.
     */
    private GraphicsPrimitiveMgr() {
    }

    public static synchronized void register(GraphicsPrimitive[] newPrimitives)
    {
        GraphicsPrimitive[] devCollection = primitives;
        int oldSize = 0;
        int newSize = newPrimitives.length;
        if (debugTrace) {
            writeLog("Registering " + newSize + " primitives");
            for (int i = 0; i < newSize; i++) {
                writeLog(newPrimitives[i].toString());
            }
        }
        if (devCollection != null) {
            oldSize = devCollection.length;
        }
        GraphicsPrimitive[] temp = new GraphicsPrimitive[oldSize + newSize];
        if (devCollection != null) {
            System.arraycopy(devCollection, 0, temp, 0, oldSize);
        }
        System.arraycopy(newPrimitives, 0, temp, oldSize, newSize);
        needssort = true;
        primitives = temp;
    }

    public static synchronized GraphicsPrimitive locate(int primTypeID,
                                                        SurfaceType dsttype)
    {
        return locate(primTypeID,
                      SurfaceType.OpaqueColor,
                      CompositeType.Src,
                      dsttype);
    }

    public static synchronized GraphicsPrimitive locate(int primTypeID,
                                                        SurfaceType srctype,
                                                        CompositeType comptype,
                                                        SurfaceType dsttype)
    {
        /*
          System.out.println("Looking for:");
          System.out.println("    method: "+signature);
          System.out.println("    from:   "+srctype);
          System.out.println("    by:     "+comptype);
          System.out.println("    to:     "+dsttype);
        */
        GraphicsPrimitive prim = locatePrim(primTypeID,
                                            srctype, comptype, dsttype);

        if (prim == null) {
            //System.out.println("Trying general loop");
            prim = GeneralPrimitives.locate(primTypeID);
            if (prim != null) {
                prim = prim.makePrimitive(srctype, comptype, dsttype);
                if (prim != null && GraphicsPrimitive.traceflags != 0) {
                    prim = prim.traceWrap();
                }
            }
        }
        return prim;
    }

    public static synchronized GraphicsPrimitive
        locatePrim(int primTypeID,
                   SurfaceType srctype,
                   CompositeType comptype,
                   SurfaceType dsttype)
    {
        /*
          System.out.println("Looking for:");
          System.out.println("    method: "+signature);
          System.out.println("    from:   "+srctype);
          System.out.println("    by:     "+comptype);
          System.out.println("    to:     "+dsttype);
        */
        SurfaceType src, dst;
        CompositeType cmp;
        GraphicsPrimitive prim;
        PrimitiveSpec spec = new PrimitiveSpec();

        for (dst = dsttype; dst != null; dst = dst.getSuperType()) {
            for (src = srctype; src != null; src = src.getSuperType()) {
                for (cmp = comptype; cmp != null; cmp = cmp.getSuperType()) {
                    /*
                      System.out.println("Trying:");
                      System.out.println("    method: "+spec.methodSignature);
                      System.out.println("    from:   "+spec.sourceType);
                      System.out.println("    by:     "+spec.compType);
                      System.out.println("    to:     "+spec.destType);
                    */

                    spec.uniqueID =
                        GraphicsPrimitive.makeUniqueID(primTypeID, src, cmp, dst);
                    prim = locate(spec);
                    if (prim != null) {
                        //System.out.println("<GPMgr> Found: " + prim + " in " + i + " steps");
                        return prim;
                    }
                }
            }
        }
        return null;
    }

    private static GraphicsPrimitive locate(PrimitiveSpec spec) {
        if (needssort) {
            if (GraphicsPrimitive.traceflags != 0) {
                for (int i = 0; i < primitives.length; i++) {
                    primitives[i] = primitives[i].traceWrap();
                }
            }
            Arrays.sort(primitives, primSorter);
            needssort = false;
        }
        GraphicsPrimitive[] devCollection = primitives;
        if (devCollection == null) {
            return null;
        }
        int index = Arrays.binarySearch(devCollection, spec, primFinder);
        if (index >= 0) {
            GraphicsPrimitive prim = devCollection[index];
            if (prim instanceof GraphicsPrimitiveProxy) {
                prim = ((GraphicsPrimitiveProxy) prim).instantiate();
                devCollection[index] = prim;
                if (debugTrace) {
                    writeLog("Instantiated graphics primitive " + prim);
                }
            }
            if (debugTrace) {
                writeLog("Lookup found[" + index + "]["+ prim + "]");
            }
            return prim;
        }
        if (debugTrace) {
            writeLog("Lookup found nothing for:");
            writeLog(" " + spec.uniqueID);
        }
        return null;
    }

    private static void writeLog(String str) {
        if (debugTrace) {
            System.err.println(str);
        }
    }

    /**
     * A holder for general primitives to avoid circular dependencies
     * between GraphicsPrimitiveMgr and Blit/etc classes.
     */
    final static class GeneralPrimitives {

        private static GraphicsPrimitive[] primitives;

        /**
         * Registers the general loop which will be used to produce specific
         * primitives by the {@link GraphicsPrimitive#makePrimitive} function.
         *
         * @param gen the graphics primitive to be registered as the general loop
         */
        static synchronized void register(GraphicsPrimitive gen) {
            if (primitives == null) {
                primitives = new GraphicsPrimitive[]{gen};
                return;
            }
            int len = primitives.length;
            GraphicsPrimitive[] newGen = new GraphicsPrimitive[len + 1];
            System.arraycopy(primitives, 0, newGen, 0, len);
            newGen[len] = gen;
            primitives = newGen;
        }

        static synchronized GraphicsPrimitive locate(int primTypeID) {
            if (primitives == null) {
                return null;
            }
            for (int i = 0; i < primitives.length; i++) {
                GraphicsPrimitive prim = primitives[i];
                if (prim.getPrimTypeID() == primTypeID) {
                    return prim;
                }
            }
            return null;
        }
    }

    /**
     * Test that all of the GraphicsPrimitiveProxy objects actually
     * resolve to something.  Throws a RuntimeException if anything
     * is wrong, an has no effect if all is well.
     */
    // This is only really meant to be called from GraphicsPrimitiveProxyTest
    // in the regression tests directory, but it has to be here because
    // it needs access to a private data structure.  It is not very
    // big, though.
    public static void testPrimitiveInstantiation() {
        testPrimitiveInstantiation(false);
    }

    public static void testPrimitiveInstantiation(boolean verbose) {
        int resolved = 0;
        int unresolved = 0;
        GraphicsPrimitive[] prims = primitives;
        for (int j = 0; j < prims.length; j++) {
            GraphicsPrimitive p = prims[j];
            if (p instanceof GraphicsPrimitiveProxy) {
                GraphicsPrimitive r = ((GraphicsPrimitiveProxy) p).instantiate();
                if (!r.getSignature().equals(p.getSignature()) ||
                    r.getUniqueID() != p.getUniqueID()) {
                    System.out.println("r.getSignature == "+r.getSignature());
                    System.out.println("r.getUniqueID == " + r.getUniqueID());
                    System.out.println("p.getSignature == "+p.getSignature());
                    System.out.println("p.getUniqueID == " + p.getUniqueID());
                    throw new RuntimeException("Primitive " + p
                                               + " returns wrong signature for "
                                               + r.getClass());
                }
                // instantiate checks that p.satisfiesSameAs(r)
                unresolved++;
                p = r;
                if (verbose) {
                    System.out.println(p);
                }
            } else {
                if (verbose) {
                    System.out.println(p + " (not proxied).");
                }
                resolved++;
            }
        }
        System.out.println(resolved+
                           " graphics primitives were not proxied.");
        System.out.println(unresolved+
                           " proxied graphics primitives resolved correctly.");
        System.out.println(resolved+unresolved+
                           " total graphics primitives");
    }

    public static void main(String[] argv) {
        // REMIND: Should trigger loading of platform primitives somehow...
        if (needssort) {
            Arrays.sort(primitives, primSorter);
            needssort = false;
        }
        testPrimitiveInstantiation(argv.length > 0);
    }
}
