/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <sal/config.h>

#include <rtl/ustring.hxx>
#include <unotools/fontdefs.hxx>

#include <font/PhysicalFontFaceCollection.hxx>
#include <font/PhysicalFontCollection.hxx>
#include <utility>

namespace vcl::font
{

void PhysicalFontFamily::CalcType( ImplFontAttrs& rType, FontWeight& rWeight, FontWidth& rWidth,
                                   FontFamily eFamily, const utl::FontNameAttr* pFontAttr )
{
    if ( eFamily != FAMILY_DONTKNOW )
    {
        if ( eFamily == FAMILY_SWISS )
            rType |= ImplFontAttrs::SansSerif;
        else if ( eFamily == FAMILY_ROMAN )
            rType |= ImplFontAttrs::Serif;
        else if ( eFamily == FAMILY_SCRIPT )
            rType |= ImplFontAttrs::Script;
        else if ( eFamily == FAMILY_MODERN )
            rType |= ImplFontAttrs::Fixed;
        else if ( eFamily == FAMILY_DECORATIVE )
            rType |= ImplFontAttrs::Decorative;
    }

    if ( pFontAttr )
    {
        rType |= pFontAttr->Type;

        if ( ((rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL)) &&
             (pFontAttr->Weight != WEIGHT_DONTKNOW) )
            rWeight = pFontAttr->Weight;
        if ( ((rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL)) &&
             (pFontAttr->Width != WIDTH_DONTKNOW) )
            rWidth = pFontAttr->Width;
    }
}

static ImplFontAttrs lcl_IsCJKFont( std::u16string_view rFontName )
{
    // Test, if Fontname includes CJK characters --> In this case we
    // mention that it is a CJK font
    for (const sal_Unicode& ch : rFontName)
    {
        // japanese
        if ( ((ch >= 0x3040) && (ch <= 0x30FF)) ||
             ((ch >= 0x3190) && (ch <= 0x319F)) )
            return ImplFontAttrs::CJK|ImplFontAttrs::CJK_JP;

        // korean
        if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) ||
             ((ch >= 0xA960) && (ch <= 0xA97F)) ||
             ((ch >= 0xD7B0) && (ch <= 0xD7FF)) ||
             ((ch >= 0x3130) && (ch <= 0x318F)) ||
             ((ch >= 0x1100) && (ch <= 0x11FF)) )
            return ImplFontAttrs::CJK|ImplFontAttrs::CJK_KR;

        // chinese
        if ( (ch >= 0x3400) && (ch <= 0x9FFF) )
            return ImplFontAttrs::CJK|ImplFontAttrs::CJK_TC|ImplFontAttrs::CJK_SC;

        // cjk
        if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) ||
             ((ch >= 0xFF00) && (ch <= 0xFFEE)) )
            return ImplFontAttrs::CJK;

    }

    return ImplFontAttrs::None;
}

PhysicalFontFamily::PhysicalFontFamily( OUString aSearchName )
:   maSearchName(std::move( aSearchName )),
    mnTypeFaces( FontTypeFaces::NONE ),
    meFamily( FAMILY_DONTKNOW ),
    mePitch( PITCH_DONTKNOW ),
    mnMinQuality( -1 ),
    mnMatchType( ImplFontAttrs::None ),
    meMatchWeight( WEIGHT_DONTKNOW ),
    meMatchWidth( WIDTH_DONTKNOW )
{}

PhysicalFontFamily::~PhysicalFontFamily()
{
}

void PhysicalFontFamily::AddFontFace( PhysicalFontFace* pNewFontFace )
{
    if( maFontFaces.empty() )
    {
        maFamilyName   = pNewFontFace->GetFamilyName();
        meFamily       = pNewFontFace->GetFamilyType();
        mePitch        = pNewFontFace->GetPitch();
        mnMinQuality   = pNewFontFace->GetQuality();
    }
    else
    {
        if( meFamily == FAMILY_DONTKNOW )
            meFamily = pNewFontFace->GetFamilyType();
        if( mePitch == PITCH_DONTKNOW )
            mePitch = pNewFontFace->GetPitch();
        if( mnMinQuality > pNewFontFace->GetQuality() )
            mnMinQuality = pNewFontFace->GetQuality();
    }

    // set attributes for attribute based font matching
    mnTypeFaces |= FontTypeFaces::Scalable;

    if( pNewFontFace->IsMicrosoftSymbolEncoded() )
        mnTypeFaces |= FontTypeFaces::Symbol;
    else
        mnTypeFaces |= FontTypeFaces::NoneSymbol;

    if( pNewFontFace->GetWeight() != WEIGHT_DONTKNOW )
    {
        if( pNewFontFace->GetWeight() >= WEIGHT_SEMIBOLD )
            mnTypeFaces |= FontTypeFaces::Bold;
        else if( pNewFontFace->GetWeight() <= WEIGHT_SEMILIGHT )
            mnTypeFaces |= FontTypeFaces::Light;
        else
            mnTypeFaces |= FontTypeFaces::Normal;
    }

    if( pNewFontFace->GetItalic() == ITALIC_NONE )
        mnTypeFaces |= FontTypeFaces::NoneItalic;
    else if( (pNewFontFace->GetItalic() == ITALIC_NORMAL)
         ||  (pNewFontFace->GetItalic() == ITALIC_OBLIQUE) )
        mnTypeFaces |= FontTypeFaces::Italic;

    // reassign name (sharing saves memory)
    if( pNewFontFace->GetFamilyName() == GetFamilyName() )
        pNewFontFace->SetFamilyName( GetFamilyName() );

    // add the new physical font face, replacing existing font face if necessary
    // TODO: get rid of linear search?
    auto it(maFontFaces.begin());
    for (; it != maFontFaces.end(); ++it)
    {
        PhysicalFontFace* pFoundFontFace = it->get();
        sal_Int32 eComp = pNewFontFace->CompareIgnoreSize( *pFoundFontFace );
        if( eComp > 0 )
            continue;
        if( eComp < 0 )
            break;

        // ignore duplicate if its quality is worse
        if( pNewFontFace->GetQuality() < pFoundFontFace->GetQuality() )
            return;

        // keep the device font if its quality is good enough
        if( pNewFontFace->GetQuality() == pFoundFontFace->GetQuality() )
            return;

        // replace existing font face with a better one
        *it = pNewFontFace; // insert at sort position
        return;
    }

    maFontFaces.emplace(it, pNewFontFace); // insert at sort position
}

// get font attributes using the normalized font family name
void PhysicalFontFamily::InitMatchData( const utl::FontSubstConfiguration& rFontSubst,
    const OUString& rSearchName )
{
    OUString aShortName;
    OUString aMatchFamilyName(maMatchFamilyName);
    // get font attributes from the decorated font name
    utl::FontSubstConfiguration::getMapName( rSearchName, aShortName, aMatchFamilyName,
                            meMatchWeight, meMatchWidth, mnMatchType );
    maMatchFamilyName = aMatchFamilyName;
    const utl::FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( rSearchName );
    // eventually use the stripped name
    if( !pFontAttr )
        if( aShortName != rSearchName )
            pFontAttr = rFontSubst.getSubstInfo( aShortName );
    CalcType( mnMatchType, meMatchWeight, meMatchWidth, meFamily, pFontAttr );
    mnMatchType |= lcl_IsCJKFont( maFamilyName );
}

PhysicalFontFace* PhysicalFontFamily::FindBestFontFace( const vcl::font::FontSelectPattern& rFSD ) const
{
    if( maFontFaces.empty() )
        return nullptr;
    if( maFontFaces.size() == 1)
        return maFontFaces[0].get();

    // FontName+StyleName should map to FamilyName+StyleName
    const OUString& rSearchName = rFSD.maTargetName;
    OUString aTargetStyleName;
    const OUString* pTargetStyleName = nullptr;
    if((rSearchName.getLength() > maSearchName.getLength())
        && rSearchName.startsWith( maSearchName ) )
    {
        aTargetStyleName = rSearchName.copy(maSearchName.getLength() + 1);
        pTargetStyleName = &aTargetStyleName;
    }

    // TODO: linear search improve!
    PhysicalFontFace* pBestFontFace = maFontFaces[0].get();
    FontMatchStatus aFontMatchStatus = {0, pTargetStyleName};
    for (auto const& font : maFontFaces)
    {
        PhysicalFontFace* pFoundFontFace = font.get();
        if( pFoundFontFace->IsBetterMatch( rFSD, aFontMatchStatus ) )
            pBestFontFace = pFoundFontFace;
    }

    return pBestFontFace;
}

// update device font list with unique font faces, with uniqueness
// meaning different font attributes, but not different fonts sizes
void PhysicalFontFamily::UpdateDevFontList( vcl::font::PhysicalFontFaceCollection& rDevFontList ) const
{
    PhysicalFontFace* pPrevFace = nullptr;
    for (auto const& font : maFontFaces)
    {
        PhysicalFontFace* pFoundFontFace = font.get();
        if( !pPrevFace || pFoundFontFace->CompareIgnoreSize( *pPrevFace ) )
            rDevFontList.Add( pFoundFontFace );
        pPrevFace = pFoundFontFace;
    }
}

void PhysicalFontFamily::UpdateCloneFontList(vcl::font::PhysicalFontCollection& rFontCollection) const
{
    OUString aFamilyName = GetEnglishSearchFontName( GetFamilyName() );
    PhysicalFontFamily* pFamily(nullptr);

    for (auto const& font : maFontFaces)
    {
        PhysicalFontFace *pFoundFontFace = font.get();

        if (!pFamily)
        {   // tdf#98989 lazy create as family without faces won't work
            pFamily = rFontCollection.FindOrCreateFontFamily(aFamilyName);
        }
        assert(pFamily);
        pFamily->AddFontFace( pFoundFontFace );
    }
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
