/*
 * Copyright (C) 2020 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

WI.FontDetailsPanel = class FontDetailsPanel extends WI.StyleDetailsPanel
{
    constructor(delegate)
    {
        const className = "font";
        const identifier = "font";
        const label = WI.UIString("Font", "Font @ Font Details Sidebar Title", "Title for the Font details sidebar.");
        super(delegate, className, identifier, label);

        this._fontPropertiesMap = new Map;
        this._fontVariationsMap = new Map;
        this._fontFeaturesMap = new Map;
    }

    // Public

    get supportsToggleCSSClass()
    {
        return false;
    }

    refresh(significantChange)
    {
        super.refresh(significantChange);

        let fontMap = this.nodeStyles.computedPrimaryFont?.calculateFontProperties(this.nodeStyles) ?? {};
        this._fontPropertiesMap = fontMap.propertiesMap ?? new Map;
        this._fontVariationsMap = fontMap.variationsMap ?? new Map;
        this._fontFeaturesMap = fontMap.featuresMap ?? new Map;

        this._fontNameRow.value = this.nodeStyles.computedPrimaryFont.name;

        this._fontSizeRow.value = this._formatSizeValue(this._fontPropertiesMap.get("font-size"));
        this._fontStyleRow.value = this._formatStyleValue(this._fontPropertiesMap.get("font-style"));
        this._fontWeightRow.value = this._formatSimpleSingleValue(this._fontPropertiesMap.get("font-weight"), "wght", "%s");
        this._fontStretchRow.value = this._formatSimpleSingleValue(this._fontPropertiesMap.get("font-stretch"), "wdth", WI.UIString("%s%%", "%s%% @ Font Details Sidebar", "A single value expressed as a percentage where the value has already been converted from a number to a string."));

        this._fontVariantLigaturesRow.value = this._formatLigatureValue(this._fontPropertiesMap.get("font-variant-ligatures"));
        this._fontVariantPositionRow.value = this._formatPositionValue(this._fontPropertiesMap.get("font-variant-position"));
        this._fontVariantCapsRow.value = this._formatCapitalsValue(this._fontPropertiesMap.get("font-variant-caps"));
        this._fontVariantNumericRow.value = this._formatNumericValue(this._fontPropertiesMap.get("font-variant-numeric"));
        this._fontVariantAlternatesRow.value = this._formatAlternatesValue(this._fontPropertiesMap.get("font-variant-alternates"));
        this._fontVariantEastAsianRow.value = this._formatEastAsianValue(this._fontPropertiesMap.get("font-variant-east-asian"));

        let featureRows = [];
        for (let [key, value] of this._fontFeaturesMap)
            featureRows.push(new WI.DetailsSectionSimpleRow(key, value));
        this._fontAdditionalFeaturesGroup.rows = featureRows;
        this._fontAdditionalFeaturesGroup.hidden = !featureRows.length;

        let variationRows = [];
        for (let [key, value] of this._fontVariationsMap) {
            let name = value.name ? WI.UIString("%s (%s)").format(value.name, key) : key;
            variationRows.push(new WI.DetailsSectionSimpleRow(name, this._formatVariationValue(value)));
        }
        if (!variationRows.length) {
            let emptyRow = new WI.DetailsSectionRow(WI.UIString("No additional variation axes.", "No additional variation axes. @ Font Details Sidebar", "Message shown when there are no additional variation axes to show."));
            emptyRow.showEmptyMessage();
            variationRows.push(emptyRow);
        }
        this._fontVariationsGroup.rows = variationRows;
    }

    // Protected

    initialLayout()
    {
        super.initialLayout();

        // Identity
        this._fontNameRow = new WI.DetailsSectionSimpleRow(WI.UIString("Name", "Name @ Font Details Sidebar Property", "Property title for the family name of the font."));
        let previewGroup = new WI.DetailsSectionGroup([this._fontNameRow]);

        let fontNameSection = new WI.DetailsSection("font-identity", WI.UIString("Identity", "Identity @ Font Details Sidebar Section", "Section title for font identity information."), [previewGroup]);
        this.element.appendChild(fontNameSection.element);

        // Basic Properties
        this._fontSizeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Size", "Size @ Font Details Sidebar Property", "Property title for `font-size`."));
        this._fontStyleRow = new WI.DetailsSectionSimpleRow(WI.UIString("Style", "Style @ Font Details Sidebar Property", "Property title for `font-style`."));
        this._fontWeightRow = new WI.DetailsSectionSimpleRow(WI.UIString("Weight", "Weight @ Font Details Sidebar Property", "Property title for `font-weight`."));
        this._fontStretchRow = new WI.DetailsSectionSimpleRow(WI.UIString("Stretch", "Stretch @ Font Details Sidebar Property", "Property title for `font-stretch`."));
        let basicPropertiesGroup = new WI.DetailsSectionGroup([this._fontSizeRow, this._fontStyleRow, this._fontWeightRow, this._fontStretchRow]);

        let fontBasicPropertiesSection = new WI.DetailsSection("font-basic-properties", WI.UIString("Basic Properties", "Basic Properties @ Font Details Sidebar Section", "Section title for basic font properties."), [basicPropertiesGroup]);
        this.element.appendChild(fontBasicPropertiesSection.element);

        // Feature Properties
        this._fontVariantLigaturesRow = new WI.DetailsSectionSimpleRow(WI.UIString("Ligatures", "Ligatures @ Font Details Sidebar Property", "Property title for `font-variant-ligatures`."));
        this._fontVariantPositionRow = new WI.DetailsSectionSimpleRow(WI.UIString("Position", "Position @ Font Details Sidebar Property", "Property title for `font-variant-position`."));
        this._fontVariantCapsRow = new WI.DetailsSectionSimpleRow(WI.UIString("Capitals", "Capitals @ Font Details Sidebar Property", "Property title for `font-variant-caps`."));
        this._fontVariantNumericRow = new WI.DetailsSectionSimpleRow(WI.UIString("Numeric", "Numeric @ Font Details Sidebar Property", "Property title for `font-variant-numeric`."));
        this._fontVariantAlternatesRow = new WI.DetailsSectionSimpleRow(WI.UIString("Alternate Glyphs", "Alternate Glyphs @ Font Details Sidebar Property", "Property title for `font-variant-alternates`."));
        this._fontVariantEastAsianRow = new WI.DetailsSectionSimpleRow(WI.UIString("East Asian", "East Asian @ Font Details Sidebar Property", "Property title for `font-variant-east-asian`."));
        let featurePropertiesGroup = new WI.DetailsSectionGroup([this._fontVariantLigaturesRow, this._fontVariantPositionRow, this._fontVariantCapsRow, this._fontVariantNumericRow, this._fontVariantAlternatesRow, this._fontVariantEastAsianRow]);

        this._fontAdditionalFeaturesGroup = new WI.DetailsSectionGroup;

        let fontFeaturePropertiesSection = new WI.DetailsSection("font-feature-properties", WI.UIString("Feature Properties", "Feature Properties @ Font Details Sidebar Section", "Section title for font feature properties."), [featurePropertiesGroup, this._fontAdditionalFeaturesGroup]);
        this.element.appendChild(fontFeaturePropertiesSection.element);

        // Variation Properties
        this._fontVariationsGroup = new WI.DetailsSectionGroup;

        let fontVariationPropertiesSection = new WI.DetailsSection("font-variation-properties", WI.UIString("Variation Properties", "Variation Properties @ Font Details Sidebar Section", "Section title for font variation properties."), [this._fontVariationsGroup]);
        this.element.appendChild(fontVariationPropertiesSection.element);
    }

    _formatSizeValue(property)
    {
        return property.value;
    }

    _formatStyleValue(property)
    {
        // `slnt` is expressed in negated values relative to `oblique`.
        if (this._hasVariationValue(property, "slnt"))
            return WI.UIString("Oblique %ddeg", "Oblique %ddeg @ Font Details Sidebar Property Value", "Property value for oblique text.").format(-1 * parseFloat(property.variations.get("slnt").value));

        if (this._hasVariationValue(property, "ital"))
            return WI.UIString("Italic", "Italic @ Font Details Sidebar Property Value", "Property value for oblique text.");

        if (property.value === "normal")
            return WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value.");

        return property.value;
    }

    _formatSimpleSingleValue(property, variationTag, variationFormat)
    {
        let result;
        if (this._hasVariationValue(property, variationTag))
            result = variationFormat.format(this._formatAxisValueAsString(property.variations.get(variationTag).value));
        else
            result = this._formatAxisValueAsString(property.value);

        if (this._hasVariationValue(property, variationTag, {optional: true})) {
            let axis = property.variations.get(variationTag);
            return this._createVariationValueElement(result, axis.minimumValue, axis.maximumValue, axis.defaultValue);
        }

        return result;
    }

    _formatVariationValue(variation)
    {
        let value = variation.value || variation.value === 0 ? variation.value : variation.defaultValue;
        return this._createVariationValueElement(this._formatAxisValueAsString(value), variation.minimumValue, variation.maximumValue, variation.defaultValue);
    }

    _createVariationValueElement(value, minimumValue, maximumValue, defaultValue)
    {
        let valueElement = document.createElement("div");
        valueElement.textContent = value;

        let secondaryElement = valueElement.appendChild(document.createElement("span"));
        secondaryElement.className = "secondary";
        secondaryElement.textContent = WI.UIString(" (Range: %s-%s, Default: %s)", " (Range: %s-%s, Default: %s) @ Font Details Sidebar", "A range and default value for a single variation axis of a font.").format(this._formatAxisValueAsString(minimumValue), this._formatAxisValueAsString(maximumValue), this._formatAxisValueAsString(defaultValue));
        
        return valueElement;
    }

    _formatAxisValueAsString(value)
    {
        const options = {
            minimumFractionDigits: 0,
            maximumFractionDigits: 2,
            useGrouping: false,
        }
        return value.toLocaleString(undefined, options);
    }

    _formatSimpleFeatureValues(property, features, noMatchesResult)
    {
        let valueParts = property.value.match(WI.Font.SettingPattern);
        let results = [];
        // Only one result should be pushed per group.
        let resultGroups = new Set;

        for (let feature of features) {
            if ((!feature.group || !resultGroups.has(feature.group)) && this._featureIsEnabled(property, feature.tag, valueParts.includes(feature.name))) {
                results.push(feature.result);
                resultGroups.add(feature.group);
            }
        }

        if (results.length)
            return results.join(WI.UIString(", "));
        return noMatchesResult;
    }

    _formatLigatureValue(property)
    {
        let valueParts = property.value.match(WI.Font.SettingPattern);

        let results = [];

        // Common ligatures is a special case where it is on by default, so we have to check if they are disabled.
        if (!this._featureIsEnabled(property, WI.unlocalizedString("clig"), !valueParts.includes(WI.unlocalizedString("no-common-ligatures"))))
            results.push(WI.UIString("Disabled Common", "Disabled Common @ Font Details Sidebar Property Value", "Property value for `font-variant-ligatures: no-common-ligatures`."));
        else
            results.push(WI.UIString("Common", "Common @ Font Details Sidebar Property Value", "Property value for `font-variant-ligatures: common-ligatures`."));

        // Add the other ligature features only if there was at least one to avoid concatinating with an empty string.
        let otherResults = this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("dlig"),
                name: WI.unlocalizedString("discretionary-ligatures"),
                result: WI.UIString("Discretionary", "Discretionary @ Font Details Sidebar Property Value", "Property value for `font-variant-ligatures: discretionary-ligatures`."),
            },
            {
                tag: WI.unlocalizedString("hlig"),
                name: WI.unlocalizedString("historical-ligatures"),
                result: WI.UIString("Historical", "Historical @ Font Details Sidebar Property Value", "Property value for `font-variant-ligatures: historical-ligatures`."),
            },
            {
                tag: WI.unlocalizedString("calt"),
                name: WI.unlocalizedString("contextual"),
                result: WI.UIString("Contextual Alternates", "Contextual Alternates @ Font Details Sidebar Property Value", "Property value for `font-variant-ligatures: contextual`."),
            },
        ]);
        if (otherResults)
            results.push(otherResults);

        return results.join(WI.UIString(", "));
    }

    _formatPositionValue(property)
    {
        return this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("subs"),
                name: WI.unlocalizedString("sub"),
                result: WI.UIString("Subscript", "Subscript @ Font Details Sidebar Property Value", "Property value for `font-variant-position: sub`."),
                group: "position",
            },
            {
                tag: WI.unlocalizedString("sups"),
                name: WI.unlocalizedString("super"),
                result: WI.UIString("Superscript", "Superscript @ Font Details Sidebar Property Value", "Property value for `font-variant-position: super`."),
                group: "position",
            },
        ], WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value."));
    }

    _formatCapitalsValue(property)
    {
        return this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("c2sc"),
                name: WI.unlocalizedString("all-small-caps"),
                result: WI.UIString("All Small Capitals", "All Small Capitals @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: all-small-caps`."),
                group: "capitals",
            },
            {
                tag: WI.unlocalizedString("smcp"),
                name: WI.unlocalizedString("small-caps"),
                result: WI.UIString("Small Capitals", "Small Capitals @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: small-caps`."),
                group: "capitals",
            },
            {
                tag: WI.unlocalizedString("c2pc"),
                name: WI.unlocalizedString("all-petite-caps"),
                result: WI.UIString("All Petite Capitals", "All Petite Capitals @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: all-petite-caps`."),
                group: "capitals",
            },
            {
                tag: WI.unlocalizedString("pcap"),
                name: WI.unlocalizedString("petite-caps"),
                result: WI.UIString("Petite Capitals", "Petite Capitals @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: petite-caps`."),
                group: "capitals",
            },
            {
                tag: WI.unlocalizedString("unic"),
                name: WI.unlocalizedString("unicase"),
                result: WI.UIString("Unicase", "Unicase @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: unicase`."),
                group: "capitals",
            },
            {
                tag: WI.unlocalizedString("titl"),
                name: WI.unlocalizedString("titling-caps"),
                result: WI.UIString("Titling Capitals", "Titling Capitals @ Font Details Sidebar Property Value", "Property value for `font-variant-capitals: titling-caps`."),
                group: "capitals",
            },
        ], WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value."));
    }

    _formatNumericValue(property)
    {
        return this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("lnum"),
                name: WI.unlocalizedString("lining-nums"),
                result: WI.UIString("Lining Numerals", "Lining Numerals @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: lining-nums`."),
                group: "figures",
            },
            {
                tag: WI.unlocalizedString("onum"),
                name: WI.unlocalizedString("oldstyle-nums"),
                result: WI.UIString("Old-Style Numerals", "Old-Style Numerals @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: oldstyle-nums`."),
                group: "figures",
            },
            {
                tag: WI.unlocalizedString("pnum"),
                name: WI.unlocalizedString("proportional-nums"),
                result: WI.UIString("Proportional Numerals", "Proportional Numerals @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: proportional-nums`."),
                group: "spacing",
            },
            {
                tag: WI.unlocalizedString("tnum"),
                name: WI.unlocalizedString("tabular-nums"),
                result: WI.UIString("Tabular Numerals", "Tabular Numerals @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: tabular-nums`."),
                group: "spacing",
            },
            {
                tag: WI.unlocalizedString("frac"),
                name: WI.unlocalizedString("diagonal-fractions"),
                result: WI.UIString("Diagonal Fractions", "Diagonal Fractions @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: diagonal-fractions`."),
                group: "fractions",
            },
            {
                tag: WI.unlocalizedString("afrc"),
                name: WI.unlocalizedString("stacked-fractions"),
                result: WI.UIString("Stacked Fractions", "Stacked Fractions @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: stacked-fractions`."),
                group: "fractions",
            },
            {
                tag: WI.unlocalizedString("ordn"),
                name: WI.unlocalizedString("ordinal"),
                result: WI.UIString("Ordinal Letter Forms", "Ordinal Letter Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: ordinal`."),
            },
            {
                tag: WI.unlocalizedString("zero"),
                name: WI.unlocalizedString("slashed-zero"),
                result: WI.UIString("Slashed Zeros", "Slashed Zeros @ Font Details Sidebar Property Value", "Property value for `font-variant-numeric: slashed-zero`."),
            },
        ], WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value."));
    }

    _formatAlternatesValue(property)
    {
        return this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("hist"),
                name: WI.unlocalizedString("historical-forms"),
                result: WI.UIString("Historical Forms", "Historical Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: historical-forms`."),
            },
        ], WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value."));
    }

    _formatEastAsianValue(property)
    {
        return this._formatSimpleFeatureValues(property, [
            {
                tag: WI.unlocalizedString("jp78"),
                name: WI.unlocalizedString("jis78"),
                result: WI.UIString("JIS78 Forms", "JIS78 Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: jis78`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("jp83"),
                name: WI.unlocalizedString("jis83"),
                result: WI.UIString("JIS83 Forms", "JIS83 Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: jis83`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("jp90"),
                name: WI.unlocalizedString("jis90"),
                result: WI.UIString("JIS90 Forms", "JIS90 Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: jis90`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("jp04"),
                name: WI.unlocalizedString("jis04"),
                result: WI.UIString("JIS2004 Forms", "JIS2004 Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: jis04`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("smpl"),
                name: WI.unlocalizedString("simplified"),
                result: WI.UIString("Simplified Forms", "Simplified Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: simplified`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("trad"),
                name: WI.unlocalizedString("traditional"),
                result: WI.UIString("Traditional Forms", "Traditional Forms @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: traditional`."),
                group: "forms",
            },
            {
                tag: WI.unlocalizedString("fwid"),
                name: WI.unlocalizedString("full-width"),
                result: WI.UIString("Full-Width Variants", "Full-Width Variants @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: full-width`."),
                group: "width",
            },
            {
                tag: WI.unlocalizedString("pwid"),
                name: WI.unlocalizedString("proportional-width"),
                result: WI.UIString("Proportional-Width Variants", "Proportional-Width Variants @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: proportional-width`."),
                group: "width",
            },
            {
                tag: WI.unlocalizedString("ruby"),
                name: WI.unlocalizedString("ruby"),
                result: WI.UIString("Ruby Glyphs", "Ruby Glyphs @ Font Details Sidebar Property Value", "Property value for `font-variant-alternates: ruby`."),
            },
        ], WI.UIString("Normal", "Normal @ Font Details Sidebar Property Value", "Property value for any `normal` CSS value."));
    }

    _featureIsEnabled(property, featureTag, tagNotPresentCondition)
    {
        let featureValue = property.features?.get(featureTag);
        if (featureValue || featureValue === 0)
            return !!featureValue;
        return tagNotPresentCondition;
    }

    _hasVariationValue(property, variationTag, {optional} = {})
    {
        if (property.variations?.has(variationTag))
            return property.variations.get(variationTag).value || optional;
        return false;
    }
};
