/*
 * 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: FontManager.java 1293736 2012-02-26 02:29:01Z gadams $ */

package org.apache.fop.fonts;

import java.io.File;
import java.net.MalformedURLException;
import java.util.List;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.FOPException;
import org.apache.fop.fonts.FontTriplet.Matcher;
import org.apache.fop.fonts.substitute.FontSubstitutions;

// TODO: Refactor fonts package so major font activities (autodetection etc)
// are all centrally managed and delegated from this class

/**
 * The manager of fonts. The class holds a reference to the font cache and information about
 * font substitution, referenced fonts and similar.
 */
public class FontManager {
    /** Use cache (record previously detected font triplet info) */
    public static final boolean DEFAULT_USE_CACHE = true;

    /** The base URL for all font URL resolutions. */
    private String fontBase = null;

    /** Font cache to speed up auto-font configuration (null if disabled) */
    private FontCache fontCache = null;

    /** Font substitutions */
    private FontSubstitutions fontSubstitutions = null;

    /** Allows enabling kerning on the base 14 fonts, default is false */
    private boolean enableBase14Kerning = false;

    /** FontTriplet matcher for fonts that shall be referenced rather than embedded. */
    private FontTriplet.Matcher referencedFontsMatcher;

    /** Enables/disables the use of font caching */
    private boolean useCache = DEFAULT_USE_CACHE;

    /** Provides a font cache file path **/
    private File cacheFile;

    /**
     * Main constructor
     */
    public FontManager() {
    }

    /**
     * Sets the font base URL.
     * @param fontBase font base URL
     * @throws MalformedURLException if there's a problem with a URL
     */
    public void setFontBaseURL(String fontBase) throws MalformedURLException {
        this.fontBase = fontBase;
    }

    /**
     * Returns the font base URL.
     * @return the font base URL (or null if none was set)
     */
    public String getFontBaseURL() {
        return this.fontBase;
    }

    /** @return true if kerning on base 14 fonts is enabled */
    public boolean isBase14KerningEnabled() {
        return this.enableBase14Kerning;
    }

    /**
     * Controls whether kerning is activated on base 14 fonts.
     * @param value true if kerning should be activated
     */
    public void setBase14KerningEnabled(boolean value) {
        this.enableBase14Kerning = value;
    }

    /**
     * Sets the font substitutions
     * @param substitutions font substitutions
     */
    public void setFontSubstitutions(FontSubstitutions substitutions) {
        this.fontSubstitutions = substitutions;
    }

    /**
     * Returns the font substitution catalog
     * @return the font substitution catalog
     */
    protected FontSubstitutions getFontSubstitutions() {
        if (fontSubstitutions == null) {
            this.fontSubstitutions = new FontSubstitutions();
        }
        return fontSubstitutions;
    }

    /**
     * Sets the font cache file
     * @param cacheFile the font cache file
     */
    public void setCacheFile(File cacheFile) {
        this.cacheFile = cacheFile;
    }

    /**
     * Returns the font cache file
     * @return the font cache file
     */
    public File getCacheFile() {
        if (cacheFile != null) {
            return this.cacheFile;
        }
        return FontCache.getDefaultCacheFile(false);
    }

    /**
     * Whether or not to cache results of font triplet detection/auto-config
     * @param useCache use cache or not
     */
    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
        if (!useCache) {
            this.fontCache = null;
        }
    }

    /**
     * Cache results of font triplet detection/auto-config?
     * @return true if this font manager uses the cache
     */
    public boolean useCache() {
        return useCache;
    }

    /**
     * Returns the font cache instance used by this font manager.
     * @return the font cache
     */
    public FontCache getFontCache() {
        if (fontCache == null) {
            if (useCache) {
                if (cacheFile != null) {
                    fontCache = FontCache.loadFrom(cacheFile);
                } else {
                    fontCache = FontCache.load();
                }
                if (fontCache == null) {
                    fontCache = new FontCache();
                }
            }
        }
        return fontCache;
    }

    /**
     * Saves the FontCache as necessary
     *
     * @throws FOPException fop exception
     */
    public void saveCache() throws FOPException {
        if (useCache) {
            if (fontCache != null && fontCache.hasChanged()) {
                if (cacheFile != null) {
                    fontCache.saveTo(cacheFile);
                } else {
                    fontCache.save();
                }
            }
        }
    }

    /**
     * Deletes the current FontCache file
     * @return Returns true if the font cache file was successfully deleted.
     */
    public boolean deleteCache() {
        boolean deleted = false;
        if (useCache) {
            if (cacheFile != null) {
                deleted = cacheFile.delete();
            } else {
                deleted = FontCache.getDefaultCacheFile(true).delete();
            }
        }
        return deleted;
    }

    /**
     * Sets up the fonts on a given FontInfo object. The fonts to setup are defined by an
     * array of {@link FontCollection} objects.
     * @param fontInfo the FontInfo object to set up
     * @param fontCollections the array of font collections/sources
     */
    public void setup(FontInfo fontInfo, FontCollection[] fontCollections) {
        int startNum = 1;

        for (int i = 0, c = fontCollections.length; i < c; i++) {
            startNum = fontCollections[i].setup(startNum, fontInfo);
        }
        // Make any defined substitutions in the font info
        getFontSubstitutions().adjustFontInfo(fontInfo);
    }

    /**
     * Minimum implemenation of FontResolver.
     */
    public static class MinimalFontResolver implements FontResolver {
        private boolean useComplexScriptFeatures;
        MinimalFontResolver(boolean useComplexScriptFeatures) {
            this.useComplexScriptFeatures = useComplexScriptFeatures;
        }
        /** {@inheritDoc} */
        public Source resolve(String href) {
            //Minimal functionality here
            return new StreamSource(href);
        }
        /** {@inheritDoc} */
        public boolean isComplexScriptFeaturesEnabled() {
            return useComplexScriptFeatures;
        }
    }

    /**
     * Create minimal font resolver.
     * @param useComplexScriptFeatures true if complex script features enabled
     * @return a new FontResolver to be used by the font subsystem
     */
    public static FontResolver createMinimalFontResolver(boolean useComplexScriptFeatures) {
        return new MinimalFontResolver ( useComplexScriptFeatures );
    }

    /**
     * Sets the {@link FontTriplet.Matcher} that can be used to identify the fonts that shall
     * be referenced rather than embedded.
     * @param matcher the font triplet matcher
     */
    public void setReferencedFontsMatcher(FontTriplet.Matcher matcher) {
        this.referencedFontsMatcher = matcher;
    }

    /**
     * Gets the {@link FontTriplet.Matcher} that can be used to identify the fonts that shall
     * be referenced rather than embedded.
     * @return the font triplet matcher (or null if none is set)
     */
    public Matcher getReferencedFontsMatcher() {
        return this.referencedFontsMatcher;
    }

    /**
     * Updates the referenced font list using the FontManager's referenced fonts matcher
     * ({@link #getReferencedFontsMatcher()}).
     * @param fontInfoList a font info list
     */
    public void updateReferencedFonts(List<EmbedFontInfo> fontInfoList) {
        Matcher matcher = getReferencedFontsMatcher();
        updateReferencedFonts(fontInfoList, matcher);
    }

    /**
     * Updates the referenced font list.
     * @param fontInfoList a font info list
     * @param matcher the font triplet matcher to use
     */
    public void updateReferencedFonts(List<EmbedFontInfo> fontInfoList, Matcher matcher) {
        if (matcher == null) {
            return; //No referenced fonts
        }
        for (EmbedFontInfo fontInfo : fontInfoList) {
            for (FontTriplet triplet : fontInfo.getFontTriplets()) {
                if (matcher.matches(triplet)) {
                    fontInfo.setEmbedded(false);
                    break;
                }
            }
        }
    }
}
