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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.complexscripts.scripts.ScriptProcessor;
import org.apache.fop.complexscripts.util.GlyphSequence;

// CSOFF: LineLengthCheck

/**
 * <p>The <code>GlyphDefinitionTable</code> class is a glyph table that implements
 * glyph definition functionality according to the OpenType GDEF table.</p>
 *
 * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
 */
public class GlyphDefinitionTable extends GlyphTable {

    /** logging instance */
    private static final Log log = LogFactory.getLog(GlyphDefinitionTable.class);

    /** glyph class subtable type */
    public static final int GDEF_LOOKUP_TYPE_GLYPH_CLASS = 1;
    /** attachment point subtable type */
    public static final int GDEF_LOOKUP_TYPE_ATTACHMENT_POINT = 2;
    /** ligature caret subtable type */
    public static final int GDEF_LOOKUP_TYPE_LIGATURE_CARET = 3;
    /** mark attachment subtable type */
    public static final int GDEF_LOOKUP_TYPE_MARK_ATTACHMENT = 4;

    /** pre-defined glyph class - base glyph */
    public static final int GLYPH_CLASS_BASE = 1;
    /** pre-defined glyph class - ligature glyph */
    public static final int GLYPH_CLASS_LIGATURE = 2;
    /** pre-defined glyph class - mark glyph */
    public static final int GLYPH_CLASS_MARK = 3;
    /** pre-defined glyph class - component glyph */
    public static final int GLYPH_CLASS_COMPONENT = 4;

    /** singleton glyph class table */
    private GlyphClassSubtable gct;
    /** singleton attachment point table */
    // private AttachmentPointSubtable apt; // NOT YET USED
    /** singleton ligature caret table */
    // private LigatureCaretSubtable lct;   // NOT YET USED
    /** singleton mark attachment table */
    private MarkAttachmentSubtable mat;

    /**
     * Instantiate a <code>GlyphDefinitionTable</code> object using the specified subtables.
     * @param subtables a list of identified subtables
     */
    public GlyphDefinitionTable(List subtables, Map<String, ScriptProcessor> processors) {
        super(null, new HashMap(0), processors);
        if ((subtables == null) || (subtables.size() == 0)) {
            throw new AdvancedTypographicTableFormatException("subtables must be non-empty");
        } else {
            for (Object o : subtables) {
                if (o instanceof GlyphDefinitionSubtable) {
                    addSubtable((GlyphSubtable) o);
                } else {
                    throw new AdvancedTypographicTableFormatException("subtable must be a glyph definition subtable");
                }
            }
            freezeSubtables();
        }
    }

    /**
     * Reorder combining marks in glyph sequence so that they precede (within the sequence) the base
     * character to which they are applied. N.B. In the case of LTR segments, marks are not reordered by this,
     * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede
     * their base glyph.
     * @param gs an input glyph sequence
     * @param widths associated advance widths (also reordered)
     * @param gpa associated glyph position adjustments (also reordered)
     * @param script a script identifier
     * @param language a language identifier
     * @return the reordered (output) glyph sequence
     */
    public GlyphSequence reorderCombiningMarks(GlyphSequence gs, int[] widths, int[][] gpa, String script, String language) {
        ScriptProcessor sp = ScriptProcessor.getInstance(script, processors);
        return sp.reorderCombiningMarks(this, gs, widths, gpa, script, language);
    }

    /** {@inheritDoc} */
    protected void addSubtable(GlyphSubtable subtable) {
        if (subtable instanceof GlyphClassSubtable) {
            this.gct = (GlyphClassSubtable) subtable;
        } else if (subtable instanceof AttachmentPointSubtable) {
            // TODO - not yet used
            // this.apt = (AttachmentPointSubtable) subtable;
        } else if (subtable instanceof LigatureCaretSubtable) {
            // TODO - not yet used
            // this.lct = (LigatureCaretSubtable) subtable;
        } else if (subtable instanceof MarkAttachmentSubtable) {
            this.mat = (MarkAttachmentSubtable) subtable;
        } else {
            throw new UnsupportedOperationException("unsupported glyph definition subtable type: " + subtable);
        }
    }

    /**
     * Determine if glyph belongs to pre-defined glyph class.
     * @param gid a glyph identifier (index)
     * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
     * @return true if glyph belongs to specified glyph class
     */
    public boolean isGlyphClass(int gid, int gc) {
        if (gct != null) {
            return gct.isGlyphClass(gid, gc);
        } else {
            return false;
        }
    }

    /**
     * Determine glyph class.
     * @param gid a glyph identifier (index)
     * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
     */
    public int getGlyphClass(int gid) {
        if (gct != null) {
            return gct.getGlyphClass(gid);
        } else {
            return -1;
        }
    }

    /**
     * Determine if glyph belongs to (font specific) mark attachment class.
     * @param gid a glyph identifier (index)
     * @param mac a (font specific) mark attachment class
     * @return true if glyph belongs to specified mark attachment class
     */
    public boolean isMarkAttachClass(int gid, int mac) {
        if (mat != null) {
            return mat.isMarkAttachClass(gid, mac);
        } else {
            return false;
        }
    }

    /**
     * Determine mark attachment class.
     * @param gid a glyph identifier (index)
     * @return a non-negative mark attachment class, or -1 if no class defined
     */
    public int getMarkAttachClass(int gid) {
        if (mat != null) {
            return mat.getMarkAttachClass(gid);
        } else {
            return -1;
        }
    }

    /**
     * Map a lookup type name to its constant (integer) value.
     * @param name lookup type name
     * @return lookup type
     */
    public static int getLookupTypeFromName(String name) {
        int t;
        String s = name.toLowerCase();
        if ("glyphclass".equals(s)) {
            t = GDEF_LOOKUP_TYPE_GLYPH_CLASS;
        } else if ("attachmentpoint".equals(s)) {
            t = GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
        } else if ("ligaturecaret".equals(s)) {
            t = GDEF_LOOKUP_TYPE_LIGATURE_CARET;
        } else if ("markattachment".equals(s)) {
            t = GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
        } else {
            t = -1;
        }
        return t;
    }

    /**
     * Map a lookup type constant (integer) value to its name.
     * @param type lookup type
     * @return lookup type name
     */
    public static String getLookupTypeName(int type) {
        String tn = null;
        switch (type) {
        case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
            tn = "glyphclass";
            break;
        case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
            tn = "attachmentpoint";
            break;
        case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
            tn = "ligaturecaret";
            break;
        case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
            tn = "markattachment";
            break;
        default:
            tn = "unknown";
            break;
        }
        return tn;
    }

    /**
     * Create a definition subtable according to the specified arguments.
     * @param type subtable type
     * @param id subtable identifier
     * @param sequence subtable sequence
     * @param flags subtable flags (must be zero)
     * @param format subtable format
     * @param mapping subtable mapping table
     * @param entries subtable entries
     * @return a glyph subtable instance
     */
    public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
        GlyphSubtable st = null;
        switch (type) {
        case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
            st = GlyphClassSubtable.create(id, sequence, flags, format, mapping, entries);
            break;
        case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
            st = AttachmentPointSubtable.create(id, sequence, flags, format, mapping, entries);
            break;
        case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
            st = LigatureCaretSubtable.create(id, sequence, flags, format, mapping, entries);
            break;
        case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
            st = MarkAttachmentSubtable.create(id, sequence, flags, format, mapping, entries);
            break;
        default:
            break;
        }
        return st;
    }

    private abstract static class GlyphClassSubtable extends GlyphDefinitionSubtable {
        GlyphClassSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping);
        }
        /** {@inheritDoc} */
        public int getType() {
            return GDEF_LOOKUP_TYPE_GLYPH_CLASS;
        }
        /**
         * Determine if glyph belongs to pre-defined glyph class.
         * @param gid a glyph identifier (index)
         * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
         * @return true if glyph belongs to specified glyph class
         */
        public abstract boolean isGlyphClass(int gid, int gc);
        /**
         * Determine glyph class.
         * @param gid a glyph identifier (index)
         * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
         */
        public abstract int getGlyphClass(int gid);
        static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            if (format == 1) {
                return new GlyphClassSubtableFormat1(id, sequence, flags, format, mapping, entries);
            } else {
                throw new UnsupportedOperationException();
            }
        }
    }

    private static class GlyphClassSubtableFormat1 extends GlyphClassSubtable {
        GlyphClassSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping, entries);
        }
        /** {@inheritDoc} */
        public List getEntries() {
            return null;
        }
        /** {@inheritDoc} */
        public boolean isCompatible(GlyphSubtable subtable) {
            return subtable instanceof GlyphClassSubtable;
        }
        /** {@inheritDoc} */
        public boolean isGlyphClass(int gid, int gc) {
            GlyphClassMapping cm = getClasses();
            if (cm != null) {
                return cm.getClassIndex(gid, 0) == gc;
            } else {
                return false;
            }
        }
        /** {@inheritDoc} */
        public int getGlyphClass(int gid) {
            GlyphClassMapping cm = getClasses();
            if (cm != null) {
                return cm.getClassIndex(gid, 0);
            } else {
                return -1;
            }
        }
    }

    private abstract static class AttachmentPointSubtable extends GlyphDefinitionSubtable {
        AttachmentPointSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping);
        }
        /** {@inheritDoc} */
        public int getType() {
            return GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
        }
        static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            if (format == 1) {
                return new AttachmentPointSubtableFormat1(id, sequence, flags, format, mapping, entries);
            } else {
                throw new UnsupportedOperationException();
            }
        }
    }

    private static class AttachmentPointSubtableFormat1 extends AttachmentPointSubtable {
        AttachmentPointSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping, entries);
        }
        /** {@inheritDoc} */
        public List getEntries() {
            return null;
        }
        /** {@inheritDoc} */
        public boolean isCompatible(GlyphSubtable subtable) {
            return subtable instanceof AttachmentPointSubtable;
        }
    }

    private abstract static class LigatureCaretSubtable extends GlyphDefinitionSubtable {
        LigatureCaretSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping);
        }
        /** {@inheritDoc} */
        public int getType() {
            return GDEF_LOOKUP_TYPE_LIGATURE_CARET;
        }
        static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            if (format == 1) {
                return new LigatureCaretSubtableFormat1(id, sequence, flags, format, mapping, entries);
            } else {
                throw new UnsupportedOperationException();
            }
        }
    }

    private static class LigatureCaretSubtableFormat1 extends LigatureCaretSubtable {
        LigatureCaretSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping, entries);
        }
        /** {@inheritDoc} */
        public List getEntries() {
            return null;
        }
        /** {@inheritDoc} */
        public boolean isCompatible(GlyphSubtable subtable) {
            return subtable instanceof LigatureCaretSubtable;
        }
    }

    private abstract static class MarkAttachmentSubtable extends GlyphDefinitionSubtable {
        MarkAttachmentSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping);
        }
        /** {@inheritDoc} */
        public int getType() {
            return GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
        }
        /**
         * Determine if glyph belongs to (font specific) mark attachment class.
         * @param gid a glyph identifier (index)
         * @param mac a (font specific) mark attachment class
         * @return true if glyph belongs to specified mark attachment class
         */
        public abstract boolean isMarkAttachClass(int gid, int mac);
        /**
         * Determine mark attachment class.
         * @param gid a glyph identifier (index)
         * @return a non-negative mark attachment class, or -1 if no class defined
         */
        public abstract int getMarkAttachClass(int gid);
        static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            if (format == 1) {
                return new MarkAttachmentSubtableFormat1(id, sequence, flags, format, mapping, entries);
            } else {
                throw new UnsupportedOperationException();
            }
        }
    }

    private static class MarkAttachmentSubtableFormat1 extends MarkAttachmentSubtable {
        MarkAttachmentSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
            super(id, sequence, flags, format, mapping, entries);
        }
        /** {@inheritDoc} */
        public List getEntries() {
            return null;
        }
        /** {@inheritDoc} */
        public boolean isCompatible(GlyphSubtable subtable) {
            return subtable instanceof MarkAttachmentSubtable;
        }
        /** {@inheritDoc} */
        public boolean isMarkAttachClass(int gid, int mac) {
            GlyphClassMapping cm = getClasses();
            if (cm != null) {
                return cm.getClassIndex(gid, 0) == mac;
            } else {
                return false;
            }
        }
        /** {@inheritDoc} */
        public int getMarkAttachClass(int gid) {
            GlyphClassMapping cm = getClasses();
            if (cm != null) {
                return cm.getClassIndex(gid, 0);
            } else {
                return -1;
            }
        }
    }

}
