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

import java.util.List;
import java.util.Map;
import java.util.Set;

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

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.FontUtil;

/**
 * Encapsulates a font substitution qualifier
 */
public class FontQualifier {

    /** logger instance */
    private static Log log = LogFactory.getLog(FontQualifier.class);

    /** font family attribute value */
    private AttributeValue fontFamilyAttributeValue;

    /** font style attribute value */
    private AttributeValue fontStyleAttributeValue;

    /** font weight attribute value */
    private AttributeValue fontWeightAttributeValue;

    /**
     * Default constructor
     */
    public FontQualifier() {
    }

    /**
     * Sets the font family
     * @param fontFamily the font family
     */
    public void setFontFamily(String fontFamily) {
        AttributeValue fontFamilyAttribute = AttributeValue.valueOf(fontFamily);
        if (fontFamilyAttribute == null) {
            log.error("Invalid font-family value '" + fontFamily + "'");
            return;
        }
        this.fontFamilyAttributeValue = fontFamilyAttribute;
    }

    /**
     * Sets the font style
     * @param fontStyle the font style
     */
    public void setFontStyle(String fontStyle) {
        AttributeValue fontStyleAttribute = AttributeValue.valueOf(fontStyle);
        if (fontStyleAttribute != null) {
            this.fontStyleAttributeValue = fontStyleAttribute;
        }
    }

    /**
     * Sets the font weight
     * @param fontWeight the font weight
     */
    public void setFontWeight(String fontWeight) {
        AttributeValue fontWeightAttribute = AttributeValue.valueOf(fontWeight);
        if (fontWeightAttribute != null) {
            for (Object weightObj : fontWeightAttribute) {
                if (weightObj instanceof String) {
                    String weightString = ((String) weightObj).trim();
                    try {
                        FontUtil.parseCSS2FontWeight(weightString);
                    } catch (IllegalArgumentException ex) {
                        log.error("Invalid font-weight value '" + weightString + "'");
                        return;
                    }
                }
            }
            this.fontWeightAttributeValue = fontWeightAttribute;
        }
    }

    /**
     * @return the font family attribute
     */
    public AttributeValue getFontFamily() {
        return this.fontFamilyAttributeValue;
    }

    /**
     * @return the font style attribute
     */
    public AttributeValue getFontStyle() {
        if (fontStyleAttributeValue == null) {
            return AttributeValue.valueOf(Font.STYLE_NORMAL);
        }
        return this.fontStyleAttributeValue;
    }

    /**
     * @return the font weight attribute
     */
    public AttributeValue getFontWeight() {
        if (fontWeightAttributeValue == null) {
            return AttributeValue.valueOf(Integer.toString(Font.WEIGHT_NORMAL));
        }
        return this.fontWeightAttributeValue;
    }

    /**
     * @return true if this rule has a font weight
     */
    public boolean hasFontWeight() {
        return this.fontWeightAttributeValue != null;
    }

    /**
     * @return true if this rule has a font style
     */
    public boolean hasFontStyle() {
        return this.fontStyleAttributeValue != null;
    }

    /**
     * Returns a list of matching font triplet found in a given font info
     *
     * @param fontInfo the font info
     * @return a list of matching font triplets
     */
    protected List<FontTriplet> match(FontInfo fontInfo) {
        AttributeValue fontFamilyValue = getFontFamily();
        AttributeValue weightValue = getFontWeight();
        AttributeValue styleValue = getFontStyle();

        List<FontTriplet> matchingTriplets = new java.util.ArrayList<FontTriplet>();

        // try to find matching destination font triplet
        for (Object aFontFamilyValue : fontFamilyValue) {
            String fontFamilyString = (String) aFontFamilyValue;
            Map<FontTriplet, String> triplets = fontInfo.getFontTriplets();
            if (triplets != null) {
                Set<FontTriplet> tripletSet = triplets.keySet();
                for (Object aTripletSet : tripletSet) {
                    FontTriplet triplet = (FontTriplet) aTripletSet;
                    String fontName = triplet.getName();

                    // matched font family name
                    if (fontFamilyString.toLowerCase().equals(fontName.toLowerCase())) {

                        // try and match font weight
                        boolean weightMatched = false;
                        int fontWeight = triplet.getWeight();
                        for (Object weightObj : weightValue) {
                            if (weightObj instanceof FontWeightRange) {
                                FontWeightRange intRange = (FontWeightRange) weightObj;
                                if (intRange.isWithinRange(fontWeight)) {
                                    weightMatched = true;
                                }
                            } else if (weightObj instanceof String) {
                                String fontWeightString = (String) weightObj;
                                int fontWeightValue = FontUtil.parseCSS2FontWeight(
                                        fontWeightString);
                                if (fontWeightValue == fontWeight) {
                                    weightMatched = true;
                                }
                            } else if (weightObj instanceof Integer) {
                                Integer fontWeightInteger = (Integer) weightObj;
                                int fontWeightValue = fontWeightInteger;
                                if (fontWeightValue == fontWeight) {
                                    weightMatched = true;
                                }
                            }
                        }

                        // try and match font style
                        boolean styleMatched = false;
                        String fontStyleString = triplet.getStyle();
                        for (Object aStyleValue : styleValue) {
                            String style = (String) aStyleValue;
                            if (fontStyleString.equals(style)) {
                                styleMatched = true;
                            }
                        }

                        if (weightMatched && styleMatched) {
                            matchingTriplets.add(triplet);
                        }
                    }
                }
            }
        }
        return matchingTriplets;
    }

    /**
     * Returns the highest priority matching font triplet found in a given font info
     * @param fontInfo the font info
     * @return the highest priority matching font triplet
     */
    protected FontTriplet bestMatch(FontInfo fontInfo) {
        List<FontTriplet> matchingTriplets = match(fontInfo);
        FontTriplet bestTriplet = null;
        if (matchingTriplets.size() == 1) {
            bestTriplet = matchingTriplets.get(0);
        } else {
            for (Object matchingTriplet : matchingTriplets) {
                FontTriplet triplet = (FontTriplet) matchingTriplet;
                if (bestTriplet == null) {
                    bestTriplet = triplet;
                } else {
                    int priority = triplet.getPriority();
                    if (priority < bestTriplet.getPriority()) {
                        bestTriplet = triplet;
                    }
                }
            }
        }
        return bestTriplet;
    }

    /**
     * @return a list of font triplets matching this qualifier
     */
    public List<FontTriplet> getTriplets() {
        List<FontTriplet> triplets = new java.util.ArrayList<FontTriplet>();

        AttributeValue fontFamilyValue = getFontFamily();
        for (Object aFontFamilyValue : fontFamilyValue) {
            String name = (String) aFontFamilyValue;

            AttributeValue styleValue = getFontStyle();
            for (Object aStyleValue : styleValue) {
                String style = (String) aStyleValue;

                AttributeValue weightValue = getFontWeight();
                for (Object weightObj : weightValue) {
                    if (weightObj instanceof FontWeightRange) {
                        FontWeightRange fontWeightRange = (FontWeightRange) weightObj;
                        int[] weightRange = fontWeightRange.toArray();
                        for (int aWeightRange : weightRange) {
                            triplets.add(new FontTriplet(name, style, aWeightRange));
                        }
                    } else if (weightObj instanceof String) {
                        String weightString = (String) weightObj;
                        int weight = FontUtil.parseCSS2FontWeight(weightString);
                        triplets.add(new FontTriplet(name, style, weight));
                    } else if (weightObj instanceof Integer) {
                        Integer weightInteger = (Integer) weightObj;
                        int weight = weightInteger;
                        triplets.add(new FontTriplet(name, style, weight));
                    }
                }
            }
        }
        return triplets;
    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        String str = "";
        if (fontFamilyAttributeValue != null) {
            str += "font-family=" + fontFamilyAttributeValue;
        }
        if (fontStyleAttributeValue != null) {
            if (str.length() > 0) {
                str += ", ";
            }
            str += "font-style=" + fontStyleAttributeValue;
        }
        if (fontWeightAttributeValue != null) {
            if (str.length() > 0) {
                str += ", ";
            }
            str += "font-weight=" + fontWeightAttributeValue;
        }
        return str;
    }
}
