/* -*- 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 <algorithm>
#include <math.h>
#include <string_view>

#include <o3tl/unit_conversion.hxx>
#include <sal/mathconf.h>
#include <sal/macros.h>
#include <sal/log.hxx>
#include <tools/solar.h>
#include <o3tl/string_view.hxx>
#include <unotools/fontdefs.hxx>
#include <filter/msfilter/msvbahelper.hxx>
#include <xestream.hxx>
#include <formula/errorcodes.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <xlstyle.hxx>
#include <xlname.hxx>
#include <xistream.hxx>
#include <xltools.hxx>

// GUID import/export

XclGuid::XclGuid()
    : mpnData{}
{
}

XclGuid::XclGuid(
        sal_uInt32 nData1, sal_uInt16 nData2, sal_uInt16 nData3,
        sal_uInt8 nData41, sal_uInt8 nData42, sal_uInt8 nData43, sal_uInt8 nData44,
        sal_uInt8 nData45, sal_uInt8 nData46, sal_uInt8 nData47, sal_uInt8 nData48 )
{
    // convert to little endian -> makes streaming easy
    UInt32ToSVBT32( nData1, mpnData );
    ShortToSVBT16( nData2, mpnData + 4 );
    ShortToSVBT16( nData3, mpnData + 6 );
    mpnData[  8 ] = nData41;
    mpnData[  9 ] = nData42;
    mpnData[ 10 ] = nData43;
    mpnData[ 11 ] = nData44;
    mpnData[ 12 ] = nData45;
    mpnData[ 13 ] = nData46;
    mpnData[ 14 ] = nData47;
    mpnData[ 15 ] = nData48;
}

bool operator==( const XclGuid& rCmp1, const XclGuid& rCmp2 )
{
    return ::std::equal( rCmp1.mpnData, std::end( rCmp1.mpnData ), rCmp2.mpnData );
}

XclImpStream& operator>>( XclImpStream& rStrm, XclGuid& rGuid )
{
    rStrm.Read( rGuid.mpnData, 16 );     // mpnData always in little endian
    return rStrm;
}

XclExpStream& operator<<( XclExpStream& rStrm, const XclGuid& rGuid )
{
    rStrm.Write( rGuid.mpnData, 16 );    // mpnData already in little endian
    return rStrm;
}

// Excel Tools

// GUID's
const XclGuid XclTools::maGuidStdLink(
    0x79EAC9D0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B );

const XclGuid XclTools::maGuidUrlMoniker(
    0x79EAC9E0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B );

const XclGuid XclTools::maGuidFileMoniker(
    0x00000303, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 );

// numeric conversion

double XclTools::GetDoubleFromRK( sal_Int32 nRKValue )
{
    sal_math_Double smD{};

    if( ::get_flag( nRKValue, EXC_RK_INTFLAG ) )
    {
        sal_Int32 nTemp = nRKValue >> 2;
        ::set_flag< sal_Int32 >( nTemp, 0xE0000000, nRKValue < 0 );
        smD.value = nTemp;
    }
    else
    {
        smD.w32_parts.msw = nRKValue & EXC_RK_VALUEMASK;
    }

    if( ::get_flag( nRKValue, EXC_RK_100FLAG ) )
        smD.value /= 100.0;

    return smD.value;
}

bool XclTools::GetRKFromDouble( sal_Int32& rnRKValue, double fValue )
{
    double fFrac, fInt;

    // integer
    fFrac = modf( fValue, &fInt );
    if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) ) // 2^29
    {
        rnRKValue
            = static_cast<sal_Int32>(
                static_cast<sal_uInt32>(static_cast<sal_Int32>(fInt)) << 2)
            | EXC_RK_INT;
        return true;
    }

    // integer/100
    fFrac = modf( fValue * 100.0, &fInt );
    if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) )
    {
        rnRKValue
            = static_cast<sal_Int32>(
                static_cast<sal_uInt32>(static_cast<sal_Int32>(fInt)) << 2)
            | EXC_RK_INT100;
        return true;
    }

    // double
    return false;
}

Degree100 XclTools::GetScRotation( sal_uInt16 nXclRot, Degree100 nRotForStacked )
{
    if( nXclRot == EXC_ROT_STACKED )
        return nRotForStacked;
    OSL_ENSURE( nXclRot <= 180, "XclTools::GetScRotation - illegal rotation angle" );
    return Degree100(static_cast< sal_Int32 >( (nXclRot <= 180) ? (100 * ((nXclRot > 90) ? (450 - nXclRot) : nXclRot)) : 0 ));
}

sal_uInt8 XclTools::GetXclRotation( Degree100 nScRot )
{
    sal_Int32 nXclRot = nScRot.get() / 100;
    if( (0 <= nXclRot) && (nXclRot <= 90) )
        return static_cast< sal_uInt8 >( nXclRot );
    if( nXclRot < 180 )
        return static_cast< sal_uInt8 >( 270 - nXclRot );
    if( nXclRot < 270 )
        return static_cast< sal_uInt8 >( nXclRot - 180 );
    if( nXclRot < 360 )
        return static_cast< sal_uInt8 >( 450 - nXclRot );
    return 0;
}

sal_uInt8 XclTools::GetXclRotFromOrient( sal_uInt8 nXclOrient )
{
    switch( nXclOrient )
    {
        case EXC_ORIENT_NONE:       return EXC_ROT_NONE;
        case EXC_ORIENT_STACKED:    return EXC_ROT_STACKED;
        case EXC_ORIENT_90CCW:      return EXC_ROT_90CCW;
        case EXC_ORIENT_90CW:       return EXC_ROT_90CW;
        default:    OSL_FAIL( "XclTools::GetXclRotFromOrient - unknown text orientation" );
    }
    return EXC_ROT_NONE;
}

sal_uInt8 XclTools::GetXclOrientFromRot( sal_uInt16 nXclRot )
{
    if( nXclRot == EXC_ROT_STACKED )
        return EXC_ORIENT_STACKED;
    OSL_ENSURE( nXclRot <= 180, "XclTools::GetXclOrientFromRot - unknown text rotation" );
    if( (45 < nXclRot) && (nXclRot <= 90) )
        return EXC_ORIENT_90CCW;
    if( (135 < nXclRot) && (nXclRot <= 180) )
        return EXC_ORIENT_90CW;
    return EXC_ORIENT_NONE;
}

sal_uInt8 XclTools::GetXclErrorCode( FormulaError nScError )
{
    switch( nScError )
    {
        case FormulaError::IllegalArgument:        return EXC_ERR_VALUE;
        case FormulaError::IllegalFPOperation:     return EXC_ERR_NUM;     // maybe DIV/0 or NUM...
        case FormulaError::DivisionByZero:         return EXC_ERR_DIV0;
        case FormulaError::IllegalParameter:       return EXC_ERR_VALUE;
        case FormulaError::PairExpected:           return EXC_ERR_VALUE;
        case FormulaError::OperatorExpected:       return EXC_ERR_VALUE;
        case FormulaError::VariableExpected:       return EXC_ERR_VALUE;
        case FormulaError::ParameterExpected:      return EXC_ERR_VALUE;
        case FormulaError::NoValue:                return EXC_ERR_VALUE;
        case FormulaError::CircularReference:      return EXC_ERR_VALUE;
        case FormulaError::NoCode:                 return EXC_ERR_NULL;
        case FormulaError::NoRef:                  return EXC_ERR_REF;
        case FormulaError::NoName:                 return EXC_ERR_NAME;
        case FormulaError::NoAddin:                return EXC_ERR_NAME;
        case FormulaError::NoMacro:                return EXC_ERR_NAME;
        case FormulaError::NotAvailable:           return EXC_ERR_NA;
        default: break;
    }
    return EXC_ERR_NA;
}

FormulaError XclTools::GetScErrorCode( sal_uInt8 nXclError )
{
    switch( nXclError )
    {
        case EXC_ERR_NULL:  return FormulaError::NoCode;
        case EXC_ERR_DIV0:  return FormulaError::DivisionByZero;
        case EXC_ERR_VALUE: return FormulaError::NoValue;
        case EXC_ERR_REF:   return FormulaError::NoRef;
        case EXC_ERR_NAME:  return FormulaError::NoName;
        case EXC_ERR_NUM:   return FormulaError::IllegalFPOperation;
        case EXC_ERR_NA:    return FormulaError::NotAvailable;
        default:            OSL_FAIL( "XclTools::GetScErrorCode - unknown error code" );
    }
    return FormulaError::NotAvailable;
}

double XclTools::ErrorToDouble( sal_uInt8 nXclError )
{
    return CreateDoubleError(GetScErrorCode( nXclError ));
}

XclBoolError XclTools::ErrorToEnum( double& rfDblValue, bool bErrOrBool, sal_uInt8 nValue )
{
    XclBoolError eType;
    if( bErrOrBool )
    {
        // error value
        switch( nValue )
        {
            case EXC_ERR_NULL:  eType = xlErrNull;      break;
            case EXC_ERR_DIV0:  eType = xlErrDiv0;      break;
            case EXC_ERR_VALUE: eType = xlErrValue;     break;
            case EXC_ERR_REF:   eType = xlErrRef;       break;
            case EXC_ERR_NAME:  eType = xlErrName;      break;
            case EXC_ERR_NUM:   eType = xlErrNum;       break;
            case EXC_ERR_NA:    eType = xlErrNA;        break;
            default:            eType = xlErrUnknown;
        }
        rfDblValue = 0.0;
    }
    else
    {
        // Boolean value
        eType = nValue ? xlErrTrue : xlErrFalse;
        rfDblValue = nValue ? 1.0 : 0.0;
    }
    return eType;
}

template <typename N> static N to(double f) { return limit_cast<N>(f + 0.5); }

sal_uInt16 XclTools::GetTwipsFromInch( double fInches )
{
    return to<sal_uInt16>(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::twip));
}

sal_uInt16 XclTools::GetTwipsFromHmm( sal_Int32 nHmm )
{
    return limit_cast<sal_uInt16>(o3tl::convert(nHmm, o3tl::Length::mm100, o3tl::Length::twip));
}

double XclTools::GetInchFromTwips( sal_Int32 nTwips )
{
    return o3tl::convert<double>(nTwips, o3tl::Length::twip, o3tl::Length::in);
}

double XclTools::GetInchFromHmm( sal_Int32 nHmm )
{
    return o3tl::convert<double>(nHmm, o3tl::Length::mm100, o3tl::Length::in);
}

sal_Int32 XclTools::GetHmmFromInch( double fInches )
{
    return to<sal_Int32>(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::mm100));
}

sal_Int32 XclTools::GetHmmFromTwips( sal_Int32 nTwips )
{
    return limit_cast<sal_Int32>(o3tl::convert(nTwips, o3tl::Length::twip, o3tl::Length::mm100));
}

sal_uInt16 XclTools::GetScColumnWidth( sal_uInt16 nXclWidth, tools::Long nScCharWidth )
{
    double fScWidth = static_cast< double >( nXclWidth ) / 256.0 * nScCharWidth - 0.5;
    return limit_cast< sal_uInt16 >( fScWidth );
}

sal_uInt16 XclTools::GetXclColumnWidth( sal_uInt16 nScWidth, tools::Long nScCharWidth )
{
    double fXclWidth = ( static_cast< double >( nScWidth ) + 0.5 ) * 256.0 / nScCharWidth;
    return limit_cast< sal_uInt16 >( fXclWidth );
}

// takes font height in twips (1/20 pt = 1/1440 in)
// returns correction value in 1/256th of *digit width* of default font
double XclTools::GetXclDefColWidthCorrection( tools::Long nXclDefFontHeight )
{
    // Excel uses *max digit width of default font* (W) as cell width unit. Also it has 5-pixel
    // "correction" to cell widths (ECMA-376-1:2016 18.3.1.81): each cell has 1-pixel padding, then
    // 3 pixels for the border (which may be 1-pixel - hairline - then it will have 2 additional
    // 1-pixel spacings from each side; or e.g. 2 hairlines with 1-pixel spacing in the middle; or
    // thick 3-pixel). Obviously, correction size entirely depends on pixel size (and it is actually
    // different in Excel on monitors with different resolution). Thus actual (displayed/printed)
    // cell widths consist of X*W+5px; stored in file is the X (or X*256 if 1/256th of digit width
    // units are used) value.
    // This formula apparently converts this 5-pixel correction to 1/256th of digit width units.
    // Looks like it is created from
    //
    //    5 * 256 * 1440 * 2.1333 / (96 * max(N-15, 60)) + 50.0
    //
    // where 5 - pixel correction; 256 - used to produce 1/256th of digit width; 1440 - used to
    // convert font height N (in twips) to inches; 2.1333 - an (empirical?) quotient to convert
    // font *height* into digit *width*; 96 - "standard" monitor resolution (DPI).
    // Additionally the formula uses 15 (of unknown origin), 60 (minimal font height 3 pt), and
    // 50.0 (also of unknown origin).
    //
    // TODO: convert this to take font digit width directly (and possibly DPI?), to avoid guessing
    // the digit width and pixel size. Or DPI might stay 96, to not follow Excel dependency on DPI
    // in addition to used font, and have absolute size of the correction fixed 5/96 in.
    return 40960.0 / ::std::max( nXclDefFontHeight - 15, tools::Long(60) ) + 50.0;
}

// formatting

Color XclTools::GetPatternColor( const Color& rPattColor, const Color& rBackColor, sal_uInt16 nXclPattern )
{
    // 0x00 == 0% transparence (full rPattColor)
    // 0x80 == 100% transparence (full rBackColor)
    static const sal_uInt8 pnRatioTable[] =
    {
        0x80, 0x00, 0x40, 0x20, 0x60, 0x40, 0x40, 0x40,     // 00 - 07
        0x40, 0x40, 0x20, 0x60, 0x60, 0x60, 0x60, 0x48,     // 08 - 15
        0x50, 0x70, 0x78                                    // 16 - 18
    };
    return (nXclPattern < std::size( pnRatioTable )) ?
        ScfTools::GetMixedColor( rPattColor, rBackColor, pnRatioTable[ nXclPattern ] ) : rPattColor;
}

// text encoding

namespace {

const struct XclCodePageEntry
{
    sal_uInt16                  mnCodePage;
    rtl_TextEncoding            meTextEnc;
}
pCodePageTable[] =
{
    {     437,  RTL_TEXTENCODING_IBM_437        },  // OEM US
//  {     720,  RTL_TEXTENCODING_IBM_720        },  // OEM Arabic
    {     737,  RTL_TEXTENCODING_IBM_737        },  // OEM Greek
    {     775,  RTL_TEXTENCODING_IBM_775        },  // OEM Baltic
    {     850,  RTL_TEXTENCODING_IBM_850        },  // OEM Latin I
    {     852,  RTL_TEXTENCODING_IBM_852        },  // OEM Latin II (Central European)
    {     855,  RTL_TEXTENCODING_IBM_855        },  // OEM Cyrillic
    {     857,  RTL_TEXTENCODING_IBM_857        },  // OEM Turkish
//  {     858,  RTL_TEXTENCODING_IBM_858        },  // OEM Multilingual Latin I with Euro
    {     860,  RTL_TEXTENCODING_IBM_860        },  // OEM Portuguese
    {     861,  RTL_TEXTENCODING_IBM_861        },  // OEM Icelandic
    {     862,  RTL_TEXTENCODING_IBM_862        },  // OEM Hebrew
    {     863,  RTL_TEXTENCODING_IBM_863        },  // OEM Canadian (French)
    {     864,  RTL_TEXTENCODING_IBM_864        },  // OEM Arabic
    {     865,  RTL_TEXTENCODING_IBM_865        },  // OEM Nordic
    {     866,  RTL_TEXTENCODING_IBM_866        },  // OEM Cyrillic (Russian)
    {     869,  RTL_TEXTENCODING_IBM_869        },  // OEM Greek (Modern)
    {     874,  RTL_TEXTENCODING_MS_874         },  // MS Windows Thai
    {     932,  RTL_TEXTENCODING_MS_932         },  // MS Windows Japanese Shift-JIS
    {     936,  RTL_TEXTENCODING_MS_936         },  // MS Windows Chinese Simplified GBK
    {     949,  RTL_TEXTENCODING_MS_949         },  // MS Windows Korean (Wansung)
    {     950,  RTL_TEXTENCODING_MS_950         },  // MS Windows Chinese Traditional BIG5
    {    1200,  RTL_TEXTENCODING_DONTKNOW       },  // Unicode (BIFF8) - return *_DONTKNOW to preserve old code page
    {    1250,  RTL_TEXTENCODING_MS_1250        },  // MS Windows Latin II (Central European)
    {    1251,  RTL_TEXTENCODING_MS_1251        },  // MS Windows Cyrillic
    {    1252,  RTL_TEXTENCODING_MS_1252        },  // MS Windows Latin I (BIFF4-BIFF8)
    {    1253,  RTL_TEXTENCODING_MS_1253        },  // MS Windows Greek
    {    1254,  RTL_TEXTENCODING_MS_1254        },  // MS Windows Turkish
    {    1255,  RTL_TEXTENCODING_MS_1255        },  // MS Windows Hebrew
    {    1256,  RTL_TEXTENCODING_MS_1256        },  // MS Windows Arabic
    {    1257,  RTL_TEXTENCODING_MS_1257        },  // MS Windows Baltic
    {    1258,  RTL_TEXTENCODING_MS_1258        },  // MS Windows Vietnamese
    {    1361,  RTL_TEXTENCODING_MS_1361        },  // MS Windows Korean (Johab)
    {   10000,  RTL_TEXTENCODING_APPLE_ROMAN    },  // Apple Roman
    {   32768,  RTL_TEXTENCODING_APPLE_ROMAN    },  // Apple Roman
    {   32769,  RTL_TEXTENCODING_MS_1252        }   // MS Windows Latin I (BIFF2-BIFF3)
};
const XclCodePageEntry* const pCodePageTableEnd = std::end(pCodePageTable);

struct XclCodePageEntry_CPPred
{
    explicit     XclCodePageEntry_CPPred( sal_uInt16 nCodePage ) : mnCodePage( nCodePage ) {}
    bool         operator()( const XclCodePageEntry& rEntry ) const { return rEntry.mnCodePage == mnCodePage; }
    sal_uInt16          mnCodePage;
};

struct XclCodePageEntry_TEPred
{
    explicit     XclCodePageEntry_TEPred( rtl_TextEncoding eTextEnc ) : meTextEnc( eTextEnc ) {}
    bool         operator()( const XclCodePageEntry& rEntry ) const { return rEntry.meTextEnc == meTextEnc; }
    rtl_TextEncoding    meTextEnc;
};

} // namespace

rtl_TextEncoding XclTools::GetTextEncoding( sal_uInt16 nCodePage )
{
    const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_CPPred( nCodePage ) );
    if( pEntry == pCodePageTableEnd )
    {
        SAL_WARN("sc",  "XclTools::GetTextEncoding - unknown code page: 0x" << std::hex << nCodePage );
        return RTL_TEXTENCODING_DONTKNOW;
    }
    return pEntry->meTextEnc;
}

sal_uInt16 XclTools::GetXclCodePage( rtl_TextEncoding eTextEnc )
{
    if( eTextEnc == RTL_TEXTENCODING_UNICODE )
        return 1200;    // for BIFF8

    const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_TEPred( eTextEnc ) );
    if( pEntry == pCodePageTableEnd )
    {
        SAL_WARN("sc",  "XclTools::GetXclCodePage - unsupported text encoding: 0x" << std::hex << eTextEnc );
        return 1252;
    }
    return pEntry->mnCodePage;
}

OUString XclTools::GetXclFontName( const OUString& rFontName )
{
    // substitute with MS fonts
    OUString aNewName = GetSubsFontName(rFontName, SubsFontFlags::ONLYONE | SubsFontFlags::MS);
    return aNewName.isEmpty() ? rFontName : aNewName;
}

// built-in defined names
const char maDefNamePrefix[]    = "Excel_BuiltIn_"; /// Prefix for built-in defined names.
const char maDefNamePrefixXml[] = "_xlnm.";         /// Prefix for built-in defined names for OOX

const char* const ppcDefNames[] =
{
    "Consolidate_Area",
    "Auto_Open",
    "Auto_Close",
    "Extract",
    "Database",
    "Criteria",
    "Print_Area",
    "Print_Titles",
    "Recorder",
    "Data_Form",
    "Auto_Activate",
    "Auto_Deactivate",
    "Sheet_Title",
    "_FilterDatabase"
};

OUString XclTools::GetXclBuiltInDefName( sal_Unicode cBuiltIn )
{
    OSL_ENSURE( std::size( ppcDefNames ) == EXC_BUILTIN_UNKNOWN,
        "XclTools::GetXclBuiltInDefName - built-in defined name list modified" );

    if( cBuiltIn < std::size( ppcDefNames ) )
        return OUString::createFromAscii(ppcDefNames[cBuiltIn]);
    else
        return OUString::number(cBuiltIn);
}

OUString XclTools::GetBuiltInDefName( sal_Unicode cBuiltIn )
{
    return maDefNamePrefix + GetXclBuiltInDefName(cBuiltIn);
}

OUString XclTools::GetBuiltInDefNameXml( sal_Unicode cBuiltIn )
{
    return maDefNamePrefixXml + GetXclBuiltInDefName(cBuiltIn);
}

sal_Unicode XclTools::GetBuiltInDefNameIndex( const OUString& rDefName )
{
    sal_Int32 nPrefixLen = 0;
    if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefix ) )
        nPrefixLen = strlen(maDefNamePrefix);
    else if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefixXml ) )
        nPrefixLen = strlen(maDefNamePrefixXml);
    if( nPrefixLen > 0 )
    {
        for( sal_Unicode cBuiltIn = 0; cBuiltIn < EXC_BUILTIN_UNKNOWN; ++cBuiltIn )
        {
            OUString aBuiltInName(GetXclBuiltInDefName(cBuiltIn));
            sal_Int32 nBuiltInLen = aBuiltInName.getLength();
            if( rDefName.matchIgnoreAsciiCase( aBuiltInName, nPrefixLen ) )
            {
                // name can be followed by underline or space character
                sal_Int32 nNextCharPos = nPrefixLen + nBuiltInLen;
                sal_Unicode cNextChar = (rDefName.getLength() > nNextCharPos) ? rDefName[nNextCharPos] : '\0';
                if( (cNextChar == '\0') || (cNextChar == ' ') || (cNextChar == '_') )
                    return cBuiltIn;
            }
        }
    }
    return EXC_BUILTIN_UNKNOWN;
}

// built-in style names

const char maStyleNamePrefix1[] = "Excel_BuiltIn_";  /// Prefix for built-in cell style names.
const char maStyleNamePrefix2[] = "Excel Built-in "; /// Prefix for built-in cell style names from OOX filter.

const char* const ppcStyleNames[] =
{
    "",                 // "Normal" not used directly, but localized "Default"
    "RowLevel_",        // outline level will be appended
    "ColumnLevel_",     // outline level will be appended
    "Comma",
    "Currency",
    "Percent",
    "Comma_0",
    "Currency_0",
    "Hyperlink",
    "Followed_Hyperlink"
};

OUString XclTools::GetBuiltInStyleName( sal_uInt8 nStyleId, std::u16string_view rName, sal_uInt8 nLevel )
{
    OUString aStyleName;

    if( nStyleId == EXC_STYLE_NORMAL )  // "Normal" becomes "Default" style
    {
        aStyleName = ScResId( STR_STYLENAME_STANDARD );
    }
    else
    {
        OUStringBuffer aBuf(maStyleNamePrefix1);
        if( nStyleId < std::size( ppcStyleNames ) )
            aBuf.appendAscii(ppcStyleNames[nStyleId]);
        else if (!rName.empty())
            aBuf.append(rName);
        else
            aBuf.append(static_cast<sal_Int32>(nStyleId));

        if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) )
            aBuf.append(static_cast<sal_Int32>(nLevel+1));

        aStyleName = aBuf.makeStringAndClear();
    }

    return aStyleName;
}

bool XclTools::IsBuiltInStyleName( const OUString& rStyleName, sal_uInt8* pnStyleId, sal_Int32* pnNextChar )
{
    // "Default" becomes "Normal"
    if (rStyleName == ScResId(STR_STYLENAME_STANDARD))
    {
        if( pnStyleId ) *pnStyleId = EXC_STYLE_NORMAL;
        if( pnNextChar ) *pnNextChar = rStyleName.getLength();
        return true;
    }

    // try the other built-in styles
    sal_uInt8 nFoundId = 0;
    sal_Int32 nNextChar = 0;

    sal_Int32 nPrefixLen = 0;
    if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix1 ) )
        nPrefixLen = strlen(maStyleNamePrefix1);
    else if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix2 ) )
        nPrefixLen = strlen(maStyleNamePrefix2);
    if( nPrefixLen > 0 )
    {
        for( sal_uInt8 nId = 0; nId < std::size( ppcStyleNames ); ++nId )
        {
            if( nId != EXC_STYLE_NORMAL )
            {
                OUString aShortName = OUString::createFromAscii(ppcStyleNames[nId]);
                if( rStyleName.matchIgnoreAsciiCase( aShortName, nPrefixLen ) &&
                    (nNextChar < nPrefixLen + aShortName.getLength()))
                {
                    nFoundId = nId;
                    nNextChar = nPrefixLen + aShortName.getLength();
                }
            }
        }
    }

    if( nNextChar > 0 )
    {
        if( pnStyleId ) *pnStyleId = nFoundId;
        if( pnNextChar ) *pnNextChar = nNextChar;
        return true;
    }

    if( pnStyleId ) *pnStyleId = EXC_STYLE_USERDEF;
    if( pnNextChar ) *pnNextChar = 0;
    return nPrefixLen > 0;  // also return true for unknown built-in styles
}

bool XclTools::GetBuiltInStyleId( sal_uInt8& rnStyleId, sal_uInt8& rnLevel, const OUString& rStyleName )
{
    sal_uInt8 nStyleId;
    sal_Int32 nNextChar;
    if( IsBuiltInStyleName( rStyleName, &nStyleId, &nNextChar ) && (nStyleId != EXC_STYLE_USERDEF) )
    {
        if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) )
        {
            std::u16string_view aLevel = rStyleName.subView(nNextChar);
            sal_Int32 nLevel = o3tl::toInt32(aLevel);
            if (std::u16string_view(OUString::number(nLevel)) == aLevel
                && nLevel > 0 && nLevel <= EXC_STYLE_LEVELCOUNT)
            {
                rnStyleId = nStyleId;
                rnLevel = static_cast< sal_uInt8 >( nLevel - 1 );
                return true;
            }
        }
        else if( rStyleName.getLength() == nNextChar )
        {
            rnStyleId = nStyleId;
            rnLevel = EXC_STYLE_NOLEVEL;
            return true;
        }
    }

    rnStyleId = EXC_STYLE_USERDEF;
    rnLevel = EXC_STYLE_NOLEVEL;
    return false;
}

// conditional formatting style names

const char maCFStyleNamePrefix1[] = "Excel_CondFormat_"; /// Prefix for cond. formatting style names.
const char maCFStyleNamePrefix2[] = "ConditionalStyle_"; /// Prefix for cond. formatting style names from OOX filter.
const char maCFStyleNamePrefix3[] = "ExtConditionalStyle_";

OUString XclTools::GetCondFormatStyleName( SCTAB nScTab, sal_Int32 nFormat, sal_uInt16 nCondition )
{
    return maCFStyleNamePrefix1 +
        OUString::number(static_cast<sal_Int32>(nScTab+1)) +
        "_" +
        OUString::number(static_cast<sal_Int32>(nFormat+1)) +
        "_" +
        OUString::number(static_cast<sal_Int32>(nCondition+1));
}

bool XclTools::IsCondFormatStyleName( const OUString& rStyleName )
{
    if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix1 ) )
        return true;

    if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix2 ) )
        return true;

    if (rStyleName.startsWithIgnoreAsciiCase(maCFStyleNamePrefix3))
        return true;

    return false;
}

// stream handling

void XclTools::SkipSubStream( XclImpStream& rStrm )
{
    bool bLoop = true;
    while( bLoop && rStrm.StartNextRecord() )
    {
        sal_uInt16 nRecId = rStrm.GetRecId();
        bLoop = nRecId != EXC_ID_EOF;
        if( (nRecId == EXC_ID2_BOF) || (nRecId == EXC_ID3_BOF) || (nRecId == EXC_ID4_BOF) || (nRecId == EXC_ID5_BOF) )
            SkipSubStream( rStrm );
    }
}

// Basic macro names

const char maSbMacroPrefix[] = "vnd.sun.star.script:";              /// Prefix for StarBasic macros.
const char maSbMacroSuffix[] = "?language=Basic&location=document"; /// Suffix for StarBasic macros.

OUString XclTools::GetSbMacroUrl( const OUString& rMacroName, SfxObjectShell* pDocShell )
{
    OSL_ENSURE( !rMacroName.isEmpty(), "XclTools::GetSbMacroUrl - macro name is empty" );
    ::ooo::vba::MacroResolvedInfo aMacroInfo = ::ooo::vba::resolveVBAMacro( pDocShell, rMacroName );
    if( aMacroInfo.mbFound )
        return ::ooo::vba::makeMacroURL( aMacroInfo.msResolvedMacro );
    return OUString();
}

OUString XclTools::GetXclMacroName( const OUString& rSbMacroUrl )
{
    sal_Int32 nSbMacroUrlLen = rSbMacroUrl.getLength();
    sal_Int32 nMacroNameLen = nSbMacroUrlLen - strlen(maSbMacroPrefix) - strlen(maSbMacroSuffix);
    if( (nMacroNameLen > 0) && rSbMacroUrl.startsWithIgnoreAsciiCase( maSbMacroPrefix ) &&
            rSbMacroUrl.endsWithIgnoreAsciiCase( maSbMacroSuffix ) )
    {
        sal_Int32 nPrjDot = rSbMacroUrl.indexOf( '.', strlen(maSbMacroPrefix) ) + 1;
        return rSbMacroUrl.copy( nPrjDot, nSbMacroUrlLen - nPrjDot - strlen(maSbMacroSuffix) );
    }
    return OUString();
}

// read/write colors

XclImpStream& operator>>( XclImpStream& rStrm, Color& rColor )
{
    sal_uInt8 nR = rStrm.ReaduInt8();
    sal_uInt8 nG = rStrm.ReaduInt8();
    sal_uInt8 nB = rStrm.ReaduInt8();
    rStrm.Ignore( 1 );//nD
    rColor = Color( nR, nG, nB );
    return rStrm;
}

XclExpStream& operator<<( XclExpStream& rStrm, const Color& rColor )
{
    return rStrm << rColor.GetRed() << rColor.GetGreen() << rColor.GetBlue() << sal_uInt8( 0 );
}

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