/*
 * 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.
 */

package org.apache.fontbox.cff;

import java.awt.geom.GeneralPath;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.fontbox.type1.Type1CharStringReader;

/**
 * A Type 0 CIDFont represented in a CFF file. Thread safe.
 *
 * @author Villu Ruusmann
 * @author John Hewson
 */
public class CFFCIDFont extends CFFFont
{
    private String registry;
    private String ordering;
    private int supplement;

    private List<Map<String, Object>> fontDictionaries = Collections.emptyList();
    private List<Map<String, Object>> privateDictionaries = Collections.emptyList();
    private FDSelect fdSelect;

    private final Map<Integer, CIDKeyedType2CharString> charStringCache =
            new ConcurrentHashMap<>();
    private Type2CharStringParser charStringParser = null;

    private final PrivateType1CharStringReader reader = new PrivateType1CharStringReader();

    /**
     * Returns the registry value.
     * 
     * @return the registry
     */
    public String getRegistry() 
    {
        return registry;
    }

    /**
     * Sets the registry value.
     *
     * @param registry the registry to set
     */
    void setRegistry(String registry)
    {
        this.registry = registry;
    }

    /**
     * Returns the ordering value.
     *
     * @return the ordering
     */
    public String getOrdering() 
    {
        return ordering;
    }

    /**
     * Sets the ordering value.
     *
     * @param ordering the ordering to set
     */
    void setOrdering(String ordering)
    {
        this.ordering = ordering;
    }

    /**
     * Returns the supplement value.
     *
     * @return the supplement
     */
    public int getSupplement() 
    {
        return supplement;
    }

    /**
     * Sets the supplement value.
     *
     * @param supplement the supplement to set
     */
    void setSupplement(int supplement)
    {
        this.supplement = supplement;
    }

    /**
     * Returns the font dictionaries.
     *
     * @return the fontDict
     */
    public List<Map<String, Object>> getFontDicts()
    {
        return fontDictionaries;
    }

    /**
     * Sets the font dictionaries.
     *
     * @param fontDict the fontDict to set
     */
    void setFontDict(List<Map<String, Object>> fontDict)
    {
        this.fontDictionaries = fontDict;
    }

    /**
     * Returns the private dictionary.
     *
     * @return the privDict
     */
    public List<Map<String, Object>> getPrivDicts()
    {
        return privateDictionaries;
    }

    /**
     * Sets the private dictionary.
     *
     * @param privDict the privDict to set
     */
    void setPrivDict(List<Map<String, Object>> privDict)
    {
        this.privateDictionaries = privDict;
    }

    /**
     * Returns the fdSelect value.
     *
     * @return the fdSelect
     */
    public FDSelect getFdSelect()
    {
        return fdSelect;
    }

    /**
     * Sets the fdSelect value.
     *
     * @param fdSelect the fdSelect to set
     */
    void setFdSelect(FDSelect fdSelect)
    {
        this.fdSelect = fdSelect;
    }

    /**
     * Returns the defaultWidthX for the given GID.
     *
     * @param gid GID
     */
    private int getDefaultWidthX(int gid)
    {
        int fdArrayIndex = this.fdSelect.getFDIndex(gid);
        if (fdArrayIndex == -1 || fdArrayIndex >= this.privateDictionaries.size())
        {
            return 1000;
        }
        Object privDictValue = this.privateDictionaries.get(fdArrayIndex).get("defaultWidthX");
        return privDictValue instanceof Number ? ((Number) privDictValue).intValue() : 1000;
    }

    /**
     * Returns the nominalWidthX for the given GID.
     *
     * @param gid GID
     */
    private int getNominalWidthX(int gid)
    {
        int fdArrayIndex = this.fdSelect.getFDIndex(gid);
        if (fdArrayIndex == -1 || fdArrayIndex >= this.privateDictionaries.size())
        {
            return 0;
        }
        Object privDictValue = this.privateDictionaries.get(fdArrayIndex).get("nominalWidthX");
        return privDictValue instanceof Number ? ((Number) privDictValue).intValue() : 0;
    }

    /**
     * Returns the LocalSubrIndex for the given GID.
     *
     * @param gid GID
     */
    private byte[][] getLocalSubrIndex(int gid)
    {
        int fdArrayIndex = this.fdSelect.getFDIndex(gid);
        if (fdArrayIndex == -1 || fdArrayIndex >= this.privateDictionaries.size())
        {
            return null;
        }
        Map<String, Object> privDict = this.privateDictionaries.get(fdArrayIndex);
        return (byte[][])privDict.get("Subrs");
    }

    /**
     * Returns the Type 2 charstring for the given CID.
     *
     * @param cid CID
     * @throws IOException if the charstring could not be read
     */
    @Override
    public CIDKeyedType2CharString getType2CharString(int cid) throws IOException
    {
        CIDKeyedType2CharString type2 = charStringCache.get(cid);
        if (type2 == null)
        {
            int gid = getCharset().getGIDForCID(cid);

            byte[] bytes = charStrings[gid];
            if (bytes == null)
            {
                bytes = charStrings[0]; // .notdef
            }
            List<Object> type2seq = getParser().parse(bytes, globalSubrIndex,
                    getLocalSubrIndex(gid), String.format(Locale.US, "%04x", cid));
            type2 = new CIDKeyedType2CharString(reader, getName(), cid, gid, type2seq,
                                                getDefaultWidthX(gid), getNominalWidthX(gid));
            charStringCache.put(cid, type2);
        }
        return type2;
    }

    private Type2CharStringParser getParser()
    {
        if (charStringParser == null)
        {
            charStringParser = new Type2CharStringParser(getName());
        }
        return charStringParser;
    }

    @Override
    public GeneralPath getPath(String selector) throws IOException
    {
        int cid = selectorToCID(selector);
        return getType2CharString(cid).getPath();
    }

    @Override
    public float getWidth(String selector) throws IOException
    {
        int cid = selectorToCID(selector);
        return getType2CharString(cid).getWidth();
    }

    @Override
    public boolean hasGlyph(String selector) throws IOException
    {
        int cid = selectorToCID(selector);
        return cid != 0;
    }

    /**
     * Parses a CID selector of the form \ddddd.
     */
    private int selectorToCID(String selector)
    {
        if (!selector.startsWith("\\"))
        {
            throw new IllegalArgumentException("Invalid selector");
        }
        return Integer.parseInt(selector.substring(1));
    }

    /**
     * Private implementation of Type1CharStringReader, because only CFFType1Font can
     * expose this publicly, as CIDFonts only support this for legacy 'seac' commands.
     */
    private class PrivateType1CharStringReader implements Type1CharStringReader
    {
        @Override
        public Type1CharString getType1CharString(String name) throws IOException
        {
            return CFFCIDFont.this.getType2CharString(0); // .notdef
        }
    }
}
