/*
 * 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.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;

// CSOFF: LineLengthCheck

/**
 * <p>The <code>GlyphSubtable</code> implements an abstract glyph subtable that
 * encapsulates identification, type, format, and coverage information.</p>
 *
 * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
 */
public abstract class GlyphSubtable implements Comparable {

    /** lookup flag - right to left */
    public static final int LF_RIGHT_TO_LEFT = 0x0001;
    /** lookup flag - ignore base glyphs */
    public static final int LF_IGNORE_BASE = 0x0002;
    /** lookup flag - ignore ligatures */
    public static final int LF_IGNORE_LIGATURE = 0x0004;
    /** lookup flag - ignore marks */
    public static final int LF_IGNORE_MARK = 0x0008;
    /** lookup flag - use mark filtering set */
    public static final int LF_USE_MARK_FILTERING_SET = 0x0010;
    /** lookup flag - reserved */
    public static final int LF_RESERVED = 0x0E00;
    /** lookup flag - mark attachment type */
    public static final int LF_MARK_ATTACHMENT_TYPE = 0xFF00;
    /** internal flag - use reverse scan */
    public static final int LF_INTERNAL_USE_REVERSE_SCAN = 0x10000;

    /** lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list; shared by multiple subtables in a single lookup  */
    private String lookupId;
    /** subtable sequence (index) number in lookup, zero based */
    private int sequence;
    /** subtable flags */
    private int flags;
    /** subtable format */
    private int format;
    /** subtable mapping table */
    private GlyphMappingTable mapping;
    /** weak reference to parent (gsub or gpos) table */
    private WeakReference table;

    /**
     * Instantiate this glyph subtable.
     * @param lookupId lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list
     * @param sequence subtable sequence (within lookup), starting with zero
     * @param flags subtable flags
     * @param format subtable format
     * @param mapping subtable mapping table
     */
    protected GlyphSubtable(String lookupId, int sequence, int flags, int format, GlyphMappingTable mapping)
    {
        if ((lookupId == null) || (lookupId.length() == 0)) {
            throw new AdvancedTypographicTableFormatException("invalid lookup identifier, must be non-empty string");
        } else if (mapping == null) {
            throw new AdvancedTypographicTableFormatException("invalid mapping table, must not be null");
        } else {
            this.lookupId = lookupId;
            this.sequence = sequence;
            this.flags = flags;
            this.format = format;
            this.mapping = mapping;
        }
    }

    /** @return this subtable's lookup identifer */
    public String getLookupId() {
        return lookupId;
    }

    /** @return this subtable's table type */
    public abstract int getTableType();

    /** @return this subtable's type */
    public abstract int getType();

    /** @return this subtable's type name */
    public abstract String getTypeName();

    /**
     * Determine if a glyph subtable is compatible with this glyph subtable. Two glyph subtables are
     * compatible if the both may appear in a single lookup table.
     * @param subtable a glyph subtable to determine compatibility
     * @return true if specified subtable is compatible with this glyph subtable, where by compatible
     * is meant that they share the same lookup type
     */
    public abstract boolean isCompatible(GlyphSubtable subtable);

    /** @return true if subtable uses reverse scanning of glyph sequence, meaning from the last glyph
     * in a glyph sequence to the first glyph
     */
    public abstract boolean usesReverseScan();

    /** @return this subtable's sequence (index) within lookup */
    public int getSequence() {
        return sequence;
    }

    /** @return this subtable's flags */
    public int getFlags() {
        return flags;
    }

    /** @return this subtable's format */
    public int getFormat() {
        return format;
    }

    /** @return this subtable's governing glyph definition table or null if none available */
    public GlyphDefinitionTable getGDEF() {
        GlyphTable gt = getTable();
        if (gt != null) {
            return gt.getGlyphDefinitions();
        } else {
            return null;
        }
    }

    /** @return this subtable's coverage mapping or null if mapping is not a coverage mapping */
    public GlyphCoverageMapping getCoverage() {
        if (mapping instanceof GlyphCoverageMapping) {
            return (GlyphCoverageMapping) mapping;
        } else {
            return null;
        }
    }

    /** @return this subtable's class mapping or null if mapping is not a class mapping */
    public GlyphClassMapping getClasses() {
        if (mapping instanceof GlyphClassMapping) {
            return (GlyphClassMapping) mapping;
        } else {
            return null;
        }
    }

    /** @return this subtable's lookup entries */
    public abstract List getEntries();

    /** @return this subtable's parent table (or null if undefined) */
    public synchronized GlyphTable getTable() {
        WeakReference r = this.table;
        return (r != null) ? (GlyphTable) r.get() : null;
    }

    /**
     * Establish a weak reference from this subtable to its parent
     * table. If table parameter is specified as <code>null</code>, then
     * clear and remove weak reference.
     * @param table the table or null
     * @throws IllegalStateException if table is already set to non-null
     */
    public synchronized void setTable(GlyphTable table) throws IllegalStateException {
        WeakReference r = this.table;
        if (table == null) {
            this.table = null;
            if (r != null) {
                r.clear();
            }
        } else if (r == null) {
            this.table = new WeakReference(table);
        } else {
            throw new IllegalStateException("table already set");
        }
    }

    /**
     * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
     * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
     */
    public void resolveLookupReferences(Map/*<String,GlyphTable.LookupTable>*/ lookupTables) {
    }

    /**
     * Map glyph id to coverage index.
     * @param gid glyph id
     * @return the corresponding coverage index of the specified glyph id
     */
    public int getCoverageIndex(int gid) {
        if (mapping instanceof GlyphCoverageMapping) {
            return ((GlyphCoverageMapping) mapping) .getCoverageIndex(gid);
        } else {
            return -1;
        }
    }

    /**
     * Map glyph id to coverage index.
     * @return the corresponding coverage index of the specified glyph id
     */
    public int getCoverageSize() {
        if (mapping instanceof GlyphCoverageMapping) {
            return ((GlyphCoverageMapping) mapping) .getCoverageSize();
        } else {
            return 0;
        }
    }

    /** {@inheritDoc} */
    public int hashCode() {
        int hc = sequence;
        hc = (hc * 3) + (lookupId.hashCode() ^ hc);
        return hc;
    }

    /**
     * {@inheritDoc}
     * @return true if the lookup identifier and the sequence number of the specified subtable is the same
     * as the lookup identifier and sequence number of this subtable
     */
    public boolean equals(Object o) {
        if (o instanceof GlyphSubtable) {
            GlyphSubtable st = (GlyphSubtable) o;
            return lookupId.equals(st.lookupId) && (sequence == st.sequence);
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     * @return the result of comparing the lookup identifier and the sequence number of the specified subtable with
     * the lookup identifier and sequence number of this subtable
     */
    public int compareTo(Object o) {
        int d;
        if (o instanceof GlyphSubtable) {
            GlyphSubtable st = (GlyphSubtable) o;
            if ((d = lookupId.compareTo(st.lookupId)) == 0) {
                if (sequence < st.sequence) {
                    d = -1;
                } else if (sequence > st.sequence) {
                    d = 1;
                }
            }
        } else {
            d = -1;
        }
        return d;
    }

    /**
     * Determine if any of the specified subtables uses reverse scanning.
     * @param subtables array of glyph subtables
     * @return true if any of the specified subtables uses reverse scanning.
     */
    public static boolean usesReverseScan(GlyphSubtable[] subtables) {
        if ((subtables == null) || (subtables.length == 0)) {
            return false;
        } else {
            for (int i = 0, n = subtables.length; i < n; i++) {
                if (subtables[i].usesReverseScan()) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Determine consistent flags for a set of subtables.
     * @param subtables array of glyph subtables
     * @return consistent flags
     * @throws IllegalStateException if inconsistent flags
     */
    public static int getFlags(GlyphSubtable[] subtables) throws IllegalStateException {
        if ((subtables == null) || (subtables.length == 0)) {
            return 0;
        } else {
            int flags = 0;
            // obtain first non-zero value of flags in array of subtables
            for (int i = 0, n = subtables.length; i < n; i++) {
                int f = subtables[i].getFlags();
                if (flags == 0) {
                    flags = f;
                    break;
                }
            }
            // enforce flag consistency
            for (int i = 0, n = subtables.length; i < n; i++) {
                int f = subtables[i].getFlags();
                if (f != flags) {
                    throw new IllegalStateException("inconsistent lookup flags " + f + ", expected " + flags);
                }
            }
            return flags | (usesReverseScan(subtables) ? LF_INTERNAL_USE_REVERSE_SCAN : 0);
        }
    }

}
