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

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.io.InternalResourceResolver;
import org.apache.fop.fonts.DefaultFontConfig.Directory;
import org.apache.fop.fonts.autodetect.FontFileFinder;
import org.apache.fop.fonts.autodetect.FontInfoFinder;
import org.apache.fop.util.LogUtil;

/**
 * The default configurator for fonts. This configurator can configure the more generic fonts used
 * by the renderers i.e. TTF, Type1 etc...
 */
public class DefaultFontConfigurator implements FontConfigurator<EmbedFontInfo> {
    /** logger instance */
    protected static final Log log = LogFactory.getLog(DefaultFontConfigurator.class);

    private final FontManager fontManager;
    private final InternalResourceResolver resourceResolver;
    private final FontEventListener listener;
    private final boolean strict;

    /**
     * Main constructor
     * @param fontManager the font manager
     * @param listener the font event listener
     * @param strict true if an Exception should be thrown if an error is found.
     */
    public DefaultFontConfigurator(FontManager fontManager, FontEventListener listener, boolean strict) {
        this.fontManager = fontManager;
        this.resourceResolver = fontManager.getResourceResolver();
        this.listener = listener;
        this.strict = strict;
    }

    /**
     * Initializes font info settings from the user configuration
     * @throws FOPException if an exception occurs while processing the configuration
     */
    public List<EmbedFontInfo> configure(FontConfig fontInfoConfig) throws FOPException {
        List<EmbedFontInfo> fontInfoList = new ArrayList<EmbedFontInfo>();
        if (fontInfoConfig != null) {
            assert fontInfoConfig instanceof DefaultFontConfig;
            DefaultFontConfig adobeFontInfoConfig = (DefaultFontConfig) fontInfoConfig;
            long start = 0;
            if (log.isDebugEnabled()) {
                log.debug("Starting font configuration...");
                start = System.currentTimeMillis();
            }
            FontAdder fontAdder = new FontAdder(fontManager, resourceResolver, listener);
            // native o/s search (autodetect) configuration
            fontManager.autoDetectFonts(adobeFontInfoConfig.isAutoDetectFonts(), fontAdder, strict,
                    listener, fontInfoList);
            // Add configured directories to FontInfo list
            addDirectories(adobeFontInfoConfig, fontAdder, fontInfoList);
            // Add configured fonts to FontInfo
            FontCache fontCache = fontManager.getFontCache();
            try {
                addFonts(adobeFontInfoConfig, fontCache, fontInfoList);
            } catch (URISyntaxException use) {
                LogUtil.handleException(log, use, strict);
            }
            // Update referenced fonts (fonts which are not to be embedded)
            fontManager.updateReferencedFonts(fontInfoList);
            // Renderer-specific referenced fonts
            List<String> referencedFonts = adobeFontInfoConfig.getReferencedFontFamily();
            if (referencedFonts.size() > 0) {
                FontTriplet.Matcher matcher = FontManagerConfigurator.createFontsMatcher(
                        referencedFonts, strict);
                fontManager.updateReferencedFonts(fontInfoList, matcher);
            }
            // Update font cache if it has changed
            fontManager.saveCache();
            if (log.isDebugEnabled()) {
                log.debug("Finished font configuration in "
                        + (System.currentTimeMillis() - start) + "ms");
            }
        }
        return Collections.unmodifiableList(fontInfoList);
    }

    private void addDirectories(DefaultFontConfig fontInfoConfig, FontAdder fontAdder,
            List<EmbedFontInfo> fontInfoList) throws FOPException {
        // directory (multiple font) configuration
        List<Directory> directories = fontInfoConfig.getDirectories();
        for (Directory directory : directories) {
            // add fonts found in directory
            FontFileFinder fontFileFinder = new FontFileFinder(directory.isRecursive() ? -1 : 1, listener);
            List<URL> fontURLList;
            try {
                fontURLList = fontFileFinder.find(directory.getDirectory());
                fontAdder.add(fontURLList, fontInfoList);
            } catch (IOException e) {
                LogUtil.handleException(log, e, strict);
            } catch (URISyntaxException use) {
                LogUtil.handleException(log, use, strict);
            }
        }
    }

    private void addFonts(DefaultFontConfig fontInfoConfig, FontCache fontCache,
            List<EmbedFontInfo> fontInfoList) throws FOPException, URISyntaxException {
        // font file (singular) configuration
        List<DefaultFontConfig.Font> fonts = fontInfoConfig.getFonts();
        for (DefaultFontConfig.Font font : fonts) {
            EmbedFontInfo embedFontInfo = getFontInfo(font, fontCache);
            if (embedFontInfo != null) {
                fontInfoList.add(embedFontInfo);
            }
        }
    }

    private EmbedFontInfo getFontInfo(DefaultFontConfig.Font font, FontCache fontCache)
            throws FOPException, URISyntaxException {
        String embed = font.getEmbedURI();
        String metrics = font.getMetrics();
        String afm = font.getAfm();
        String pfm = font.getPfm();
        URI embedUri = InternalResourceResolver.cleanURI(embed);
        URI metricsUri = metrics == null ? null : InternalResourceResolver.cleanURI(metrics);
        URI afmUri = (afm == null) ? null : InternalResourceResolver.cleanURI(afm);
        URI pfmUri = (pfm == null) ? null : InternalResourceResolver.cleanURI(pfm);
        FontUris fontUris = (afmUri != null || pfmUri != null) ? new FontUris(embedUri, metricsUri, afmUri,
                pfmUri) : new FontUris(embedUri, metricsUri);

        String subFont = font.getSubFont();
        List<FontTriplet> tripletList = font.getTripletList();

        // no font triplet info
        if (tripletList.size() == 0) {
            URI fontUri = resourceResolver.resolveFromBase(embedUri);
            FontInfoFinder finder = new FontInfoFinder();
            finder.setEventListener(listener);
            EmbedFontInfo[] infos = finder.find(fontUri, resourceResolver, fontCache);
            return infos[0]; //When subFont is set, only one font is returned
        }
        EncodingMode encodingMode = EncodingMode.getValue(font.getEncodingMode());
        EmbeddingMode embeddingMode = EmbeddingMode.getValue(font.getEmbeddingMode());
        EmbedFontInfo embedFontInfo = new EmbedFontInfo(fontUris, font.isKerning(), font.isAdvanced(),
                tripletList, subFont, encodingMode, embeddingMode);
        if (fontCache != null) {
            if (!fontCache.containsFont(embedFontInfo)) {
                fontCache.addFont(embedFontInfo, resourceResolver);
            }
        }

        if (log.isDebugEnabled()) {
            URI embedFile = embedFontInfo.getEmbedURI();
            log.debug("Adding font " + (embedFile != null ? embedFile + ", " : "")
                    + "metrics URI " + embedFontInfo.getMetricsURI());
            for (int j = 0; j < tripletList.size(); ++j) {
                FontTriplet triplet = tripletList.get(j);
                log.debug("  Font triplet "
                        + triplet.getName() + ", "
                        + triplet.getStyle() + ", "
                        + triplet.getWeight());
            }
        }
        return embedFontInfo;
    }
}
