/*
 * Copyright (c) 1997, 2015, 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.awt.image.BufferedImage;
import java.awt.AlphaComposite;
import java.awt.Rectangle;
import sun.awt.image.BufImgSurfaceData;
import sun.awt.util.ThreadGroupUtils;
import sun.java2d.SurfaceData;
import sun.java2d.pipe.Region;
import java.lang.reflect.Field;
import java.util.StringTokenizer;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.io.PrintStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.security.AccessController;
import java.security.PrivilegedAction;

import sun.misc.ManagedLocalsThread;
import sun.security.action.GetPropertyAction;

/**
 * defines interface for primitives which can be placed into
 * the graphic component manager framework
 */
public abstract class GraphicsPrimitive {

    protected static interface GeneralBinaryOp {
        /**
         * This method allows the setupGeneralBinaryOp method to set
         * the converters into the General version of the Primitive.
         */
        public void setPrimitives(Blit srcconverter,
                                  Blit dstconverter,
                                  GraphicsPrimitive genericop,
                                  Blit resconverter);

        /**
         * These 4 methods are implemented automatically for any
         * GraphicsPrimitive.  They are used by setupGeneralBinaryOp
         * to retrieve the information needed to find the right
         * converter primitives.
         */
        public SurfaceType getSourceType();
        public CompositeType getCompositeType();
        public SurfaceType getDestType();
        public String getSignature();
        public int getPrimTypeID();
    }

    protected static interface GeneralUnaryOp {
        /**
         * This method allows the setupGeneralUnaryOp method to set
         * the converters into the General version of the Primitive.
         */
        public void setPrimitives(Blit dstconverter,
                                  GraphicsPrimitive genericop,
                                  Blit resconverter);

        /**
         * These 3 methods are implemented automatically for any
         * GraphicsPrimitive.  They are used by setupGeneralUnaryOp
         * to retrieve the information needed to find the right
         * converter primitives.
         */
        public CompositeType getCompositeType();
        public SurfaceType getDestType();
        public String getSignature();
        public int getPrimTypeID();
    }

    /**
    *  INSTANCE DATA MEMBERS DESCRIBING CHARACTERISTICS OF THIS PRIMITIVE
    **/

    // Making these be instance data members (instead of virtual methods
    // overridden by subclasses) is actually cheaper, since each class
    // is a singleton.  As instance data members with final accessors,
    // accesses can be inlined.
    private String methodSignature;
    private int uniqueID;
    private static int unusedPrimID = 1;

    private SurfaceType sourceType;
    private CompositeType compositeType;
    private SurfaceType destType;

    private long pNativePrim;   // Native blit loop info

    public synchronized static final int makePrimTypeID() {
        if (unusedPrimID > 255) {
            throw new InternalError("primitive id overflow");
        }
        return unusedPrimID++;
    }

    public synchronized static final int makeUniqueID(int primTypeID,
                                                      SurfaceType src,
                                                      CompositeType cmp,
                                                      SurfaceType dst)
    {
        return (primTypeID << 24) |
            (dst.getUniqueID() << 16) |
            (cmp.getUniqueID() << 8)  |
            (src.getUniqueID());
    }

    /**
     * Create a new GraphicsPrimitive with all of the required
     * descriptive information.
     */
    protected GraphicsPrimitive(String methodSignature,
                                int primTypeID,
                                SurfaceType sourceType,
                                CompositeType compositeType,
                                SurfaceType destType)
    {
        this.methodSignature = methodSignature;
        this.sourceType = sourceType;
        this.compositeType = compositeType;
        this.destType = destType;

        if(sourceType == null || compositeType == null || destType == null) {
            this.uniqueID = primTypeID << 24;
        } else {
            this.uniqueID = GraphicsPrimitive.makeUniqueID(primTypeID,
                                                           sourceType,
                                                           compositeType,
                                                           destType);
        }
    }

    /**
     * Create a new GraphicsPrimitive for native invocation
     * with all of the required descriptive information.
     */
    protected GraphicsPrimitive(long pNativePrim,
                                String methodSignature,
                                int primTypeID,
                                SurfaceType sourceType,
                                CompositeType compositeType,
                                SurfaceType destType)
    {
        this.pNativePrim = pNativePrim;
        this.methodSignature = methodSignature;
        this.sourceType = sourceType;
        this.compositeType = compositeType;
        this.destType = destType;

        if(sourceType == null || compositeType == null || destType == null) {
            this.uniqueID = primTypeID << 24;
        } else {
            this.uniqueID = GraphicsPrimitive.makeUniqueID(primTypeID,
                                                           sourceType,
                                                           compositeType,
                                                           destType);
        }
    }

    /**
    *   METHODS TO DESCRIBE THE SURFACES PRIMITIVES
    *   CAN OPERATE ON AND THE FUNCTIONALITY THEY IMPLEMENT
    **/

    /**
     * Gets instance ID of this graphics primitive.
     *
     * Instance ID is comprised of four distinct ids (ORed together)
     * that uniquely identify each instance of a GraphicsPrimitive
     * object. The four ids making up instance ID are:
     * 1. primitive id - identifier shared by all primitives of the
     * same type (eg. all Blits have the same primitive id)
     * 2. sourcetype id - identifies source surface type
     * 3. desttype id - identifies destination surface type
     * 4. compositetype id - identifies composite used
     *
     * @return instance ID
     */
    public final int getUniqueID() {
        return uniqueID;
    }

    /**
     */
    public final String getSignature() {
        return methodSignature;
    }

    /**
     * Gets unique id for this GraphicsPrimitive type.
     *
     * This id is used to identify the TYPE of primitive (Blit vs. BlitBg)
     * as opposed to INSTANCE of primitive.
     *
     * @return primitive ID
     */
    public final int getPrimTypeID() {
        return uniqueID >>> 24;
    }

    /**
     */
    public final long getNativePrim() {
        return pNativePrim;
    }

    /**
     */
    public final SurfaceType getSourceType() {
        return sourceType;
    }

    /**
     */
    public final CompositeType getCompositeType() {
        return compositeType;
    }

    /**
     */
    public final SurfaceType getDestType() {
        return destType;
    }

    /**
     * Return true if this primitive can be used for the given signature
     * surfaces, and composite.
     *
     * @param signature The signature of the given operation.  Must be
     *          == (not just .equals) the signature string given by the
     *          abstract class that declares the operation.
     * @param srctype The surface type for the source of the operation
     * @param comptype The composite type for the operation
     * @param dsttype The surface type for the destination of the operation
     */
    public final boolean satisfies(String signature,
                                   SurfaceType srctype,
                                   CompositeType comptype,
                                   SurfaceType dsttype)
    {
        if (signature != methodSignature) {
            return false;
        }
        while (true) {
            if (srctype == null) {
                return false;
            }
            if (srctype.equals(sourceType)) {
                break;
            }
            srctype = srctype.getSuperType();
        }
        while (true) {
            if (comptype == null) {
                return false;
            }
            if (comptype.equals(compositeType)) {
                break;
            }
            comptype = comptype.getSuperType();
        }
        while (true) {
            if (dsttype == null) {
                return false;
            }
            if (dsttype.equals(destType)) {
                break;
            }
            dsttype = dsttype.getSuperType();
        }
        return true;
    }

    //
    // A version of satisfies used for regression testing
    //
    final boolean satisfiesSameAs(GraphicsPrimitive other) {
        return (methodSignature == other.methodSignature &&
                sourceType.equals(other.sourceType) &&
                compositeType.equals(other.compositeType) &&
                destType.equals(other.destType));
    }

    public abstract GraphicsPrimitive makePrimitive(SurfaceType srctype,
                                                    CompositeType comptype,
                                                    SurfaceType dsttype);

    public abstract GraphicsPrimitive traceWrap();

    static HashMap<Object, int[]> traceMap;

    public static int traceflags;
    public static String tracefile;
    public static PrintStream traceout;

    public static final int TRACELOG = 1;
    public static final int TRACETIMESTAMP = 2;
    public static final int TRACECOUNTS = 4;

    static {
        GetPropertyAction gpa = new GetPropertyAction("sun.java2d.trace");
        String trace = AccessController.doPrivileged(gpa);
        if (trace != null) {
            boolean verbose = false;
            int traceflags = 0;
            StringTokenizer st = new StringTokenizer(trace, ",");
            while (st.hasMoreTokens()) {
                String tok = st.nextToken();
                if (tok.equalsIgnoreCase("count")) {
                    traceflags |= GraphicsPrimitive.TRACECOUNTS;
                } else if (tok.equalsIgnoreCase("log")) {
                    traceflags |= GraphicsPrimitive.TRACELOG;
                } else if (tok.equalsIgnoreCase("timestamp")) {
                    traceflags |= GraphicsPrimitive.TRACETIMESTAMP;
                } else if (tok.equalsIgnoreCase("verbose")) {
                    verbose = true;
                } else if (tok.regionMatches(true, 0, "out:", 0, 4)) {
                    tracefile = tok.substring(4);
                } else {
                    if (!tok.equalsIgnoreCase("help")) {
                        System.err.println("unrecognized token: "+tok);
                    }
                    System.err.println("usage: -Dsun.java2d.trace="+
                                       "[log[,timestamp]],[count],"+
                                       "[out:<filename>],[help],[verbose]");
                }
            }
            if (verbose) {
                System.err.print("GraphicsPrimitive logging ");
                if ((traceflags & GraphicsPrimitive.TRACELOG) != 0) {
                    System.err.println("enabled");
                    System.err.print("GraphicsPrimitive timetamps ");
                    if ((traceflags & GraphicsPrimitive.TRACETIMESTAMP) != 0) {
                        System.err.println("enabled");
                    } else {
                        System.err.println("disabled");
                    }
                } else {
                    System.err.println("[and timestamps] disabled");
                }
                System.err.print("GraphicsPrimitive invocation counts ");
                if ((traceflags & GraphicsPrimitive.TRACECOUNTS) != 0) {
                    System.err.println("enabled");
                } else {
                    System.err.println("disabled");
                }
                System.err.print("GraphicsPrimitive trace output to ");
                if (tracefile == null) {
                    System.err.println("System.err");
                } else {
                    System.err.println("file '"+tracefile+"'");
                }
            }
            GraphicsPrimitive.traceflags = traceflags;
        }
    }

    public static boolean tracingEnabled() {
        return (traceflags != 0);
    }

    private static PrintStream getTraceOutputFile() {
        if (traceout == null) {
            if (tracefile != null) {
                FileOutputStream o = AccessController.doPrivileged(
                    new PrivilegedAction<FileOutputStream>() {
                        public FileOutputStream run() {
                            try {
                                return new FileOutputStream(tracefile);
                            } catch (FileNotFoundException e) {
                                return null;
                            }
                        }
                    });
                if (o != null) {
                    traceout = new PrintStream(o);
                } else {
                    traceout = System.err;
                }
            } else {
                traceout = System.err;
            }
        }
        return traceout;
    }

    public static class TraceReporter implements Runnable {
        public static void setShutdownHook() {
            AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                TraceReporter t = new TraceReporter();
                Thread thread = new ManagedLocalsThread(
                        ThreadGroupUtils.getRootThreadGroup(), t);
                thread.setContextClassLoader(null);
                Runtime.getRuntime().addShutdownHook(thread);
                return null;
            });
        }

        public void run() {
            PrintStream ps = getTraceOutputFile();
            Iterator<Map.Entry<Object, int[]>> iterator =
                traceMap.entrySet().iterator();
            long total = 0;
            int numprims = 0;
            while (iterator.hasNext()) {
                Map.Entry<Object, int[]> me = iterator.next();
                Object prim = me.getKey();
                int[] count = me.getValue();
                if (count[0] == 1) {
                    ps.print("1 call to ");
                } else {
                    ps.print(count[0]+" calls to ");
                }
                ps.println(prim);
                numprims++;
                total += count[0];
            }
            if (numprims == 0) {
                ps.println("No graphics primitives executed");
            } else if (numprims > 1) {
                ps.println(total+" total calls to "+
                           numprims+" different primitives");
            }
        }
    }

    public synchronized static void tracePrimitive(Object prim) {
        if ((traceflags & TRACECOUNTS) != 0) {
            if (traceMap == null) {
                traceMap = new HashMap<>();
                TraceReporter.setShutdownHook();
            }
            int[] o = traceMap.get(prim);
            if (o == null) {
                o = new int[1];
                traceMap.put(prim, o);
            }
            o[0]++;
        }
        if ((traceflags & TRACELOG) != 0) {
            PrintStream ps = getTraceOutputFile();
            if ((traceflags & TRACETIMESTAMP) != 0) {
                ps.print(System.currentTimeMillis()+": ");
            }
            ps.println(prim);
        }
    }

    protected void setupGeneralBinaryOp(GeneralBinaryOp gbo) {
        int primID = gbo.getPrimTypeID();
        String methodSignature = gbo.getSignature();
        SurfaceType srctype = gbo.getSourceType();
        CompositeType comptype = gbo.getCompositeType();
        SurfaceType dsttype = gbo.getDestType();
        Blit convertsrc, convertdst, convertres;
        GraphicsPrimitive performop;

        convertsrc = createConverter(srctype, SurfaceType.IntArgb);
        performop = GraphicsPrimitiveMgr.locatePrim(primID,
                                                    SurfaceType.IntArgb,
                                                    comptype, dsttype);
        if (performop != null) {
            convertdst = null;
            convertres = null;
        } else {
            performop = getGeneralOp(primID, comptype);
            if (performop == null) {
                throw new InternalError("Cannot construct general op for "+
                                        methodSignature+" "+comptype);
            }
            convertdst = createConverter(dsttype, SurfaceType.IntArgb);
            convertres = createConverter(SurfaceType.IntArgb, dsttype);
        }

        gbo.setPrimitives(convertsrc, convertdst, performop, convertres);
    }

    protected void setupGeneralUnaryOp(GeneralUnaryOp guo) {
        int primID = guo.getPrimTypeID();
        String methodSignature = guo.getSignature();
        CompositeType comptype = guo.getCompositeType();
        SurfaceType dsttype = guo.getDestType();

        Blit convertdst = createConverter(dsttype, SurfaceType.IntArgb);
        GraphicsPrimitive performop = getGeneralOp(primID, comptype);
        Blit convertres = createConverter(SurfaceType.IntArgb, dsttype);
        if (convertdst == null || performop == null || convertres == null) {
            throw new InternalError("Cannot construct binary op for "+
                                    comptype+" "+dsttype);
        }

        guo.setPrimitives(convertdst, performop, convertres);
    }

    protected static Blit createConverter(SurfaceType srctype,
                                          SurfaceType dsttype)
    {
        if (srctype.equals(dsttype)) {
            return null;
        }
        Blit cv = Blit.getFromCache(srctype, CompositeType.SrcNoEa, dsttype);
        if (cv == null) {
            throw new InternalError("Cannot construct converter for "+
                                    srctype+"=>"+dsttype);
        }
        return cv;
    }

    protected static SurfaceData convertFrom(Blit ob, SurfaceData srcData,
                                             int srcX, int srcY, int w, int h,
                                             SurfaceData dstData)
    {
        return convertFrom(ob, srcData,
                           srcX, srcY, w, h, dstData,
                           BufferedImage.TYPE_INT_ARGB);
    }

    protected static SurfaceData convertFrom(Blit ob, SurfaceData srcData,
                                             int srcX, int srcY, int w, int h,
                                             SurfaceData dstData, int type)
    {
        if (dstData != null) {
            Rectangle r = dstData.getBounds();
            if (w > r.width || h > r.height) {
                dstData = null;
            }
        }
        if (dstData == null) {
            BufferedImage dstBI = new BufferedImage(w, h, type);
            dstData = BufImgSurfaceData.createData(dstBI);
        }
        ob.Blit(srcData, dstData, AlphaComposite.Src, null,
                srcX, srcY, 0, 0, w, h);
        return dstData;
    }

    protected static void convertTo(Blit ob,
                                    SurfaceData srcImg, SurfaceData dstImg,
                                    Region clip,
                                    int dstX, int dstY, int w, int h)
    {
        if (ob != null) {
            ob.Blit(srcImg, dstImg, AlphaComposite.Src, clip,
                    0, 0, dstX, dstY, w, h);
        }
    }

    protected static GraphicsPrimitive getGeneralOp(int primID,
                                                    CompositeType comptype)
    {
        return GraphicsPrimitiveMgr.locatePrim(primID,
                                               SurfaceType.IntArgb,
                                               comptype,
                                               SurfaceType.IntArgb);
    }

    public static String simplename(Field[] fields, Object o) {
        for (int i = 0; i < fields.length; i++) {
            Field f = fields[i];
            try {
                if (o == f.get(null)) {
                    return f.getName();
                }
            } catch (Exception e) {
            }
        }
        return "\""+o.toString()+"\"";
    }

    public static String simplename(SurfaceType st) {
        return simplename(SurfaceType.class.getDeclaredFields(), st);
    }

    public static String simplename(CompositeType ct) {
        return simplename(CompositeType.class.getDeclaredFields(), ct);
    }

    private String cachedname;

    public String toString() {
        if (cachedname == null) {
            String sig = methodSignature;
            int index = sig.indexOf('(');
            if (index >= 0) {
                sig = sig.substring(0, index);
            }
            cachedname = (getClass().getName()+"::"+
                          sig+"("+
                          simplename(sourceType)+", "+
                          simplename(compositeType)+", "+
                          simplename(destType)+")");
        }
        return cachedname;
    }
}
