/*
 * 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: LazyFont.java 1811944 2017-10-12 11:44:13Z ssteiner $ */

package org.apache.fop.fonts;
import java.awt.Rectangle;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.xml.sax.InputSource;

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

import org.apache.fop.apps.io.InternalResourceResolver;
import org.apache.fop.complexscripts.fonts.Positionable;
import org.apache.fop.complexscripts.fonts.Substitutable;

/**
 * This class is used to defer the loading of a font until it is really used.
 */
public class LazyFont extends Typeface implements FontDescriptor, Substitutable, Positionable {

    private static Log log = LogFactory.getLog(LazyFont.class);

    private final FontUris fontUris;

    private final boolean useKerning;
    private final boolean useAdvanced;
    private boolean simulateStyle;
    private boolean embedAsType1;
    private final EncodingMode encodingMode;
    private final EmbeddingMode embeddingMode;
    private final String subFontName;
    private final boolean embedded;
    private final InternalResourceResolver resourceResolver;

    private boolean isMetricsLoaded;
    private Typeface realFont;
    private FontDescriptor realFontDescriptor;

    /**
     * Main constructor
     * @param fontInfo  the font info to embed
     * @param resourceResolver the font resolver to handle font URIs
     */
    public LazyFont(EmbedFontInfo fontInfo, InternalResourceResolver resourceResolver,
            boolean useComplexScripts) {

        this.fontUris = fontInfo.getFontUris();
        this.useKerning = fontInfo.getKerning();
        if (resourceResolver != null) {
            this.useAdvanced = useComplexScripts;
        } else {
            this.useAdvanced = fontInfo.getAdvanced();
        }
        this.simulateStyle = fontInfo.getSimulateStyle();
        this.embedAsType1 = fontInfo.getEmbedAsType1();
        this.encodingMode = fontInfo.getEncodingMode() != null ? fontInfo.getEncodingMode()
                : EncodingMode.AUTO;
        this.embeddingMode = fontInfo.getEmbeddingMode() != null ? fontInfo.getEmbeddingMode()
                : EmbeddingMode.AUTO;
        this.subFontName = fontInfo.getSubFontName();
        this.embedded = fontInfo.isEmbedded();
        this.resourceResolver = resourceResolver;
    }

    /** {@inheritDoc} */
    public String toString() {
        StringBuffer sbuf = new StringBuffer(super.toString());
        sbuf.append('{');
        sbuf.append("metrics-url=" + fontUris.getMetrics());
        sbuf.append(",embed-url=" + fontUris.getEmbed());
        sbuf.append(",kerning=" + useKerning);
        sbuf.append(",advanced=" + useAdvanced);
        sbuf.append('}');
        return sbuf.toString();
    }

    private void load(boolean fail) {
        if (!isMetricsLoaded) {
            try {
                if (fontUris.getMetrics() != null) {
                    // Use of XML based font metrics is DEPRECATED!
                    // @todo Possible thread problem here
                    XMLFontMetricsReader reader = null;
                    InputStream in = resourceResolver.getResource(fontUris.getMetrics());
                    InputSource src = new InputSource(in);
                    src.setSystemId(fontUris.getMetrics().toASCIIString());
                    reader = new XMLFontMetricsReader(src, resourceResolver);
                    reader.setKerningEnabled(useKerning);
                    reader.setAdvancedEnabled(useAdvanced);
                    if (this.embedded) {
                        reader.setFontEmbedURI(fontUris.getEmbed());
                    }
                    realFont = reader.getFont();
                } else {
                    if (fontUris.getEmbed() == null) {
                        throw new RuntimeException("Cannot load font. No font URIs available.");
                    }
                    realFont = FontLoader.loadFont(fontUris, subFontName, embedded, embeddingMode,
                                encodingMode, useKerning, useAdvanced, resourceResolver, simulateStyle, embedAsType1);
                }
                if (realFont instanceof FontDescriptor) {
                    realFontDescriptor = (FontDescriptor) realFont;
                }
            } catch (RuntimeException e) {
                String error = "Failed to read font file " + fontUris.getEmbed();
                throw new RuntimeException(error, e);
            } catch (Exception e) {
                String error = "Failed to read font file " + fontUris.getEmbed();
                log.error(error, e);
                if (fail) {
                    throw new RuntimeException(error, e);
                }
            }
            realFont.setEventListener(this.eventListener);
            isMetricsLoaded = true;
        }
    }

    /**
     * Gets the real font.
     * @return the real font
     */
    public Typeface getRealFont() {
        load(false);
        return realFont;
    }

    // ---- Font ----
    /** {@inheritDoc} */
    public String getEncodingName() {
        load(true);
        return realFont.getEncodingName();
    }

    /**
     * {@inheritDoc}
     */
    public char mapChar(char c) {
        if (!isMetricsLoaded) {
            load(true);
        }
        return realFont.mapChar(c);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hadMappingOperations() {
        load(true);
        return realFont.hadMappingOperations();
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasChar(char c) {
        if (!isMetricsLoaded) {
            load(true);
        }
        return realFont.hasChar(c);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMultiByte() {
        load(true);
        return realFont.isMultiByte();
    }

    // ---- FontMetrics interface ----
    /** {@inheritDoc} */
    public URI getFontURI() {
        load(true);
        return realFont.getFontURI();
    }

    /** {@inheritDoc} */
    public String getFontName() {
        load(true);
        return realFont.getFontName();
    }

    /** {@inheritDoc} */
    public String getEmbedFontName() {
        load(true);
        return realFont.getEmbedFontName();
    }

    /** {@inheritDoc} */
    public String getFullName() {
        load(true);
        return realFont.getFullName();
    }

    /** {@inheritDoc} */
    public Set<String> getFamilyNames() {
        load(true);
        return realFont.getFamilyNames();
    }

    /**
     * {@inheritDoc}
     */
    public int getMaxAscent(int size) {
        load(true);
        return realFont.getMaxAscent(size);
    }

    /**
     * {@inheritDoc}
     */
    public int getAscender(int size) {
        load(true);
        return realFont.getAscender(size);
    }

    /**
     * {@inheritDoc}
     */
    public int getCapHeight(int size) {
        load(true);
        return realFont.getCapHeight(size);
    }

    /**
     * {@inheritDoc}
     */
    public int getDescender(int size) {
        load(true);
        return realFont.getDescender(size);
    }

    /**
     * {@inheritDoc}
     */
    public int getXHeight(int size) {
        load(true);
        return realFont.getXHeight(size);
    }

    public int getUnderlinePosition(int size) {
        load(true);
        return realFont.getUnderlinePosition(size);
    }

    public int getUnderlineThickness(int size) {
        load(true);
        return realFont.getUnderlineThickness(size);
    }

    public int getStrikeoutPosition(int size) {
        load(true);
        return realFont.getStrikeoutPosition(size);
    }

    public int getStrikeoutThickness(int size) {
        load(true);
        return realFont.getStrikeoutThickness(size);
    }

    /**
     * {@inheritDoc}
     */
    public int getWidth(int i, int size) {
        if (!isMetricsLoaded) {
            load(true);
        }
        return realFont.getWidth(i, size);
    }

    /**
     * {@inheritDoc}
     */
    public int[] getWidths() {
        load(true);
        return realFont.getWidths();
    }

    public Rectangle getBoundingBox(int glyphIndex, int size) {
        load(true);
        return realFont.getBoundingBox(glyphIndex, size);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasKerningInfo() {
        load(true);
        return realFont.hasKerningInfo();
    }

    /**
     * {@inheritDoc}
     */
    public Map<Integer, Map<Integer, Integer>> getKerningInfo() {
        load(true);
        return realFont.getKerningInfo();
    }

    /** {@inheritDoc} */
    public boolean hasFeature(int tableType, String script, String language, String feature) {
        load(true);
        return realFont.hasFeature(tableType, script, language, feature);
    }

    // ---- FontDescriptor interface ----
    /**
     * {@inheritDoc}
     */
    public int getCapHeight() {
        load(true);
        return realFontDescriptor.getCapHeight();
    }

    /**
     * {@inheritDoc}
     */
    public int getDescender() {
        load(true);
        return realFontDescriptor.getDescender();
    }

    /**
     * {@inheritDoc}
     */
    public int getAscender() {
        load(true);
        return realFontDescriptor.getAscender();
    }

    /** {@inheritDoc} */
    public int getFlags() {
        load(true);
        return realFontDescriptor.getFlags();
    }

    /** {@inheritDoc} */
    public boolean isSymbolicFont() {
        load(true);
        return realFontDescriptor.isSymbolicFont();
    }

    /**
     * {@inheritDoc}
     */
    public int[] getFontBBox() {
        load(true);
        return realFontDescriptor.getFontBBox();
    }

    /**
     * {@inheritDoc}
     */
    public int getItalicAngle() {
        load(true);
        return realFontDescriptor.getItalicAngle();
    }

    /**
     * {@inheritDoc}
     */
    public int getStemV() {
        load(true);
        return realFontDescriptor.getStemV();
    }

    /**
     * {@inheritDoc}
     */
    public FontType getFontType() {
        load(true);
        return realFontDescriptor.getFontType();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEmbeddable() {
        load(true);
        return realFontDescriptor.isEmbeddable();
    }

    /**
     * {@inheritDoc}
     */
    public boolean performsSubstitution() {
        load(true);
        if (realFontDescriptor instanceof Substitutable) {
            return ((Substitutable)realFontDescriptor).performsSubstitution();
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations,
                                            boolean retainControls) {
        load(true);
        if (realFontDescriptor instanceof Substitutable) {
            return ((Substitutable)realFontDescriptor).performSubstitution(cs,
                script, language, associations, retainControls);
        } else {
            return cs;
        }
    }

    /**
     * {@inheritDoc}
     */
    public CharSequence reorderCombiningMarks(
        CharSequence cs, int[][] gpa, String script, String language, List associations) {
        if (!isMetricsLoaded) {
            load(true);
        }
        if (realFontDescriptor instanceof Substitutable) {
            return ((Substitutable)realFontDescriptor)
                .reorderCombiningMarks(cs, gpa, script, language, associations);
        } else {
            return cs;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean performsPositioning() {
        if (!isMetricsLoaded) {
            load(true);
        }
        if (realFontDescriptor instanceof Positionable) {
            return ((Positionable)realFontDescriptor).performsPositioning();
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public int[][]
        performPositioning(CharSequence cs, String script, String language, int fontSize) {
        if (!isMetricsLoaded) {
            load(true);
        }
        if (realFontDescriptor instanceof Positionable) {
            return ((Positionable)realFontDescriptor)
                .performPositioning(cs, script, language, fontSize);
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public int[][]
        performPositioning(CharSequence cs, String script, String language) {
        if (!isMetricsLoaded) {
            load(true);
        }
        if (realFontDescriptor instanceof Positionable) {
            return ((Positionable)realFontDescriptor)
                .performPositioning(cs, script, language);
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isSubsetEmbedded() {
        load(true);
        if (realFont.isMultiByte() && this.embeddingMode == EmbeddingMode.FULL) {
            return false;
        }
        return realFont.isMultiByte();
    }
}

