/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

package org.apache.fop.complexscripts.fonts;

import java.util.Arrays;
import java.util.List;

// CSOFF: LineLengthCheck

/**
 * <p>Base class implementation of glyph mapping table. This base
 * class maps glyph indices to arbitrary integers (mappping indices), and
 * is used to implement both glyph coverage and glyph class maps.</p>
 *
 * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
 */
public class GlyphMappingTable {

    /** empty mapping table */
    public static final int GLYPH_MAPPING_TYPE_EMPTY = 0;

    /** mapped mapping table */
    public static final int GLYPH_MAPPING_TYPE_MAPPED = 1;

    /** range based mapping table */
    public static final int GLYPH_MAPPING_TYPE_RANGE = 2;

    /**
     * Obtain mapping type.
     * @return mapping format type
     */
    public int getType() {
        return -1;
    }

    /**
     * Obtain mapping entries.
     * @return list of mapping entries
     */
    public List getEntries() {
        return null;
    }

    /**
     * Obtain size of mapping table, i.e., ciMax + 1, where ciMax is the maximum
     * mapping index.
     * @return size of mapping table
     */
    public int getMappingSize() {
        return 0;
    }

    /**
     * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
     * the mapping table.
     * @param gid glyph identifier (code)
     * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
     */
    public int getMappedIndex(int gid) {
        return -1;
    }

    /** empty mapping table base class */
    protected static class EmptyMappingTable extends GlyphMappingTable {
        /**
         * Construct empty mapping table.
         */
        public EmptyMappingTable() {
            this ((List) null);
        }
        /**
         * Construct empty mapping table with entries (ignored).
         * @param entries list of entries (ignored)
         */
        public EmptyMappingTable(List entries) {
        }
        /** {@inheritDoc} */
        public int getType() {
            return GLYPH_MAPPING_TYPE_EMPTY;
        }
        /** {@inheritDoc} */
        public List getEntries() {
            return new java.util.ArrayList();
        }
        /** {@inheritDoc} */
        public int getMappingSize() {
            return 0;
        }
        /** {@inheritDoc} */
        public int getMappedIndex(int gid) {
            return -1;
        }
    }

    /** mapped mapping table base class */
    protected static class MappedMappingTable extends GlyphMappingTable {
        /**
         * Construct mapped mapping table.
         */
        public MappedMappingTable() {
        }
        /** {@inheritDoc} */
        public int getType() {
            return GLYPH_MAPPING_TYPE_MAPPED;
        }
    }

    /** range mapping table base class */
    protected abstract static class RangeMappingTable extends GlyphMappingTable {
        private int[] sa;                                                // array of range (inclusive) starts
        private int[] ea;                                                // array of range (inclusive) ends
        private int[] ma;                                                // array of range mapped values
        private int miMax = -1;
        /**
         * Construct range mapping table.
         * @param entries of mapping ranges
         */
        public RangeMappingTable(List entries) {
            populate(entries);
        }
        /** {@inheritDoc} */
        public int getType() {
            return GLYPH_MAPPING_TYPE_RANGE;
        }
        /** {@inheritDoc} */
        public List getEntries() {
            List entries = new java.util.ArrayList();
            if (sa != null) {
                for (int i = 0, n = sa.length; i < n; i++) {
                    entries.add(new MappingRange(sa [ i ], ea [ i ], ma [ i ]));
                }
            }
            return entries;
        }
        /** {@inheritDoc} */
        public int getMappingSize() {
            return miMax + 1;
        }
        /** {@inheritDoc} */
        public int getMappedIndex(int gid) {
            int i;
            int mi;
            if ((i = Arrays.binarySearch(sa, gid)) >= 0) {
                mi = getMappedIndex(gid, sa [ i ], ma [ i ]);                // matches start of (some) range
            } else if ((i = -(i + 1)) == 0) {
                mi = -1;                                                        // precedes first range
            } else if (gid > ea [ --i ]) {
                mi = -1;                                                        // follows preceding (or last) range
            } else {
                mi = getMappedIndex(gid, sa [ i ], ma [ i ]);                // intersects (some) range
            }
            return mi;
        }
        /**
         * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
         * the mapping table.
         * @param gid glyph identifier (code)
         * @param s start of range
         * @param m mapping value
         * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
         */
        public abstract int getMappedIndex(int gid, int s, int m);
        private void populate(List entries) {
            int i = 0;
            int n = entries.size();
            int gidMax = -1;
            int miMax = -1;
            int[] sa = new int [ n ];
            int[] ea = new int [ n ];
            int[] ma = new int [ n ];
            for (Object o : entries) {
                if (o instanceof MappingRange) {
                    MappingRange r = (MappingRange) o;
                    int gs = r.getStart();
                    int ge = r.getEnd();
                    int mi = r.getIndex();
                    if ((gs < 0) || (gs > 65535)) {
                        throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: bad start index");
                    } else if ((ge < 0) || (ge > 65535)) {
                        throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: bad end index");
                    } else if (gs > ge) {
                        throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index");
                    } else if (gs < gidMax) {
                        throw new AdvancedTypographicTableFormatException("out of order glyph range: [" + gs + "," + ge + "]");
                    } else if (mi < 0) {
                        throw new AdvancedTypographicTableFormatException("illegal mapping index: " + mi);
                    } else {
                        int miLast;
                        sa[i] = gs;
                        ea[i] = gidMax = ge;
                        ma[i] = mi;
                        if ((miLast = mi + (ge - gs)) > miMax) {
                            miMax = miLast;
                        }
                        i++;
                    }
                } else {
                    throw new AdvancedTypographicTableFormatException("illegal mapping entry, must be Integer: " + o);
                }
            }
            assert i == n;
            assert this.sa == null;
            assert this.ea == null;
            assert this.ma == null;
            this.sa = sa;
            this.ea = ea;
            this.ma = ma;
            this.miMax = miMax;
        }
        /** {@inheritDoc} */
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append('{');
            for (int i = 0, n = sa.length; i < n; i++) {
                if (i > 0) {
                    sb.append(',');
                }
                sb.append('[');
                sb.append(Integer.toString(sa [ i ]));
                sb.append(Integer.toString(ea [ i ]));
                sb.append("]:");
                sb.append(Integer.toString(ma [ i ]));
            }
            sb.append('}');
            return sb.toString();
        }
    }

    /**
     * The <code>MappingRange</code> class encapsulates a glyph [start,end] range and
     * a mapping index.
     */
    public static class MappingRange {

        private final int gidStart;                     // first glyph in range (inclusive)
        private final int gidEnd;                       // last glyph in range (inclusive)
        private final int index;                        // mapping index;

        /**
         * Instantiate a mapping range.
         */
        public MappingRange() {
            this (0, 0, 0);
        }

        /**
         * Instantiate a specific mapping range.
         * @param gidStart start of range
         * @param gidEnd end of range
         * @param index mapping index
         */
        public MappingRange(int gidStart, int gidEnd, int index) {
            if ((gidStart < 0) || (gidEnd < 0) || (index < 0)) {
                throw new AdvancedTypographicTableFormatException();
            } else if (gidStart > gidEnd) {
                throw new AdvancedTypographicTableFormatException();
            } else {
                this.gidStart = gidStart;
                this.gidEnd = gidEnd;
                this.index = index;
            }
        }

        /** @return start of range */
        public int getStart() {
            return gidStart;
        }

        /** @return end of range */
        public int getEnd() {
            return gidEnd;
        }

        /** @return mapping index */
        public int getIndex() {
            return index;
        }

        /** @return interval as a pair of integers */
        public int[] getInterval() {
            return new int[] { gidStart, gidEnd };
        }

        /**
         * Obtain interval, filled into first two elements of specified array, or returning new array.
         * @param interval an array of length two or greater or null
         * @return interval as a pair of integers, filled into specified array
         */
        public int[] getInterval(int[] interval) {
            if ((interval == null) || (interval.length != 2)) {
                throw new IllegalArgumentException();
            } else {
                interval[0] = gidStart;
                interval[1] = gidEnd;
            }
            return interval;
        }

        /** @return length of interval */
        public int getLength() {
            return gidStart - gidEnd;
        }

    }

}
