/*
 * Copyright (c) 2011, 2017, 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 sun.font;

import java.awt.Rectangle;
import java.awt.geom.*;
import java.util.*;

import sun.awt.SunHints;

public final class CStrike extends PhysicalStrike {

    // Creates the native strike
    private static native long createNativeStrikePtr(long nativeFontPtr,
                                                     double[] glyphTx,
                                                     double[] invDevTxMatrix,
                                                     int aaHint,
                                                     int fmHint);

    // Disposes the native strike
    private static native void disposeNativeStrikePtr(long nativeStrikePtr);

    // Creates a StrikeMetrics from the underlying native system fonts
    private static native StrikeMetrics getFontMetrics(long nativeStrikePtr);

    // Returns native struct pointers used by the Sun 2D Renderer
    private static native void getGlyphImagePtrsNative(long nativeStrikePtr,
                                                       long[] glyphInfos,
                                                       int[] uniCodes, int len);

    // Returns the advance give a glyph code. It should be used only
    // when the glyph code belongs to the CFont passed in.
    private static native float getNativeGlyphAdvance(long nativeStrikePtr,
                                                      int glyphCode);

    // Returns the outline shape of a glyph
    private static native GeneralPath getNativeGlyphOutline(long nativeStrikePtr,
                                                            int glyphCode,
                                                            double x,
                                                            double y);

    // returns the bounding rect for a glyph
    private static native void getNativeGlyphImageBounds(long nativeStrikePtr,
                                                         int glyphCode,
                                                         Rectangle2D.Float result,
                                                         double x, double y);

    private final CFont nativeFont;
    private AffineTransform invDevTx;
    private final GlyphInfoCache glyphInfoCache;
    private final GlyphAdvanceCache glyphAdvanceCache;
    private long nativeStrikePtr;

    CStrike(final CFont font, final FontStrikeDesc inDesc) {
        nativeFont = font;
        desc = inDesc;
        glyphInfoCache = new GlyphInfoCache(font, desc);
        glyphAdvanceCache = new GlyphAdvanceCache();
        disposer = glyphInfoCache;

        // Normally the device transform should be the identity transform
        // for screen operations.  The device transform only becomes
        // interesting when we are outputting between different dpi surfaces,
        // like when we are printing to postscript or use retina.
        if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) {
            try {
                invDevTx = inDesc.devTx.createInverse();
            } catch (NoninvertibleTransformException ignored) {
                // ignored, since device transforms should not be that
                // complicated, and if they are - there is nothing we can do,
                // so we won't worry about it.
            }
        }
    }

    public long getNativeStrikePtr() {
        if (nativeStrikePtr != 0) {
            return nativeStrikePtr;
        }

        final double[] glyphTx = new double[6];
        desc.glyphTx.getMatrix(glyphTx);

        final double[] invDevTxMatrix = new double[6];
        if (invDevTx == null) {
            invDevTxMatrix[0] = 1;
            invDevTxMatrix[3] = 1;
        } else {
            invDevTx.getMatrix(invDevTxMatrix);
        }

        final int aaHint = desc.aaHint;
        final int fmHint = desc.fmHint;

        synchronized (this) {
            if (nativeStrikePtr != 0) {
                return nativeStrikePtr;
            }
            nativeStrikePtr =
                createNativeStrikePtr(nativeFont.getNativeFontPtr(),
                                      glyphTx, invDevTxMatrix, aaHint, fmHint);
        }

        return nativeStrikePtr;
    }

    @SuppressWarnings("deprecation")
    protected synchronized void finalize() throws Throwable {
        if (nativeStrikePtr != 0) {
            disposeNativeStrikePtr(nativeStrikePtr);
        }
        nativeStrikePtr = 0;
    }


    @Override
    public int getNumGlyphs() {
        return nativeFont.getNumGlyphs();
    }

    @Override
    StrikeMetrics getFontMetrics() {
        if (strikeMetrics == null) {
            StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr());
            if (invDevTx != null) {
                metrics.convertToUserSpace(invDevTx);
            }
            metrics.convertToUserSpace(desc.glyphTx);
            strikeMetrics = metrics;
        }
        return strikeMetrics;
    }

    @Override
    float getGlyphAdvance(final int glyphCode) {
        return getCachedNativeGlyphAdvance(glyphCode);
    }

    @Override
    float getCodePointAdvance(final int cp) {
        return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp));
    }

    @Override
    Point2D.Float getCharMetrics(final char ch) {
        return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch));
    }

    @Override
    Point2D.Float getGlyphMetrics(final int glyphCode) {
        return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f);
    }

    Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
        GeneralPath gp = getGlyphOutline(glyphCode, 0f, 0f);
        Rectangle2D r2d = gp.getBounds2D();
        Rectangle2D.Float r2df;
        if (r2d instanceof Rectangle2D.Float) {
            r2df = (Rectangle2D.Float)r2d;
        } else {
            float x = (float)r2d.getX();
            float y = (float)r2d.getY();
            float w = (float)r2d.getWidth();
            float h = (float)r2d.getHeight();
            r2df = new Rectangle2D.Float(x, y, w, h);
        }
        return r2df;
    }

    // pt, result in device space
    void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) {
        Rectangle2D.Float floatRect = new Rectangle2D.Float();

        if (invDevTx != null) {
            invDevTx.transform(pt, pt);
        }

        getGlyphImageBounds(glyphCode, pt.x, pt.y, floatRect);

        if (floatRect.width == 0 && floatRect.height == 0) {
            result.setRect(0, 0, -1, -1);
            return;
        }

        result.setRect(floatRect.x + pt.x, floatRect.y + pt.y, floatRect.width, floatRect.height);
    }

    private void getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect) {
        getNativeGlyphImageBounds(getNativeStrikePtr(), glyphCode, floatRect, x, y);
    }

    GeneralPath getGlyphOutline(int glyphCode, float x, float y) {
        return getNativeGlyphOutline(getNativeStrikePtr(), glyphCode, x, y);
    }

    // should implement, however not called though any path that is publicly exposed
    GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
        throw new Error("not implemented yet");
    }

    // called from the Sun2D renderer
    long getGlyphImagePtr(int glyphCode) {
        synchronized (glyphInfoCache) {
            long ptr = glyphInfoCache.get(glyphCode);
            if (ptr != 0L) return ptr;

            long[] ptrs = new long[1];
            int[] codes = new int[1];
            codes[0] = glyphCode;

            getGlyphImagePtrs(codes, ptrs, 1);

            ptr = ptrs[0];
            glyphInfoCache.put(glyphCode, ptr);

            return ptr;
        }
    }

    // called from the Sun2D renderer
    void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
        synchronized (glyphInfoCache) {
            // fill the image pointer array with existing pointers
            // from the cache
            int missed = 0;
            for (int i = 0; i < len; i++) {
                int code = glyphCodes[i];

                final long ptr = glyphInfoCache.get(code);
                if (ptr != 0L) {
                    images[i] = ptr;
                } else {
                    // zero this element out, because the caller does not
                    // promise to keep it clean
                    images[i] = 0L;
                    missed++;
                }
            }

            if (missed == 0) {
                return; // horray! we got away without touching native!
            }

            // all distinct glyph codes requested (partially filled)
            final int[] filteredCodes = new int[missed];
            // indices into filteredCodes array (totally filled)
            final int[] filteredIndicies = new int[missed];

            // scan, mark, and store the requested glyph codes again to
            // send into native
            int j = 0;
            int dupes = 0;
            for (int i = 0; i < len; i++) {
                if (images[i] != 0L) continue; // already filled

                final int code = glyphCodes[i];

                // we have already promised to strike this glyph - this is
                // a dupe
                if (glyphInfoCache.get(code) == -1L) {
                    filteredIndicies[j] = -1;
                    dupes++;
                    j++;
                    continue;
                }

                // this is a distinct glyph we have not struck before, or
                // promised to strike mark this one as "promise to strike"
                // in the global cache with a -1L
                final int k = j - dupes;
                filteredCodes[k] = code;
                glyphInfoCache.put(code, -1L);
                filteredIndicies[j] = k;
                j++;
            }

            final int filteredRunLen = j - dupes;
            final long[] filteredImages = new long[filteredRunLen];

            // bulk call to fill in the distinct glyph pointers from native
            getFilteredGlyphImagePtrs(filteredImages, filteredCodes, filteredRunLen);

            // scan the requested glyph list, and fill in pointers from our
            // distinct glyph list which has been filled from native
            j = 0;
            for (int i = 0; i < len; i++) {
                if (images[i] != 0L && images[i] != -1L) {
                    continue; // already placed
                }

                // index into filteredImages array
                final int k = filteredIndicies[j];
                final int code = glyphCodes[i];
                if (k == -1L) {
                    // we should have already filled the cache with this pointer
                    images[i] = glyphInfoCache.get(code);
                } else {
                    // fill the particular glyph code request, and store
                    // in the cache
                    final long ptr = filteredImages[k];
                    images[i] = ptr;
                    glyphInfoCache.put(code, ptr);
                }

                j++;
            }
        }
    }

    private void getFilteredGlyphImagePtrs(long[] glyphInfos,
                                           int[] uniCodes, int len)
    {
        getGlyphImagePtrsNative(getNativeStrikePtr(), glyphInfos, uniCodes, len);
    }

    private float getCachedNativeGlyphAdvance(int glyphCode) {
        synchronized(glyphAdvanceCache) {
            float advance = glyphAdvanceCache.get(glyphCode);
            if (advance != 0) {
                return advance;
            }

            advance = getNativeGlyphAdvance(getNativeStrikePtr(), glyphCode);
            glyphAdvanceCache.put(glyphCode, advance);
            return advance;
        }
    }

    // This class stores glyph pointers, and is indexed based on glyph codes,
    // and negative unicode values.  See the comments in
    // CCharToGlyphMapper for more details on our glyph code strategy.
    private static class GlyphInfoCache extends CStrikeDisposer {
        private static final int FIRST_LAYER_SIZE = 256;
        private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128

        // rdar://problem/5204197
        private boolean disposed = false;

        private final long[] firstLayerCache;
        private SparseBitShiftingTwoLayerArray secondLayerCache;
        private HashMap<Integer, Long> generalCache;

        GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) {
            super(nativeFont, desc);
            firstLayerCache = new long[FIRST_LAYER_SIZE];
        }

        public synchronized long get(final int index) {
            if (index < 0) {
                if (-index < SECOND_LAYER_SIZE) {
                    // catch common unicodes
                    if (secondLayerCache == null) {
                        return 0L;
                    }
                    return secondLayerCache.get(-index);
                }
            } else {
                if (index < FIRST_LAYER_SIZE) {
                    // catch common glyphcodes
                    return firstLayerCache[index];
                }
            }

            if (generalCache == null) {
                return 0L;
            }
            final Long value = generalCache.get(Integer.valueOf(index));
            if (value == null) {
                return 0L;
            }
            return value.longValue();
        }

        public synchronized void put(final int index, final long value) {
            if (index < 0) {
                if (-index < SECOND_LAYER_SIZE) {
                    // catch common unicodes
                    if (secondLayerCache == null) {
                        secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128
                    }
                    secondLayerCache.put(-index, value);
                    return;
                }
            } else {
                if (index < FIRST_LAYER_SIZE) {
                    // catch common glyphcodes
                    firstLayerCache[index] = value;
                    return;
                }
            }

            if (generalCache == null) {
                generalCache = new HashMap<Integer, Long>();
            }

            generalCache.put(Integer.valueOf(index), Long.valueOf(value));
        }

        public synchronized void dispose() {
            // rdar://problem/5204197
            // Note that sun.font.Font2D.getStrike() actively disposes
            // cleared strikeRef.  We need to check the disposed flag to
            // prevent double frees of native resources.
            if (disposed) {
                return;
            }

            super.dispose();

            // clean out the first array
            disposeLongArray(firstLayerCache);

            // clean out the two layer arrays
            if (secondLayerCache != null) {
                final long[][] secondLayerLongArrayArray = secondLayerCache.cache;
                for (int i = 0; i < secondLayerLongArrayArray.length; i++) {
                    final long[] longArray = secondLayerLongArrayArray[i];
                    if (longArray != null) disposeLongArray(longArray);
                }
            }

            // clean up everyone else
            if (generalCache != null) {
                final Iterator<Long> i = generalCache.values().iterator();
                while (i.hasNext()) {
                    final long longValue = i.next().longValue();
                    if (longValue != -1 && longValue != 0) {
                        removeGlyphInfoFromCache(longValue);
                        StrikeCache.freeLongPointer(longValue);
                    }
                }
            }

            // rdar://problem/5204197
            // Finally, set the flag.
            disposed = true;
        }

        private static void disposeLongArray(final long[] longArray) {
            for (int i = 0; i < longArray.length; i++) {
                final long ptr = longArray[i];
                if (ptr != 0 && ptr != -1) {
                    removeGlyphInfoFromCache(ptr);
                    StrikeCache.freeLongPointer(ptr); // free's the native struct pointer
                }
            }
        }

        private static class SparseBitShiftingTwoLayerArray {
            final long[][] cache;
            final int shift;
            final int secondLayerLength;

            SparseBitShiftingTwoLayerArray(final int size, final int shift) {
                this.shift = shift;
                this.cache = new long[1 << shift][];
                this.secondLayerLength = size >> shift;
            }

            public long get(final int index) {
                final int firstIndex = index >> shift;
                final long[] firstLayerRow = cache[firstIndex];
                if (firstLayerRow == null) return 0L;
                return firstLayerRow[index - (firstIndex * (1 << shift))];
            }

            public void put(final int index, final long value) {
                final int firstIndex = index >> shift;
                long[] firstLayerRow = cache[firstIndex];
                if (firstLayerRow == null) {
                    cache[firstIndex] = firstLayerRow = new long[secondLayerLength];
                }
                firstLayerRow[index - (firstIndex * (1 << shift))] = value;
            }
        }
    }

    private static class GlyphAdvanceCache {
        private static final int FIRST_LAYER_SIZE = 256;
        private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128

        private final float[] firstLayerCache = new float[FIRST_LAYER_SIZE];
        private SparseBitShiftingTwoLayerArray secondLayerCache;
        private HashMap<Integer, Float> generalCache;

        // Empty non private constructor was added because access to this
        // class shouldn't be emulated by a synthetic accessor method.
        GlyphAdvanceCache() {
            super();
        }

        public synchronized float get(final int index) {
            if (index < 0) {
                if (-index < SECOND_LAYER_SIZE) {
                    // catch common unicodes
                    if (secondLayerCache == null) return 0;
                    return secondLayerCache.get(-index);
                }
            } else {
                if (index < FIRST_LAYER_SIZE) {
                    // catch common glyphcodes
                    return firstLayerCache[index];
                }
            }

            if (generalCache == null) return 0;
            final Float value = generalCache.get(Integer.valueOf(index));
            if (value == null) return 0;
            return value.floatValue();
        }

        public synchronized void put(final int index, final float value) {
            if (index < 0) {
                if (-index < SECOND_LAYER_SIZE) {
                    // catch common unicodes
                    if (secondLayerCache == null) {
                        secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128
                    }
                    secondLayerCache.put(-index, value);
                    return;
                }
            } else {
                if (index < FIRST_LAYER_SIZE) {
                    // catch common glyphcodes
                    firstLayerCache[index] = value;
                    return;
                }
            }

            if (generalCache == null) {
                generalCache = new HashMap<Integer, Float>();
            }

            generalCache.put(Integer.valueOf(index), Float.valueOf(value));
        }

        private static class SparseBitShiftingTwoLayerArray {
            final float[][] cache;
            final int shift;
            final int secondLayerLength;

            SparseBitShiftingTwoLayerArray(final int size, final int shift) {
                this.shift = shift;
                this.cache = new float[1 << shift][];
                this.secondLayerLength = size >> shift;
            }

            public float get(final int index) {
                final int firstIndex = index >> shift;
                final float[] firstLayerRow = cache[firstIndex];
                if (firstLayerRow == null) return 0L;
                return firstLayerRow[index - (firstIndex * (1 << shift))];
            }

            public void put(final int index, final float value) {
                final int firstIndex = index >> shift;
                float[] firstLayerRow = cache[firstIndex];
                if (firstLayerRow == null) {
                    cache[firstIndex] = firstLayerRow =
                        new float[secondLayerLength];
                }
                firstLayerRow[index - (firstIndex * (1 << shift))] = value;
            }
        }
    }
}