/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef GFX_FONT_FAMILY_LIST_H
#define GFX_FONT_FAMILY_LIST_H

#include "nsDebug.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsTArray.h"
#include "mozilla/MemoryReporting.h"

namespace mozilla {

/**
 * type of font family name, either a name (e.g. Helvetica) or a
 * generic (e.g. serif, sans-serif), with the ability to distinguish
 * between unquoted and quoted names for serializaiton
 */ 

enum FontFamilyType : uint32_t {
  eFamily_none = 0,  // used when finding generics

  // explicitly named font family (e.g. Helvetica)
  eFamily_named,
  eFamily_named_quoted,

  // generics
  eFamily_serif,         // pref font code relies on this ordering!!!
  eFamily_sans_serif,
  eFamily_monospace,
  eFamily_cursive,
  eFamily_fantasy,

  // special
  eFamily_moz_variable,
  eFamily_moz_fixed,

  eFamily_generic_first = eFamily_serif,
  eFamily_generic_last = eFamily_fantasy,
  eFamily_generic_count = (eFamily_fantasy - eFamily_serif + 1)
};

enum QuotedName { eQuotedName, eUnquotedName };

/**
 * font family name, a string for the name if not a generic and
 * a font type indicated named family or which generic family
 */

struct FontFamilyName final {
    FontFamilyName()
        : mType(eFamily_named)
    {}

    // named font family - e.g. Helvetica
    explicit FontFamilyName(const nsAString& aFamilyName,
                            QuotedName aQuoted = eUnquotedName) {
        mType = (aQuoted == eQuotedName) ? eFamily_named_quoted : eFamily_named;
        mName = aFamilyName;
    }

    // generic font family - e.g. sans-serif
    explicit FontFamilyName(FontFamilyType aType) {
        NS_ASSERTION(aType != eFamily_named &&
                     aType != eFamily_named_quoted &&
                     aType != eFamily_none,
                     "expected a generic font type");
        mName.Truncate();
        mType = aType;
    }

    FontFamilyName(const FontFamilyName& aCopy) {
        mType = aCopy.mType;
        mName = aCopy.mName;
    }

    bool IsNamed() const {
        return mType == eFamily_named || mType == eFamily_named_quoted;
    }

    bool IsGeneric() const {
        return !IsNamed();
    }

    void AppendToString(nsAString& aFamilyList, bool aQuotes = true) const {
        switch (mType) {
            case eFamily_named:
                aFamilyList.Append(mName);
                break;
            case eFamily_named_quoted:
                if (aQuotes) {
                    aFamilyList.Append('"');
                }
                aFamilyList.Append(mName);
                if (aQuotes) {
                    aFamilyList.Append('"');
                }
                break;
            case eFamily_serif:
                aFamilyList.AppendLiteral("serif");
                break;
            case eFamily_sans_serif:
                aFamilyList.AppendLiteral("sans-serif");
                break;
            case eFamily_monospace:
                aFamilyList.AppendLiteral("monospace");
                break;
            case eFamily_cursive:
                aFamilyList.AppendLiteral("cursive");
                break;
            case eFamily_fantasy:
                aFamilyList.AppendLiteral("fantasy");
                break;
            case eFamily_moz_fixed:
                aFamilyList.AppendLiteral("-moz-fixed");
                break;
            default:
                break;
        }
    }

    // helper method that converts generic names to the right enum value
    static FontFamilyName
    Convert(const nsAString& aFamilyOrGenericName) {
        // should only be passed a single font - not entirely correct, a family
        // *could* have a comma in it but in practice never does so
        // for debug purposes this is fine
        NS_ASSERTION(aFamilyOrGenericName.FindChar(',') == -1,
                     "Convert method should only be passed a single family name");

        FontFamilyType genericType = eFamily_none;
        if (aFamilyOrGenericName.LowerCaseEqualsLiteral("serif")) {
            genericType = eFamily_serif;
        } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("sans-serif")) {
            genericType = eFamily_sans_serif;
        } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("monospace")) {
            genericType = eFamily_monospace;
        } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("cursive")) {
            genericType = eFamily_cursive;
        } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("fantasy")) {
            genericType = eFamily_fantasy;
        } else if (aFamilyOrGenericName.LowerCaseEqualsLiteral("-moz-fixed")) {
            genericType = eFamily_moz_fixed;
        } else {
            return FontFamilyName(aFamilyOrGenericName, eUnquotedName);
        }

        return FontFamilyName(genericType);
    }

    // memory reporting
    size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
        return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
    }

    FontFamilyType mType;
    nsString       mName; // empty if mType != eFamily_named
};

inline bool
operator==(const FontFamilyName& a, const FontFamilyName& b) {
    return a.mType == b.mType && a.mName == b.mName;
}

/**
 * font family list, array of font families and a default font type.
 * font family names are either named strings or generics. the default
 * font type is used to preserve the variable font fallback behavior
 */ 

class FontFamilyList {
public:
    FontFamilyList()
        : mDefaultFontType(eFamily_none)
    {
    }

    explicit FontFamilyList(FontFamilyType aGenericType)
        : mDefaultFontType(eFamily_none)
    {
        Append(FontFamilyName(aGenericType));
    }

    FontFamilyList(const nsAString& aFamilyName,
                   QuotedName aQuoted)
        : mDefaultFontType(eFamily_none)
    {
        Append(FontFamilyName(aFamilyName, aQuoted));
    }

    FontFamilyList(const FontFamilyList& aOther)
        : mFontlist(aOther.mFontlist)
        , mDefaultFontType(aOther.mDefaultFontType)
    {
    }

    void Append(const FontFamilyName& aFamilyName) {
        mFontlist.AppendElement(aFamilyName);
    }

    void Append(const nsTArray<nsString>& aFamilyNameList) {
        uint32_t len = aFamilyNameList.Length();
        for (uint32_t i = 0; i < len; i++) {
            mFontlist.AppendElement(FontFamilyName(aFamilyNameList[i],
                                                   eUnquotedName));
        }
    }

    void Clear() {
        mFontlist.Clear();
    }

    uint32_t Length() const {
        return mFontlist.Length();
    }

    bool IsEmpty() const {
      return mFontlist.IsEmpty();
    }

    const nsTArray<FontFamilyName>& GetFontlist() const {
        return mFontlist;
    }

    bool Equals(const FontFamilyList& aFontlist) const {
        return mFontlist == aFontlist.mFontlist &&
               mDefaultFontType == aFontlist.mDefaultFontType;
    }

    FontFamilyType FirstGeneric() const {
        uint32_t len = mFontlist.Length();
        for (uint32_t i = 0; i < len; i++) {
            const FontFamilyName& name = mFontlist[i];
            if (name.IsGeneric()) {
                return name.mType;
            }
        }
        return eFamily_none;
    }

    bool HasGeneric() const {
        return FirstGeneric() != eFamily_none;
    }

    bool HasDefaultGeneric() const {
        uint32_t len = mFontlist.Length();
        for (uint32_t i = 0; i < len; i++) {
            const FontFamilyName& name = mFontlist[i];
            if (name.mType == mDefaultFontType) {
                return true;
            }
        }
        return false;
    }

    // Find the first generic (but ignoring cursive and fantasy, as they are
    // rarely configured in any useful way) in the list.
    // If found, move it to the start and return true; else return false.
    bool PrioritizeFirstGeneric() {
        uint32_t len = mFontlist.Length();
        for (uint32_t i = 0; i < len; i++) {
            const FontFamilyName name = mFontlist[i];
            if (name.IsGeneric()) {
                if (name.mType == eFamily_cursive ||
                    name.mType == eFamily_fantasy) {
                    continue;
                }
                if (i > 0) {
                    mFontlist.RemoveElementAt(i);
                    mFontlist.InsertElementAt(0, name);
                }
                return true;
            }
        }
        return false;
    }

    void PrependGeneric(FontFamilyType aType) {
        mFontlist.InsertElementAt(0, FontFamilyName(aType));
    }

    void ToString(nsAString& aFamilyList,
                  bool aQuotes = true,
                  bool aIncludeDefault = false) const {
        aFamilyList.Truncate();
        uint32_t len = mFontlist.Length();
        for (uint32_t i = 0; i < len; i++) {
            if (i != 0) {
                aFamilyList.Append(',');
            }
            const FontFamilyName& name = mFontlist[i];
            name.AppendToString(aFamilyList, aQuotes);
        }
        if (aIncludeDefault && mDefaultFontType != eFamily_none) {
            if (!aFamilyList.IsEmpty()) {
                aFamilyList.Append(',');
            }
            if (mDefaultFontType == eFamily_serif) {
                aFamilyList.AppendLiteral("serif");
            } else {
                aFamilyList.AppendLiteral("sans-serif");
            }
        }
    }

    // searches for a specific non-generic name, lowercase comparison
    bool Contains(const nsAString& aFamilyName) const {
        uint32_t len = mFontlist.Length();
        nsAutoString fam(aFamilyName);
        ToLowerCase(fam);
        for (uint32_t i = 0; i < len; i++) {
            const FontFamilyName& name = mFontlist[i];
            if (name.mType != eFamily_named &&
                name.mType != eFamily_named_quoted) {
                continue;
            }
            nsAutoString listname(name.mName);
            ToLowerCase(listname);
            if (listname.Equals(fam)) {
                return true;
            }
        }
        return false;
    }

    FontFamilyType GetDefaultFontType() const { return mDefaultFontType; }
    void SetDefaultFontType(FontFamilyType aType) {
        NS_ASSERTION(aType == eFamily_none || aType == eFamily_serif ||
                     aType == eFamily_sans_serif,
                     "default font type must be either serif or sans-serif");
        mDefaultFontType = aType;
    }

    // memory reporting
    size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
        size_t n = 0;
        n += mFontlist.ShallowSizeOfExcludingThis(aMallocSizeOf);
        for (size_t i = 0; i < mFontlist.Length(); i++) {
            n += mFontlist[i].SizeOfExcludingThis(aMallocSizeOf);
        }
        return n;
    }

    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
        return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    }

private:
    nsTArray<FontFamilyName>   mFontlist;
    FontFamilyType             mDefaultFontType; // none, serif or sans-serif
};

inline bool
operator==(const FontFamilyList& a, const FontFamilyList& b) {
    return a.Equals(b);
}

} // namespace mozilla

#endif /* GFX_FONT_FAMILY_LIST_H */
