/* -*- 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 <config_features.h>

#include <tools/date.hxx>
#include <basic/sbxvar.hxx>
#include <basic/sbuno.hxx>
#include <osl/process.h>
#include <vcl/dibtools.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/sound.hxx>
#include <vcl/wintypes.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/weld.hxx>
#include <basic/sbx.hxx>
#include <svl/zforlist.hxx>
#include <rtl/character.hxx>
#include <rtl/math.hxx>
#include <tools/urlobj.hxx>
#include <osl/time.h>
#include <unotools/charclass.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/wincodepage.hxx>
#include <tools/wldcrd.hxx>
#include <i18nlangtag/lang.h>
#include <rtl/string.hxx>
#include <sal/log.hxx>
#include <comphelper/DirectoryHelper.hxx>
#include <comphelper/lok.hxx>

#include <runtime.hxx>
#include <sbunoobj.hxx>
#include <osl/file.hxx>
#include <errobject.hxx>

#include <comphelper/string.hxx>
#include <comphelper/processfactory.hxx>

#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <com/sun/star/script/XErrorQuery.hpp>
#include <ooo/vba/VbStrConv.hpp>
#include <ooo/vba/VbTriState.hpp>
#include <com/sun/star/bridge/oleautomation/XAutomationObject.hpp>
#include <memory>
#include <random>
#include <string_view>
#include <o3tl/char16_t2wchar_t.hxx>

// include search util
#include <com/sun/star/i18n/Transliteration.hpp>
#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <i18nutil/searchopt.hxx>
#include <unotools/textsearch.hxx>
#include <svl/numformat.hxx>

#include <date.hxx>
#include <sbstdobj.hxx>
#include <rtlproto.hxx>
#include <image.hxx>
#include <iosys.hxx>
#include "ddectrl.hxx"
#include <sbintern.hxx>
#include <basic/vbahelper.hxx>

#include <vector>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <sbobjmod.hxx>
#include <sbxmod.hxx>

#ifdef _WIN32
#include <prewin.h>
#include <direct.h>
#include <io.h>
#include <postwin.h>
#else
#include <unistd.h>
#endif

#include <vcl/TypeSerializer.hxx>

using namespace comphelper;
using namespace osl;
using namespace com::sun::star;
using namespace com::sun::star::lang;
using namespace com::sun::star::uno;

static sal_Int32 GetDayDiff(const Date& rDate) { return rDate - Date(1899'12'30); }

#if HAVE_FEATURE_SCRIPTING

static sal_Int32 nanoSecToMilliSec(sal_Int64 nNanoSeconds)
{
    // Rounding nanoseconds to milliseconds precision to avoid comparison inaccuracies
    return o3tl::convert(nNanoSeconds, 1, tools::Time::nanoPerMilli);
}

static void FilterWhiteSpace( OUString& rStr )
{
    if (rStr.isEmpty())
    {
        return;
    }
    OUStringBuffer aRet;

    for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
    {
        sal_Unicode cChar = rStr[i];
        if ((cChar != ' ') && (cChar != '\t') &&
           (cChar != '\n') && (cChar != '\r'))
        {
            aRet.append(cChar);
        }
    }

    rStr = aRet.makeStringAndClear();
}

static const CharClass& GetCharClass()
{
    static CharClass aCharClass( Application::GetSettings().GetLanguageTag() );
    return aCharClass;
}

static bool isFolder( FileStatus::Type aType )
{
    return ( aType == FileStatus::Directory || aType == FileStatus::Volume );
}


//*** UCB file access ***

// Converts possibly relative paths to absolute paths
// according to the setting done by ChDir/ChDrive
OUString getFullPath( const OUString& aRelPath )
{
    OUString aFileURL;

    // #80204 Try first if it already is a valid URL
    INetURLObject aURLObj( aRelPath );
    aFileURL = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );

    if( aFileURL.isEmpty() )
    {
        File::getFileURLFromSystemPath( aRelPath, aFileURL );
    }

    return aFileURL;
}

// TODO: -> SbiGlobals
static uno::Reference< ucb::XSimpleFileAccess3 > const & getFileAccess()
{
    static uno::Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() );
    return xSFI;
}


// Properties and methods lie down the return value at the Get (bPut = sal_False) in the
// element 0 of the Argv; the value of element 0 is saved at Put (bPut = sal_True)

// CreateObject( class )

void SbRtl_CreateObject(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    if( rPar.Count() < 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aClass(rPar.Get(1)->GetOUString());
    SbxObjectRef p = SbxBase::CreateObject( aClass );
    if( !p.is() )
        return StarBASIC::Error( ERRCODE_BASIC_CANNOT_LOAD );

    // Convenience: enter BASIC as parent
    p->SetParent( pBasic );
    rPar.Get(0)->PutObject(p.get());
}

// Error( n )

void SbRtl_Error(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    if( !pBasic )
        return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );

    OUString aErrorMsg;
    ErrCode nErr = ERRCODE_NONE;
    sal_Int32 nCode = 0;
    if (rPar.Count() == 1)
    {
        nErr = StarBASIC::GetErrBasic();
        aErrorMsg = StarBASIC::GetErrorMsg();
    }
    else
    {
        nCode = rPar.Get(1)->GetLong();
        if( nCode > 65535 )
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
        else
        {
            nErr = StarBASIC::GetSfxFromVBError( static_cast<sal_uInt16>(nCode) );
        }
    }
    bool bVBA = SbiRuntime::isVBAEnabled();
    OUString tmpErrMsg;
    if( bVBA && !aErrorMsg.isEmpty())
    {
        tmpErrMsg = aErrorMsg;
    }
    else
    {
        StarBASIC::MakeErrorText( nErr, aErrorMsg );
        tmpErrMsg = StarBASIC::GetErrorText();
    }
    // If this rtlfunc 'Error' passed an errcode the same as the active Err Objects's
    // current err then  return the description for the error message if it is set
    // ( complicated isn't it ? )
    if (bVBA && rPar.Count() > 1)
    {
        uno::Reference< ooo::vba::XErrObject > xErrObj( SbxErrObject::getUnoErrObject() );
        if ( xErrObj.is() && xErrObj->getNumber() == nCode && !xErrObj->getDescription().isEmpty() )
        {
            tmpErrMsg = xErrObj->getDescription();
        }
    }
    rPar.Get(0)->PutString(tmpErrMsg);
}

// Sinus

void SbRtl_Sin(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(sin(pArg->GetDouble()));
}


void SbRtl_Cos(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(cos(pArg->GetDouble()));
}


void SbRtl_Atn(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(atan(pArg->GetDouble()));
}


void SbRtl_Abs(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(fabs(pArg->GetDouble()));
}


void SbRtl_Asc(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    OUString aStr( pArg->GetOUString() );
    if ( aStr.isEmpty())
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        rPar.Get(0)->PutEmpty();
        return;
    }
    sal_Unicode aCh = aStr[0];
    rPar.Get(0)->PutLong(aCh);
}

static void implChr( SbxArray& rPar, bool bChrW )
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);

    OUString aStr;
    if( !bChrW && SbiRuntime::isVBAEnabled() )
    {
        char c = static_cast<char>(pArg->GetByte());
        aStr = OUString(&c, 1, osl_getThreadTextEncoding());
    }
    else
    {
        // Map negative 16-bit values to large positive ones, so that code like Chr(&H8000)
        // still works after the fix for tdf#62326 changed those four-digit hex notations to
        // produce negative values:
        sal_Int32 aCh = pArg->GetLong();
        if (aCh < -0x8000 || aCh > 0xFFFF)
        {
            StarBASIC::Error(ERRCODE_BASIC_MATH_OVERFLOW);
            aCh = 0;
        }
        aStr = OUString(static_cast<sal_Unicode>(aCh));
    }
    rPar.Get(0)->PutString(aStr);
}

void SbRtl_Chr(StarBASIC *, SbxArray & rPar, bool)
{
    implChr( rPar, false/*bChrW*/ );
}

void SbRtl_ChrW(StarBASIC *, SbxArray & rPar, bool)
{
    implChr( rPar, true/*bChrW*/ );
}

#if defined _WIN32

namespace {

extern "C" void invalidParameterHandler(
    wchar_t const * expression, wchar_t const * function, wchar_t const * file, unsigned int line,
    uintptr_t)
{
    SAL_INFO(
        "basic",
        "invalid parameter during _wgetdcwd; \""
            << (expression ? OUString(o3tl::toU(expression)) : OUString("???"))
            << "\" (" << (function ? OUString(o3tl::toU(function)) : OUString("???")) << ") at "
            << (file ? OUString(o3tl::toU(file)) : OUString("???")) << ":" << line);
}

}

#endif

void SbRtl_CurDir(StarBASIC *, SbxArray & rPar, bool)
{
    // #57064 Although this function doesn't work with DirEntry, it isn't touched
    // by the adjustment to virtual URLs, as, using the DirEntry-functionality,
    // there's no possibility to detect the current one in a way that a virtual URL
    // could be delivered.

    if (rPar.Count() > 2)
       return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

#if defined(_WIN32)
    int nCurDir = 0;  // Current dir // JSM
    if (rPar.Count() == 2)
    {
        OUString aDrive = rPar.Get(1)->GetOUString();
        if ( aDrive.getLength() != 1 )
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

        auto c = rtl::toAsciiUpperCase(aDrive[0]);
        if ( !rtl::isAsciiUpperCase( c ) )
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

        nCurDir = c - 'A' + 1;
    }
    wchar_t pBuffer[ _MAX_PATH ];
    // _wgetdcwd calls the C runtime's invalid parameter handler (which by default terminates the
    // process) if nCurDir does not correspond to an existing drive, so temporarily set a "harmless"
    // handler:
    auto const handler = _set_thread_local_invalid_parameter_handler(&invalidParameterHandler);
    auto const ok = _wgetdcwd( nCurDir, pBuffer, _MAX_PATH ) != nullptr;
    _set_thread_local_invalid_parameter_handler(handler);
    if ( !ok )
        return StarBASIC::Error( ERRCODE_BASIC_NO_DEVICE );

    rPar.Get(0)->PutString(OUString(o3tl::toU(pBuffer)));

#else

    const int PATH_INCR = 250;

    int nSize = PATH_INCR;
    std::unique_ptr<char[]> pMem;
    while( true )
    {
        pMem.reset(new char[nSize]);
        if( !pMem )
            return StarBASIC::Error( ERRCODE_BASIC_NO_MEMORY );

        if( getcwd( pMem.get(), nSize-1 ) != nullptr )
        {
            rPar.Get(0)->PutString(OUString::createFromAscii(pMem.get()));
            return;
        }
        if( errno != ERANGE )
            return StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );

        nSize += PATH_INCR;
    };

#endif
}

void SbRtl_ChDir(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    // VBA: track current directory per document type (separately for Writer, Calc, Impress, etc.)
    if( SbiRuntime::isVBAEnabled() )
    {
        ::basic::vba::registerCurrentDirectory(getDocumentModel(pBasic),
                                                rPar.Get(1)->GetOUString());
    }
}

void SbRtl_ChDrive(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
}


// Implementation of StepRENAME with UCB
void implStepRenameUCB( const OUString& aSource, const OUString& aDest )
{
    const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
    if( !xSFI.is() )
        return;

    try
    {
        OUString aSourceFullPath = getFullPath( aSource );
        if( !xSFI->exists( aSourceFullPath ) )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
            return;
        }

        OUString aDestFullPath = getFullPath( aDest );
        if( xSFI->exists( aDestFullPath ) )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_EXISTS );
        }
        else
        {
            xSFI->move( aSourceFullPath, aDestFullPath );
        }
    }
    catch(const Exception & )
    {
        StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
    }
}

// Implementation of StepRENAME with OSL
void implStepRenameOSL( const OUString& aSource, const OUString& aDest )
{
    FileBase::RC nRet = File::move( getFullPath( aSource ), getFullPath( aDest ) );
    if( nRet != FileBase::E_None )
    {
        StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }
}

void SbRtl_FileCopy(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aSource = rPar.Get(1)->GetOUString();
    OUString aDest = rPar.Get(2)->GetOUString();
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                xSFI->copy( getFullPath( aSource ), getFullPath( aDest ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
            }
        }
    }
    else
    {
        FileBase::RC nRet = File::copy( getFullPath( aSource ), getFullPath( aDest ) );
        if( nRet != FileBase::E_None )
        {
            StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
        }
    }
}

void SbRtl_Kill(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aFileSpec = rPar.Get(1)->GetOUString();

    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            OUString aFullPath = getFullPath( aFileSpec );
            if( !xSFI->exists( aFullPath ) || xSFI->isFolder( aFullPath ) )
            {
                StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
                return;
            }
            try
            {
                xSFI->kill( aFullPath );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        File::remove( getFullPath( aFileSpec ) );
    }
}

void SbRtl_MkDir(StarBASIC * pBasic, SbxArray & rPar, bool bWrite)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aPath = rPar.Get(1)->GetOUString();
    if ( SbiRuntime::isVBAEnabled() )
    {
        // In vba if the full path is not specified then
        // folder is created relative to the curdir
        INetURLObject aURLObj( getFullPath( aPath ) );
        if ( aURLObj.GetProtocol() != INetProtocol::File )
        {
            SbxArrayRef pPar = new SbxArray();
            SbxVariableRef pResult = new SbxVariable();
            SbxVariableRef pParam = new SbxVariable();
            pPar->Insert(pResult.get(), pPar->Count());
            pPar->Insert(pParam.get(), pPar->Count());
            SbRtl_CurDir( pBasic, *pPar, bWrite );

            OUString sCurPathURL;
            File::getFileURLFromSystemPath(pPar->Get(0)->GetOUString(), sCurPathURL);

            aURLObj.SetURL( sCurPathURL );
            aURLObj.Append( aPath );
            File::getSystemPathFromFileURL(aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri  ),aPath ) ;
        }
    }

    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                xSFI->createFolder( getFullPath( aPath ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        Directory::create( getFullPath( aPath ) );
    }
}


static void implRemoveDirRecursive( const OUString& aDirPath )
{
    DirectoryItem aItem;
    FileBase::RC nRet = DirectoryItem::get( aDirPath, aItem );
    bool bExists = (nRet == FileBase::E_None);

    FileStatus aFileStatus( osl_FileStatus_Mask_Type );
    nRet = aItem.getFileStatus( aFileStatus );
    bool bFolder = nRet == FileBase::E_None
        && isFolder( aFileStatus.getFileType() );

    if( !bExists || !bFolder )
    {
        return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }

    Directory aDir( aDirPath );
    nRet = aDir.open();
    if( nRet != FileBase::E_None )
    {
        return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
    }
    aDir.close();

    comphelper::DirectoryHelper::deleteDirRecursively(aDirPath);
}


void SbRtl_RmDir(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aPath = rPar.Get(1)->GetOUString();
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                if( !xSFI->isFolder( aPath ) )
                {
                    return StarBASIC::Error( ERRCODE_BASIC_PATH_NOT_FOUND );
                }
                SbiInstance* pInst = GetSbData()->pInst;
                bool bCompatibility = ( pInst && pInst->IsCompatibility() );
                if( bCompatibility )
                {
                    Sequence< OUString > aContent = xSFI->getFolderContents( aPath, true );
                    if( aContent.hasElements() )
                    {
                        return StarBASIC::Error( ERRCODE_BASIC_ACCESS_ERROR );
                    }
                }

                xSFI->kill( getFullPath( aPath ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        implRemoveDirRecursive( getFullPath( aPath ) );
    }
}

void SbRtl_SendKeys(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);
}

void SbRtl_Exp(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    double aDouble = rPar.Get(1)->GetDouble();
    aDouble = exp( aDouble );
    checkArithmeticOverflow( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}

void SbRtl_FileLen(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    SbxVariableRef pArg = rPar.Get(1);
    OUString aStr( pArg->GetOUString() );
    sal_Int32 nLen = 0;
    if( hasUno() )
    {
        const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
        if( xSFI.is() )
        {
            try
            {
                nLen = xSFI->getSize( getFullPath( aStr ) );
            }
            catch(const Exception & )
            {
                StarBASIC::Error( ERRCODE_IO_GENERAL );
            }
        }
    }
    else
    {
        DirectoryItem aItem;
        (void)DirectoryItem::get( getFullPath( aStr ), aItem );
        FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
        (void)aItem.getFileStatus( aFileStatus );
        nLen = static_cast<sal_Int32>(aFileStatus.getFileSize());
    }
    rPar.Get(0)->PutLong(nLen);
}



void SbRtl_Hex(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    SbxVariableRef pArg = rPar.Get(1);
    // converting value to unsigned and limit to 2 or 4 byte representation
    sal_uInt32 nVal = pArg->IsInteger() ?
        static_cast<sal_uInt16>(pArg->GetInteger()) :
        static_cast<sal_uInt32>(pArg->GetLong());
    rPar.Get(0)->PutString(OUString::number(nVal, 16).toAsciiUpperCase());
}

void SbRtl_FuncCaller(StarBASIC *, SbxArray & rPar, bool)
{
    if (!SbiRuntime::isVBAEnabled() || GetSbData()->pInst == nullptr || GetSbData()->pInst->pRun == nullptr)
        return StarBASIC::Error(ERRCODE_BASIC_NOT_IMPLEMENTED);

    if ( GetSbData()->pInst->pRun->GetExternalCaller() )
        *rPar.Get(0) = *GetSbData()->pInst->pRun->GetExternalCaller();
    else
    {
        SbxVariableRef pVar = new SbxVariable(SbxVARIANT);
        *rPar.Get(0) = *pVar;
    }
}
// InStr( [start],string,string,[compare] )

void SbRtl_InStr(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);

    sal_Int32 nStartPos = 1;
    sal_Int32 nFirstStringPos = 1;

    if ( nArgCount >= 3 )
    {
        nStartPos = rPar.Get(1)->GetLong();
        if( nStartPos <= 0 )
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
            nStartPos = 1;
        }
        nFirstStringPos++;
    }

    SbiInstance* pInst = GetSbData()->pInst;
    bool bTextMode;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    if( bCompatibility )
    {
        SbiRuntime* pRT = pInst->pRun;
        bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
    }
    else
    {
        bTextMode = true;
    }
    if ( nArgCount == 4 )
    {
        bTextMode = rPar.Get(4)->GetInteger();
    }
    sal_Int32 nPos;
    const OUString aToken = rPar.Get(nFirstStringPos + 1)->GetOUString();

    // #97545 Always find empty string
    if( aToken.isEmpty() )
    {
        nPos = nStartPos;
    }
    else
    {
        const OUString aStr1 = rPar.Get(nFirstStringPos)->GetOUString();
        const sal_Int32 nrStr1Len = aStr1.getLength();
        if (nStartPos > nrStr1Len)
        {
            // Start position is greater than the string being searched
            nPos = 0;
        }
        else
        {
            if( !bTextMode )
            {
                nPos = aStr1.indexOf( aToken, nStartPos - 1 ) + 1;
            }
            else
            {
                // tdf#139840 - case-insensitive operation for non-ASCII characters
                i18nutil::SearchOptions2 aSearchOptions;
                aSearchOptions.searchString = aToken;
                aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
                aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
                utl::TextSearch textSearch(aSearchOptions);

                sal_Int32 nStart = nStartPos - 1;
                sal_Int32 nEnd = nrStr1Len;
                nPos = textSearch.SearchForward(aStr1, &nStart, &nEnd) ? nStart + 1 : 0;
            }
        }
    }
    rPar.Get(0)->PutLong(nPos);
}


// InstrRev(string1, string2[, start[, compare]])

void SbRtl_InStrRev(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    const OUString aStr1 = rPar.Get(1)->GetOUString();
    const OUString aToken = rPar.Get(2)->GetOUString();

    sal_Int32 nStartPos = -1;
    if ( nArgCount >= 3 )
    {
        nStartPos = rPar.Get(3)->GetLong();
        if( nStartPos <= 0 && nStartPos != -1 )
        {
            StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
            nStartPos = -1;
        }
    }

    SbiInstance* pInst = GetSbData()->pInst;
    bool bTextMode;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    if( bCompatibility )
    {
        SbiRuntime* pRT = pInst->pRun;
        bTextMode = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
    }
    else
    {
        bTextMode = true;
    }
    if ( nArgCount == 4 )
    {
        bTextMode = rPar.Get(4)->GetInteger();
    }
    const sal_Int32 nStrLen = aStr1.getLength();
    if( nStartPos == -1 )
    {
        nStartPos = nStrLen;
    }

    sal_Int32 nPos = 0;
    if( nStartPos <= nStrLen )
    {
        sal_Int32 nTokenLen = aToken.getLength();
        if( !nTokenLen )
        {
           // Always find empty string
           nPos = nStartPos;
        }
        else if( nStrLen > 0 )
        {
            if( !bTextMode )
            {
               nPos = aStr1.lastIndexOf( aToken, nStartPos ) + 1;
            }
            else
            {
               // tdf#143332 - case-insensitive operation for non-ASCII characters
               i18nutil::SearchOptions2 aSearchOptions;
               aSearchOptions.searchString = aToken;
               aSearchOptions.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
               aSearchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
               utl::TextSearch textSearch(aSearchOptions);

               sal_Int32 nStart = 0;
               sal_Int32 nEnd = nStartPos;
               nPos = textSearch.SearchBackward(aStr1, &nEnd, &nStart) ? nStart : 0;
            }
        }
    }
    rPar.Get(0)->PutLong(nPos);
}


/*
    Int( 2.8 )  =  2.0
    Int( -2.8 ) = -3.0
    Fix( 2.8 )  =  2.0
    Fix( -2.8 ) = -2.0    <- !!
*/

void SbRtl_Int(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    double aDouble= pArg->GetDouble();
    /*
        floor( 2.8 ) =  2.0
        floor( -2.8 ) = -3.0
    */
    aDouble = floor( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}


void SbRtl_Fix(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    double aDouble = pArg->GetDouble();
    if ( aDouble >= 0.0 )
        aDouble = floor( aDouble );
    else
        aDouble = ceil( aDouble );
    rPar.Get(0)->PutDouble(aDouble);
}


void SbRtl_LCase(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    const CharClass& rCharClass = GetCharClass();
    OUString aStr(rPar.Get(1)->GetOUString());
    aStr = rCharClass.lowercase(aStr);
    rPar.Get(0)->PutString(aStr);
}

void SbRtl_Left(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aStr(rPar.Get(1)->GetOUString());
    sal_Int32 nResultLen = rPar.Get(2)->GetLong();
    if( nResultLen < 0 )
    {
        nResultLen = 0;
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else if(nResultLen > aStr.getLength())
    {
        nResultLen = aStr.getLength();
    }
    aStr = aStr.copy(0, nResultLen );
    rPar.Get(0)->PutString(aStr);
}

void SbRtl_Log(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    double aArg = rPar.Get(1)->GetDouble();
    if ( aArg <= 0 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    double d = log( aArg );
    checkArithmeticOverflow( d );
    rPar.Get(0)->PutDouble(d);
}

void SbRtl_LTrim(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aStr(comphelper::string::stripStart(rPar.Get(1)->GetOUString(), ' '));
    rPar.Get(0)->PutString(aStr);
}


// Mid( String, nStart, nLength )

void SbRtl_Mid(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    int nArgCount = rPar.Count() - 1;
    if ( nArgCount < 2 )
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }
    // #23178: replicate the functionality of Mid$ as a command
    // by adding a replacement-string as a fourth parameter.
    // In contrast to the original the third parameter (nLength)
    // can't be left out here. That's considered in bWrite already.
    if( nArgCount == 4 )
    {
        bWrite = true;
    }
    OUString aArgStr = rPar.Get(1)->GetOUString();
    sal_Int32 nStartPos = rPar.Get(2)->GetLong();
    if ( nStartPos < 1 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    nStartPos--;
    sal_Int32 nLen = -1;
    bool bWriteNoLenParam = false;
    if ( nArgCount == 3 || bWrite )
    {
        sal_Int32 n = rPar.Get(3)->GetLong();
        if( bWrite && n == -1 )
        {
            bWriteNoLenParam = true;
        }
        nLen = n;
    }
    if ( bWrite )
    {
        sal_Int32 nArgLen = aArgStr.getLength();
        if( nStartPos > nArgLen )
        {
            SbiInstance* pInst = GetSbData()->pInst;
            bool bCompatibility = ( pInst && pInst->IsCompatibility() );
            if( bCompatibility )
            {
                return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
            }
            nStartPos = nArgLen;
        }

        OUString aReplaceStr = rPar.Get(4)->GetOUString();
        sal_Int32 nReplaceStrLen = aReplaceStr.getLength();
        sal_Int32 nReplaceLen;
        if( bWriteNoLenParam )
        {
            nReplaceLen = nArgLen - nStartPos;
        }
        else
        {
            nReplaceLen = nLen;
            if( nReplaceLen < 0 || nReplaceLen > nArgLen - nStartPos )
            {
                nReplaceLen = nArgLen - nStartPos;
            }
        }

        OUStringBuffer aResultStr(aArgStr);
        sal_Int32 nErase = nReplaceLen;
        aResultStr.remove( nStartPos, nErase );
        aResultStr.insert(
            nStartPos, aReplaceStr.getStr(), std::min(nReplaceLen, nReplaceStrLen));

        rPar.Get(1)->PutString(aResultStr.makeStringAndClear());
    }
    else
    {
        OUString aResultStr;
        if (nStartPos > aArgStr.getLength())
        {
            // do nothing
        }
        else if(nArgCount == 2)
        {
            aResultStr = aArgStr.copy( nStartPos);
        }
        else
        {
            if (nLen < 0)
                nLen = 0;
            if(nStartPos + nLen > aArgStr.getLength())
            {
                nLen = aArgStr.getLength() - nStartPos;
            }
            if (nLen > 0)
                aResultStr = aArgStr.copy( nStartPos, nLen );
        }
        rPar.Get(0)->PutString(aResultStr);
    }
}

void SbRtl_Oct(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariableRef pArg = rPar.Get(1);
    // converting value to unsigned and limit to 2 or 4 byte representation
    sal_uInt32 nVal = pArg->IsInteger() ?
        static_cast<sal_uInt16>(pArg->GetInteger()) :
        static_cast<sal_uInt32>(pArg->GetLong());
    rPar.Get(0)->PutString(OUString::number(nVal, 8));
}

// Replace(expression, find, replace[, start[, count[, compare]]])

void SbRtl_Replace(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if ( nArgCount < 3 || nArgCount > 6 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    sal_Int32 lStartPos = 1;
    if (nArgCount >= 4)
    {
        if (rPar.Get(4)->GetType() != SbxEMPTY)
        {
            lStartPos = rPar.Get(4)->GetLong();
        }
        if (lStartPos < 1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }
    --lStartPos; // Make it 0-based

    sal_Int32 lCount = -1;
    if (nArgCount >= 5)
    {
        if (rPar.Get(5)->GetType() != SbxEMPTY)
        {
            lCount = rPar.Get(5)->GetLong();
        }
        if (lCount < -1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }

    bool bCaseInsensitive;
    if (nArgCount == 6)
    {
        bCaseInsensitive = rPar.Get(6)->GetInteger();
    }
    else
    {
        SbiInstance* pInst = GetSbData()->pInst;
        if (pInst && pInst->IsCompatibility())
        {
            SbiRuntime* pRT = pInst->pRun;
            bCaseInsensitive = pRT && pRT->IsImageFlag(SbiImageFlags::COMPARETEXT);
        }
        else
        {
            bCaseInsensitive = true;
        }
    }

    const OUString aExpStr = rPar.Get(1)->GetOUString();
    OUString aFindStr = rPar.Get(2)->GetOUString();
    const OUString aReplaceStr = rPar.Get(3)->GetOUString();

    OUString aSrcStr(aExpStr);
    sal_Int32 nPrevPos = std::min(lStartPos, aSrcStr.getLength());
    css::uno::Sequence<sal_Int32> aOffset;
    if (bCaseInsensitive)
    {
        // tdf#132389: case-insensitive operation for non-ASCII characters
        // tdf#142487: use css::i18n::Transliteration to correctly handle ß -> ss expansion
        // tdf#132388: We can't use utl::TextSearch (css::i18n::XTextSearch), because each call to
        //             css::i18n::XTextSearch::SearchForward transliterates input string, making
        //             performance of repeated calls unacceptable
        auto xTrans = css::i18n::Transliteration::create(comphelper::getProcessComponentContext());
        xTrans->loadModule(css::i18n::TransliterationModules_IGNORE_CASE, {});
        aFindStr = xTrans->transliterate(aFindStr, 0, aFindStr.getLength(), aOffset);
        aSrcStr = xTrans->transliterate(aSrcStr, nPrevPos, aSrcStr.getLength() - nPrevPos, aOffset);
        nPrevPos = std::distance(aOffset.begin(),
                                 std::lower_bound(aOffset.begin(), aOffset.end(), nPrevPos));
    }

    auto getExpStrPos = [aOffset, nExpLen = aExpStr.getLength()](sal_Int32 nSrcStrPos) -> sal_Int32
    {
        assert(!aOffset.hasElements() || aOffset.getLength() >= nSrcStrPos);
        if (!aOffset.hasElements())
            return nSrcStrPos;
        return aOffset.getLength() > nSrcStrPos ? aOffset[nSrcStrPos] : nExpLen;
    };

    // Note: the result starts from lStartPos, removing everything to the left. See i#94895.
    OUStringBuffer sResult(aSrcStr.getLength() - nPrevPos);
    sal_Int32 nCounts = 0;
    while (lCount == -1 || lCount > nCounts)
    {
        sal_Int32 nPos = aSrcStr.indexOf(aFindStr, nPrevPos);
        if (nPos < 0)
            break;

        lStartPos = getExpStrPos(nPrevPos);
        sResult.append(aExpStr.getStr() + lStartPos, getExpStrPos(nPos) - lStartPos);
        sResult.append(aReplaceStr);
        nPrevPos = nPos + aFindStr.getLength();
        nCounts++;
    }
    lStartPos = getExpStrPos(nPrevPos);
    sResult.append(aExpStr.getStr() + lStartPos, aExpStr.getLength() - lStartPos);
    rPar.Get(0)->PutString(sResult.makeStringAndClear());
}

void SbRtl_Right(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    const OUString aStr = rPar.Get(1)->GetOUString();
    int nResultLen = rPar.Get(2)->GetLong();
    if( nResultLen < 0 )
    {
        nResultLen = 0;
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    int nStrLen = aStr.getLength();
    if ( nResultLen > nStrLen )
    {
        nResultLen = nStrLen;
    }
    OUString aResultStr = aStr.copy( nStrLen - nResultLen );
    rPar.Get(0)->PutString(aResultStr);
}

void SbRtl_RTL(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutObject(pBasic->getRTL().get());
}

void SbRtl_RTrim(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    OUString aStr(comphelper::string::stripEnd(rPar.Get(1)->GetOUString(), ' '));
    rPar.Get(0)->PutString(aStr);
}

void SbRtl_Sgn(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    double aDouble = rPar.Get(1)->GetDouble();
    sal_Int16 nResult = 0;
    if ( aDouble > 0 )
    {
        nResult = 1;
    }
    else if ( aDouble < 0 )
    {
        nResult = -1;
    }
    rPar.Get(0)->PutInteger(nResult);
}

void SbRtl_Space(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    const sal_Int32 nCount = rPar.Get(1)->GetLong();
    OUStringBuffer aBuf(nCount);
    string::padToLength(aBuf, nCount, ' ');
    rPar.Get(0)->PutString(aBuf.makeStringAndClear());
}

void SbRtl_Sqr(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    double aDouble = rPar.Get(1)->GetDouble();
    if ( aDouble < 0 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    rPar.Get(0)->PutDouble(sqrt(aDouble));
}

void SbRtl_Str(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    OUString aStr;
    OUString aStrNew;
    SbxVariableRef pArg = rPar.Get(1);
    const SbxDataType argType = pArg->GetType();
    if (argType == SbxSTRING)
    {
        // From Help: "If a string is passed as argument, it is returned without any changes"
        aStr = pArg->GetOUString();
    }
    else
    {
        pArg->Format(aStr);
    }

    // Numbers start with a space
    if (argType != SbxBOOL && argType != SbxSTRING && pArg->IsNumericRTL())
    {
        // replace commas by points so that it's symmetric to Val!
        aStr = aStr.replaceFirst( ",", "." );

        SbiInstance* pInst = GetSbData()->pInst;
        bool bCompatibility = ( pInst && pInst->IsCompatibility() );
        if( bCompatibility )
        {
            sal_Int32 nLen = aStr.getLength();

            const sal_Unicode* pBuf = aStr.getStr();

            bool bNeg = ( pBuf[0] == '-' );
            sal_Int32 iZeroSearch = 0;
            if( bNeg )
            {
                aStrNew += "-";
                iZeroSearch++;
            }
            else
            {
                if( pBuf[0] != ' ' )
                {
                    aStrNew += " ";
                }
            }
            sal_Int32 iNext = iZeroSearch + 1;
            if( pBuf[iZeroSearch] == '0' && nLen > iNext && pBuf[iNext] == '.' )
            {
                iZeroSearch += 1;
            }
            aStrNew += aStr.subView(iZeroSearch);
        }
        else
        {
            if (!aStr.startsWith("-"))
                aStrNew = " ";
            aStrNew += aStr;
        }
    }
    else
    {
        aStrNew = aStr;
    }
    rPar.Get(0)->PutString(aStrNew);
}

void SbRtl_StrComp(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 3)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        rPar.Get(0)->PutEmpty();
        return;
    }
    const OUString aStr1 = rPar.Get(1)->GetOUString();
    const OUString aStr2 = rPar.Get(2)->GetOUString();

    SbiInstance* pInst = GetSbData()->pInst;
    bool bTextCompare;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    if( bCompatibility )
    {
        SbiRuntime* pRT = pInst->pRun;
        bTextCompare = pRT && pRT->IsImageFlag( SbiImageFlags::COMPARETEXT );
    }
    else
    {
        bTextCompare = true;
    }
    if (rPar.Count() == 4)
        bTextCompare = rPar.Get(3)->GetInteger();

    if( !bCompatibility )
    {
        bTextCompare = !bTextCompare;
    }
    sal_Int32 nRetValue = 0;
    if( bTextCompare )
    {
        ::utl::TransliterationWrapper* pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
        if( !pTransliterationWrapper )
        {
            const uno::Reference< uno::XComponentContext >& xContext = getProcessComponentContext();
            GetSbData()->pTransliterationWrapper.reset(
                new ::utl::TransliterationWrapper( xContext,
                    TransliterationFlags::IGNORE_CASE |
                    TransliterationFlags::IGNORE_KANA |
                    TransliterationFlags::IGNORE_WIDTH ) );
            pTransliterationWrapper = GetSbData()->pTransliterationWrapper.get();
        }

        LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
        pTransliterationWrapper->loadModuleIfNeeded( eLangType );
        nRetValue = pTransliterationWrapper->compareString( aStr1, aStr2 );
    }
    else
    {
        sal_Int32 aResult;
        aResult = aStr1.compareTo( aStr2 );
        if ( aResult < 0  )
        {
            nRetValue = -1;
        }
        else if ( aResult > 0)
        {
            nRetValue = 1;
        }
    }
    rPar.Get(0)->PutInteger(sal::static_int_cast<sal_Int16>(nRetValue));
}

void SbRtl_String(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    sal_Unicode aFiller;
    sal_Int32 lCount = rPar.Get(1)->GetLong();
    if( lCount < 0 || lCount > 0xffff )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    if (rPar.Get(2)->GetType() == SbxINTEGER)
    {
        aFiller = static_cast<sal_Unicode>(rPar.Get(2)->GetInteger());
    }
    else
    {
        const OUString aStr = rPar.Get(2)->GetOUString();
        aFiller = aStr[0];
    }
    OUStringBuffer aBuf(lCount);
    string::padToLength(aBuf, lCount, aFiller);
    rPar.Get(0)->PutString(aBuf.makeStringAndClear());
}

void SbRtl_Tab(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    const sal_Int32 nCount = std::max(rPar.Get(1)->GetLong(), sal_Int32(0));
    OUStringBuffer aStr(nCount);
    comphelper::string::padToLength(aStr, nCount, '\t');
    rPar.Get(0)->PutString(aStr.makeStringAndClear());
}

void SbRtl_Tan(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    SbxVariableRef pArg = rPar.Get(1);
    rPar.Get(0)->PutDouble(tan(pArg->GetDouble()));
}

void SbRtl_UCase(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    const CharClass& rCharClass = GetCharClass();
    OUString aStr(rPar.Get(1)->GetOUString());
    aStr = rCharClass.uppercase( aStr );
    rPar.Get(0)->PutString(aStr);
}


void SbRtl_Val(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }
    double nResult = 0.0;

    OUString aStr(rPar.Get(1)->GetOUString());

    FilterWhiteSpace( aStr );
    if ( aStr.getLength() > 1 && aStr[0] == '&' )
    {
        sal_Unicode aChar = aStr[1];
        if ( aChar == 'h' || aChar == 'H' )
        {
            nResult = static_cast<sal_Int16>(o3tl::toInt64(aStr.subView(2), 16));
        }
        else if ( aChar == 'o' || aChar == 'O' )
        {
            nResult = static_cast<sal_Int16>(o3tl::toInt64(aStr.subView(2), 8));
        }
    }
    else
    {
        rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok;
        sal_Int32 nParseEnd = 0;
        nResult = ::rtl::math::stringToDouble( aStr, '.', ',', &eStatus, &nParseEnd );
        if ( eStatus != rtl_math_ConversionStatus_Ok )
            StarBASIC::Error( ERRCODE_BASIC_MATH_OVERFLOW );
        /* TODO: we should check whether all characters were parsed here,
         * but earlier code silently ignored trailing nonsense such as "1x"
         * resulting in 1 with the side effect that any alpha-only-string
         * like "x" resulted in 0. Not changing that now (2013-03-22) as
         * user macros may rely on it. */
#if 0
        else if ( nParseEnd != aStr.getLength() )
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
#endif
    }

    rPar.Get(0)->PutDouble(nResult);
}


// Helper functions for date conversion
sal_Int16 implGetDateDay( double aDate )
{
    aDate = floor( aDate );
    Date aRefDate(1899'12'30);
    aRefDate.AddDays( aDate );

    sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetDay() );
    return nRet;
}

sal_Int16 implGetDateMonth( double aDate )
{
    Date aRefDate(1899'12'30);
    sal_Int32 nDays = static_cast<sal_Int32>(aDate);
    aRefDate.AddDays( nDays );
    sal_Int16 nRet = static_cast<sal_Int16>( aRefDate.GetMonth() );
    return nRet;
}

css::util::Date SbxDateToUNODate( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();

    css::util::Date aUnoDate;
    aUnoDate.Day   = implGetDateDay  ( aDate );
    aUnoDate.Month = implGetDateMonth( aDate );
    aUnoDate.Year  = implGetDateYear ( aDate );

    return aUnoDate;
}

void SbxDateFromUNODate( SbxValue *pVal, const css::util::Date& aUnoDate)
{
    double dDate;
    if( implDateSerial( aUnoDate.Year, aUnoDate.Month, aUnoDate.Day, false, SbDateCorrection::None, dDate ) )
    {
        pVal->PutDate( dDate );
    }
}

// Function to convert date to UNO date (com.sun.star.util.Date)
void SbRtl_CDateToUnoDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODate(rPar.Get(1))));
}

// Function to convert date from UNO date (com.sun.star.util.Date)
void SbRtl_CDateFromUnoDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Date>::get()));
    css::util::Date aUnoDate;
    if(aAny >>= aUnoDate)
        SbxDateFromUNODate(rPar.Get(0), aUnoDate);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}

css::util::Time SbxDateToUNOTime( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();

    css::util::Time aUnoTime;
    aUnoTime.Hours       = implGetHour      ( aDate );
    aUnoTime.Minutes     = implGetMinute    ( aDate );
    aUnoTime.Seconds     = implGetSecond    ( aDate );
    aUnoTime.NanoSeconds = implGetNanoSecond( aDate );

    return aUnoTime;
}

void SbxDateFromUNOTime( SbxValue *pVal, const css::util::Time& aUnoTime)
{
    pVal->PutDate(implTimeSerial(aUnoTime.Hours, aUnoTime.Minutes, aUnoTime.Seconds,
                                 nanoSecToMilliSec(aUnoTime.NanoSeconds)));
}

// Function to convert date to UNO time (com.sun.star.util.Time)
void SbRtl_CDateToUnoTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNOTime(rPar.Get(1))));
}

// Function to convert date from UNO time (com.sun.star.util.Time)
void SbRtl_CDateFromUnoTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::Time>::get()));
    css::util::Time aUnoTime;
    if(aAny >>= aUnoTime)
        SbxDateFromUNOTime(rPar.Get(0), aUnoTime);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}

css::util::DateTime SbxDateToUNODateTime( const SbxValue* const pVal )
{
    double aDate = pVal->GetDate();

    css::util::DateTime aUnoDT;
    aUnoDT.Day   = implGetDateDay  ( aDate );
    aUnoDT.Month = implGetDateMonth( aDate );
    aUnoDT.Year  = implGetDateYear ( aDate );
    aUnoDT.Hours       = implGetHour      ( aDate );
    aUnoDT.Minutes     = implGetMinute    ( aDate );
    aUnoDT.Seconds     = implGetSecond    ( aDate );
    aUnoDT.NanoSeconds = implGetNanoSecond( aDate );

    return aUnoDT;
}

void SbxDateFromUNODateTime( SbxValue *pVal, const css::util::DateTime& aUnoDT)
{
    double dDate(0.0);
    if (implDateTimeSerial(aUnoDT.Year, aUnoDT.Month, aUnoDT.Day, aUnoDT.Hours, aUnoDT.Minutes,
                           aUnoDT.Seconds, nanoSecToMilliSec(aUnoDT.NanoSeconds), dDate))
    {
        pVal->PutDate( dDate );
    }
}

// Function to convert date to UNO date (com.sun.star.util.Date)
void SbRtl_CDateToUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    unoToSbxValue(rPar.Get(0), Any(SbxDateToUNODateTime(rPar.Get(1))));
}

// Function to convert date from UNO date (com.sun.star.util.Date)
void SbRtl_CDateFromUnoDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2 || rPar.Get(1)->GetType() != SbxOBJECT)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    Any aAny(sbxToUnoValue(rPar.Get(1), cppu::UnoType<css::util::DateTime>::get()));
    css::util::DateTime aUnoDT;
    if(aAny >>= aUnoDT)
        SbxDateFromUNODateTime(rPar.Get(0), aUnoDT);
    else
        SbxBase::SetError( ERRCODE_BASIC_CONVERSION );
}

// Function to convert date to ISO 8601 date format YYYYMMDD
void SbRtl_CDateToIso(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);

    double aDate = rPar.Get(1)->GetDate();

    // Date may actually even be -YYYYYMMDD
    char Buffer[11];
    sal_Int16 nYear = implGetDateYear(aDate);
    snprintf(Buffer, sizeof(Buffer), (nYear < 0 ? "%05d%02d%02d" : "%04d%02d%02d"),
             static_cast<int>(nYear), static_cast<int>(implGetDateMonth(aDate)),
             static_cast<int>(implGetDateDay(aDate)));
    OUString aRetStr = OUString::createFromAscii(Buffer);
    rPar.Get(0)->PutString(aRetStr);
}

// Function to convert date from ISO 8601 date format YYYYMMDD or YYYY-MM-DD
// And even YYMMDD for compatibility, sigh...
void SbRtl_CDateFromIso(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        do
        {
            OUString aStr = rPar.Get(1)->GetOUString();
            if (aStr.isEmpty())
                break;

            // Valid formats are
            // YYYYMMDD    -YYYMMDD     YYYYYMMDD    -YYYYYMMDD    YYMMDD
            // YYYY-MM-DD  -YYYY-MM-DD  YYYYY-MM-DD  -YYYYY-MM-DD

            sal_Int32 nSign = 1;
            if (aStr[0] == '-')
            {
                nSign = -1;
                aStr = aStr.copy(1);
            }
            const sal_Int32 nLen = aStr.getLength();

            // Signed YYMMDD two digit year is invalid.
            if (nLen == 6 && nSign == -1)
                break;

            // Now valid
            // YYYYMMDD    YYYYYMMDD    YYMMDD
            // YYYY-MM-DD  YYYYY-MM-DD
            if (nLen != 6 && (nLen < 8 || 11 < nLen))
                break;

            bool bUseTwoDigitYear = false;
            std::u16string_view aYearStr, aMonthStr, aDayStr;
            if (nLen == 6 || nLen == 8 || nLen == 9)
            {
                // ((Y)YY)YYMMDD
                if (!comphelper::string::isdigitAsciiString(aStr))
                    break;

                const sal_Int32 nMonthPos = (nLen == 8 ? 4 : (nLen == 6 ? 2 : 5));
                if (nMonthPos == 2)
                    bUseTwoDigitYear = true;
                aYearStr  = aStr.subView( 0, nMonthPos );
                aMonthStr = aStr.subView( nMonthPos, 2 );
                aDayStr   = aStr.subView( nMonthPos + 2, 2 );
            }
            else
            {
                // (Y)YYYY-MM-DD
                const sal_Int32 nMonthSep = (nLen == 11 ? 5 : 4);
                if (aStr.indexOf('-') != nMonthSep)
                    break;
                if (aStr.indexOf('-', nMonthSep + 1) != nMonthSep + 3)
                    break;

                aYearStr  = aStr.subView( 0, nMonthSep );
                aMonthStr = aStr.subView( nMonthSep + 1, 2 );
                aDayStr   = aStr.subView( nMonthSep + 4, 2 );
                if (    !comphelper::string::isdigitAsciiString(aYearStr) ||
                        !comphelper::string::isdigitAsciiString(aMonthStr) ||
                        !comphelper::string::isdigitAsciiString(aDayStr))
                    break;
            }

            double dDate;
            if (!implDateSerial( static_cast<sal_Int16>(nSign * o3tl::toInt32(aYearStr)),
                        static_cast<sal_Int16>(o3tl::toInt32(aMonthStr)), static_cast<sal_Int16>(o3tl::toInt32(aDayStr)),
                        bUseTwoDigitYear, SbDateCorrection::None, dDate ))
                break;

            rPar.Get(0)->PutDate(dDate);

            return;
        }
        while (false);

        SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER );
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}

void SbRtl_DateSerial(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 4)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nYear = rPar.Get(1)->GetInteger();
    sal_Int16 nMonth = rPar.Get(2)->GetInteger();
    sal_Int16 nDay = rPar.Get(3)->GetInteger();

    double dDate;
    if( implDateSerial( nYear, nMonth, nDay, true, SbDateCorrection::RollOver, dDate ) )
    {
        rPar.Get(0)->PutDate(dDate);
    }
}

void SbRtl_TimeSerial(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 4)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nHour = rPar.Get(1)->GetInteger();
    if ( nHour == 24 )
    {
        nHour = 0;                      // because of UNO DateTimes, which go till 24 o'clock
    }
    sal_Int16 nMinute = rPar.Get(2)->GetInteger();
    sal_Int16 nSecond = rPar.Get(3)->GetInteger();
    if ((nHour < 0 || nHour > 23)   ||
        (nMinute < 0 || nMinute > 59 )  ||
        (nSecond < 0 || nSecond > 59 ))
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    rPar.Get(0)->PutDate(implTimeSerial(nHour, nMinute, nSecond, 0)); // JSM
}

void SbRtl_DateValue(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        // #39629 check GetSbData()->pInst, can be called from the URL line
        std::shared_ptr<SvNumberFormatter> pFormatter;
        if( GetSbData()->pInst )
        {
            pFormatter = GetSbData()->pInst->GetNumberFormatter();
        }
        else
        {
            sal_uInt32 n;   // Dummy
            pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
        }

        LanguageType eLangType = Application::GetSettings().GetLanguageTag().getLanguageType();
        sal_uInt32 nIndex = pFormatter->GetStandardIndex( eLangType);
        double fResult;
        OUString aStr(rPar.Get(1)->GetOUString());
        bool bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
        SvNumFormatType nType = pFormatter->GetType( nIndex );

        // DateValue("February 12, 1969") raises error if the system locale is not en_US
        // It seems that both locale number formatter and English number
        // formatter are supported in Visual Basic.
        if( !bSuccess && ( eLangType != LANGUAGE_ENGLISH_US ) )
        {
            // Try using LANGUAGE_ENGLISH_US to get the date value.
            nIndex = pFormatter->GetStandardIndex( LANGUAGE_ENGLISH_US);
            bSuccess = pFormatter->IsNumberFormat( aStr, nIndex, fResult );
            nType = pFormatter->GetType( nIndex );
        }

        if(bSuccess && (nType==SvNumFormatType::DATE || nType==SvNumFormatType::DATETIME))
        {
            if ( nType == SvNumFormatType::DATETIME )
            {
                // cut time
                if ( fResult  > 0.0 )
                {
                    fResult = floor( fResult );
                }
                else
                {
                    fResult = ceil( fResult );
                }
            }
            rPar.Get(0)->PutDate(fResult);
        }
        else
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
    }
}

void SbRtl_TimeValue(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() < 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        std::shared_ptr<SvNumberFormatter> pFormatter;
        if( GetSbData()->pInst )
            pFormatter = GetSbData()->pInst->GetNumberFormatter();
        else
        {
            sal_uInt32 n;
            pFormatter = SbiInstance::PrepareNumberFormatter( n, n, n );
        }

        sal_uInt32 nIndex = 0;
        double fResult;
        bool bSuccess = pFormatter->IsNumberFormat(rPar.Get(1)->GetOUString(),
                                                   nIndex, fResult );
        SvNumFormatType nType = pFormatter->GetType(nIndex);
        if(bSuccess && (nType==SvNumFormatType::TIME||nType==SvNumFormatType::DATETIME))
        {
            if ( nType == SvNumFormatType::DATETIME )
            {
                // cut days
                fResult = fmod( fResult, 1 );
            }
            rPar.Get(0)->PutDate(fResult);
        }
        else
        {
            StarBASIC::Error( ERRCODE_BASIC_CONVERSION );
        }
    }
}

void SbRtl_Day(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    SbxVariableRef pArg = rPar.Get(1);
    double aDate = pArg->GetDate();

    sal_Int16 nDay = implGetDateDay( aDate );
    rPar.Get(0)->PutInteger(nDay);
}

void SbRtl_Year(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    sal_Int16 nYear = implGetDateYear(rPar.Get(1)->GetDate());
    rPar.Get(0)->PutInteger(nYear);
}

sal_Int16 implGetHour( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerHour)
                                  % ::tools::Time::hourPerDay);
}

void SbRtl_Hour(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    double nArg = rPar.Get(1)->GetDate();
    sal_Int16 nHour = implGetHour( nArg );
    rPar.Get(0)->PutInteger(nHour);
}

void SbRtl_Minute(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    double nArg = rPar.Get(1)->GetDate();
    sal_Int16 nMin = implGetMinute( nArg );
    rPar.Get(0)->PutInteger(nMin);
}

void SbRtl_Month(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    sal_Int16 nMonth = implGetDateMonth(rPar.Get(1)->GetDate());
    rPar.Get(0)->PutInteger(nMonth);
}

sal_Int16 implGetSecond( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerSec)
                                  % ::tools::Time::secondPerMinute);
}

sal_Int32 implGetNanoSecond(double dDate)
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    nMilliSeconds %= ::tools::Time::milliSecPerSec;

    return static_cast<sal_Int32>(nMilliSeconds * ::tools::Time ::nanoPerMilli);
}

void SbRtl_Second(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    double nArg = rPar.Get(1)->GetDate();
    sal_Int16 nSecond = implGetSecond( nArg );
    rPar.Get(0)->PutInteger(nSecond);
}

double Now_Impl()
{
    // tdf#161469 - align implementation with the now function in calc, i.e., include subseconds
    DateTime aActTime(DateTime::SYSTEM);
    return static_cast<double>(GetDayDiff(aActTime))
           + implTimeSerial(aActTime.GetHour(), aActTime.GetMin(), aActTime.GetSec(),
                            nanoSecToMilliSec(aActTime.GetNanoSec()));
}

// Date Now()

void SbRtl_Now(StarBASIC*, SbxArray& rPar, bool)
{
    if (rPar.Count() != 1)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    rPar.Get(0)->PutDate(Now_Impl());
}

// Date Time()

void SbRtl_Time(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    if ( !bWrite )
    {
        tools::Time aTime( tools::Time::SYSTEM );
        SbxVariable* pMeth = rPar.Get(0);
        if (!pMeth->IsString())
        {
            pMeth->PutDate(aTime.GetTimeInDays());
            return;
        }
        OUString aRes;
        if( pMeth->IsFixed() )
        {
            // Time$: hh:mm:ss
            char buf[ 20 ];
            snprintf( buf, sizeof(buf), "%02d:%02d:%02d",
                      aTime.GetHour(), aTime.GetMin(), aTime.GetSec() );
            aRes = OUString::createFromAscii( buf );
        }
        else
        {
            // Time: system dependent
            tools::Long nSeconds=aTime.GetHour();
            nSeconds *= 3600;
            nSeconds += aTime.GetMin() * 60;
            nSeconds += aTime.GetSec();
            double nDays = static_cast<double>(nSeconds) * ( 1.0 / (24.0*3600.0) );
            const Color* pCol;

            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdTimeIdx();
            }
            else
            {
                sal_uInt32 n;   // Dummy
                pFormatter = SbiInstance::PrepareNumberFormatter( n, nIndex, n );
            }

            pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
        }
        pMeth->PutString( aRes );
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
    }
}

void SbRtl_Timer(StarBASIC *, SbxArray & rPar, bool)
{
    tools::Time aTime( tools::Time::SYSTEM );
    tools::Long nSeconds = aTime.GetHour();
    nSeconds *= 3600;
    nSeconds += aTime.GetMin() * 60;
    nSeconds += aTime.GetSec();
    rPar.Get(0)->PutDate(static_cast<double>(nSeconds));
}


void SbRtl_Date(StarBASIC *, SbxArray & rPar, bool bWrite)
{
    if ( !bWrite )
    {
        Date aToday( Date::SYSTEM );
        double nDays = static_cast<double>(GetDayDiff( aToday ));
        SbxVariable* pMeth = rPar.Get(0);
        if( pMeth->IsString() )
        {
            OUString aRes;
            const Color* pCol;

            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdDateIdx();
            }
            else
            {
                sal_uInt32 n;
                pFormatter = SbiInstance::PrepareNumberFormatter( nIndex, n, n );
            }

            pFormatter->GetOutputString( nDays, nIndex, aRes, &pCol );
            pMeth->PutString( aRes );
        }
        else
        {
            pMeth->PutDate( nDays );
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_NOT_IMPLEMENTED );
    }
}

void SbRtl_IsArray(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    rPar.Get(0)->PutBool((rPar.Get(1)->GetType() & SbxARRAY) != 0);
}

void SbRtl_IsObject(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariable* pVar = rPar.Get(1);
    bool bObject = pVar->IsObject();
    SbxBase* pObj = (bObject ? pVar->GetObject() : nullptr);

    if( auto pUnoClass = dynamic_cast<SbUnoClass*>( pObj) )
    {
        bObject = pUnoClass->getUnoClass().is();
    }
    rPar.Get(0)->PutBool(bObject);
}

void SbRtl_IsDate(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    // #46134 only string is converted, all other types result in sal_False
    SbxVariableRef xArg = rPar.Get(1);
    SbxDataType eType = xArg->GetType();
    bool bDate = false;

    if( eType == SbxDATE )
    {
        bDate = true;
    }
    else if( eType == SbxSTRING )
    {
        ErrCode nPrevError = SbxBase::GetError();
        SbxBase::ResetError();

        // force conversion of the parameter to SbxDATE
        xArg->SbxValue::GetDate();

        bDate = !SbxBase::IsError();

        SbxBase::ResetError();
        SbxBase::SetError( nPrevError );
    }
    rPar.Get(0)->PutBool(bDate);
}

void SbRtl_IsEmpty(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariable* pVar = nullptr;
    if( SbiRuntime::isVBAEnabled() )
    {
        pVar = getDefaultProp(rPar.Get(1));
    }
    if ( pVar )
    {
        pVar->Broadcast( SfxHintId::BasicDataWanted );
        rPar.Get(0)->PutBool(pVar->IsEmpty());
    }
    else
    {
        rPar.Get(0)->PutBool(rPar.Get(1)->IsEmpty());
    }
}

void SbRtl_IsError(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxVariable* pVar = rPar.Get(1);
    SbUnoObject* pObj = dynamic_cast<SbUnoObject*>( pVar  );
    if ( !pObj )
    {
        if ( SbxBase* pBaseObj = (pVar->IsObject() ? pVar->GetObject() : nullptr) )
        {
            pObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
        }
    }
    uno::Reference< script::XErrorQuery > xError;
    if ( pObj )
    {
        xError.set( pObj->getUnoAny(), uno::UNO_QUERY );
    }
    if ( xError.is() )
    {
        rPar.Get(0)->PutBool(xError->hasError());
    }
    else
    {
        rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
    }
}

void SbRtl_IsNull(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    // #51475 because of Uno-objects return true
    // even if the pObj value is NULL
    SbxVariableRef pArg = rPar.Get(1);
    bool bNull = rPar.Get(1)->IsNull();
    if( !bNull && pArg->GetType() == SbxOBJECT )
    {
        SbxBase* pObj = pArg->GetObject();
        if( !pObj )
        {
            bNull = true;
        }
    }
    rPar.Get(0)->PutBool(bNull);
}

void SbRtl_IsNumeric(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    rPar.Get(0)->PutBool(rPar.Get(1)->IsNumericRTL());
}


void SbRtl_IsMissing(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    // #57915 Missing is reported by an error
    rPar.Get(0)->PutBool(rPar.Get(1)->IsErr());
}

// Function looks for wildcards, removes them and always returns the pure path
static OUString implSetupWildcard(const OUString& rFileParam, SbiRTLData& rRTLData)
{
    static const char cDelim1 = '/';
    static const char cDelim2 = '\\';
    static const char cWild1 = '*';
    static const char cWild2 = '?';

    rRTLData.moWildCard.reset();
    rRTLData.sFullNameToBeChecked.clear();

    OUString aFileParam = rFileParam;
    sal_Int32 nLastWild = aFileParam.lastIndexOf( cWild1 );
    if( nLastWild < 0 )
    {
        nLastWild = aFileParam.lastIndexOf( cWild2 );
    }
    bool bHasWildcards = ( nLastWild >= 0 );


    sal_Int32 nLastDelim = aFileParam.lastIndexOf( cDelim1 );
    if( nLastDelim < 0 )
    {
        nLastDelim = aFileParam.lastIndexOf( cDelim2 );
    }
    if( bHasWildcards )
    {
        // Wildcards in path?
        if( nLastDelim >= 0 && nLastDelim > nLastWild )
        {
            return aFileParam;
        }
    }
    else
    {
        OUString aPathStr = getFullPath( aFileParam );
        if( nLastDelim != aFileParam.getLength() - 1 )
        {
            rRTLData.sFullNameToBeChecked = aPathStr;
        }
        return aPathStr;
    }

    OUString aPureFileName;
    if( nLastDelim < 0 )
    {
        aPureFileName = aFileParam;
        aFileParam.clear();
    }
    else
    {
        aPureFileName = aFileParam.copy( nLastDelim + 1 );
        aFileParam = aFileParam.copy( 0, nLastDelim );
    }

    // Try again to get a valid URL/UNC-path with only the path
    OUString aPathStr = getFullPath( aFileParam );

    // Is there a pure file name left? Otherwise the path is
    // invalid anyway because it was not accepted by OSL before
    if (aPureFileName != "*")
    {
        rRTLData.moWildCard.emplace(aPureFileName);
    }
    return aPathStr;
}

static bool implCheckWildcard(std::u16string_view rName, SbiRTLData const& rRTLData)
{
    bool bMatch = true;

    if (rRTLData.moWildCard)
    {
        bMatch = rRTLData.moWildCard->Matches(rName);
    }
    return bMatch;
}


static bool isRootDir( std::u16string_view aDirURLStr )
{
    INetURLObject aDirURLObj( aDirURLStr );
    bool bRoot = false;

    // Check if it's a root directory
    sal_Int32 nCount = aDirURLObj.getSegmentCount();

    // No segment means Unix root directory "file:///"
    if( nCount == 0 )
    {
        bRoot = true;
    }
    // Exactly one segment needs further checking, because it
    // can be Unix "file:///foo/" -> no root
    // or Windows  "file:///c:/"  -> root
    else if( nCount == 1 )
    {
        OUString aSeg1 = aDirURLObj.getName( 0, true,
                                             INetURLObject::DecodeMechanism::WithCharset );
        if( aSeg1[1] == ':' )
        {
            bRoot = true;
        }
    }
    // More than one segments can never be root
    // so bRoot remains false

    return bRoot;
}

void SbRtl_Dir(StarBASIC *, SbxArray & rPar, bool)
{
    OUString aPath;

    const sal_uInt32 nParCount = rPar.Count();
    if( nParCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbiRTLData& rRTLData = GetSbData()->pInst->GetRTLData();

        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                if ( nParCount >= 2 )
                {
                    OUString aFileParam = rPar.Get(1)->GetOUString();

                    OUString aFileURLStr = implSetupWildcard(aFileParam, rRTLData);
                    if (!rRTLData.sFullNameToBeChecked.isEmpty())
                    {
                        bool bExists = false;
                        try { bExists = xSFI->exists( aFileURLStr ); }
                        catch(const Exception & ) {}

                        OUString aNameOnlyStr;
                        if( bExists )
                        {
                            INetURLObject aFileURL( aFileURLStr );
                            aNameOnlyStr = aFileURL.getName( INetURLObject::LAST_SEGMENT,
                                                             true, INetURLObject::DecodeMechanism::WithCharset );
                        }
                        rPar.Get(0)->PutString(aNameOnlyStr);
                        return;
                    }

                    try
                    {
                        OUString aDirURLStr;
                        bool bFolder = xSFI->isFolder( aFileURLStr );

                        if( bFolder )
                        {
                            aDirURLStr = aFileURLStr;
                        }
                        else
                        {
                            rPar.Get(0)->PutString(u""_ustr);
                        }

                        sal_Int16 nFlags = SbAttributes::NORMAL;
                        if ( nParCount > 2 )
                        {
                            rRTLData.nDirFlags = nFlags = rPar.Get(2)->GetInteger();
                        }
                        else
                        {
                            rRTLData.nDirFlags = SbAttributes::NORMAL;
                        }
                        // Read directory
                        bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
                        rRTLData.aDirSeq = xSFI->getFolderContents(aDirURLStr, bIncludeFolders);
                        rRTLData.nCurDirPos = 0;

                        // #78651 Add "." and ".." directories for VB compatibility
                        if( bIncludeFolders )
                        {
                            bool bRoot = isRootDir( aDirURLStr );

                            // If it's no root directory we flag the need for
                            // the "." and ".." directories by the value -2
                            // for the actual position. Later for -2 will be
                            // returned "." and for -1 ".."
                            if( !bRoot )
                            {
                                rRTLData.nCurDirPos = -2;
                            }
                        }
                    }
                    catch(const Exception & )
                    {
                    }
                }


                if (rRTLData.aDirSeq.hasElements())
                {
                    bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);

                    SbiInstance* pInst = GetSbData()->pInst;
                    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
                    for( ;; )
                    {
                        if (rRTLData.nCurDirPos < 0)
                        {
                            if (rRTLData.nCurDirPos == -2)
                            {
                                aPath = ".";
                            }
                            else if (rRTLData.nCurDirPos == -1)
                            {
                                aPath = "..";
                            }
                            rRTLData.nCurDirPos++;
                        }
                        else if (rRTLData.nCurDirPos >= rRTLData.aDirSeq.getLength())
                        {
                            rRTLData.aDirSeq.realloc(0);
                            aPath.clear();
                            break;
                        }
                        else
                        {
                            OUString aFile
                                = rRTLData.aDirSeq.getConstArray()[rRTLData.nCurDirPos++];

                            if( bCompatibility )
                            {
                                if( !bFolderFlag )
                                {
                                    bool bFolder = xSFI->isFolder( aFile );
                                    if( bFolder )
                                    {
                                        continue;
                                    }
                                }
                            }
                            else
                            {
                                // Only directories
                                if( bFolderFlag )
                                {
                                    bool bFolder = xSFI->isFolder( aFile );
                                    if( !bFolder )
                                    {
                                        continue;
                                    }
                                }
                            }

                            INetURLObject aURL( aFile );
                            aPath = aURL.getName( INetURLObject::LAST_SEGMENT, true,
                                                  INetURLObject::DecodeMechanism::WithCharset );
                        }

                        bool bMatch = implCheckWildcard(aPath, rRTLData);
                        if( !bMatch )
                        {
                            continue;
                        }
                        break;
                    }
                }
                rPar.Get(0)->PutString(aPath);
            }
        }
        else
        {
            // TODO: OSL
            if ( nParCount >= 2 )
            {
                OUString aFileParam = rPar.Get(1)->GetOUString();

                OUString aDirURL = implSetupWildcard(aFileParam, rRTLData);

                sal_Int16 nFlags = SbAttributes::NORMAL;
                if ( nParCount > 2 )
                {
                    rRTLData.nDirFlags = nFlags = rPar.Get(2)->GetInteger();
                }
                else
                {
                    rRTLData.nDirFlags = SbAttributes::NORMAL;
                }

                // Read directory
                bool bIncludeFolders = bool(nFlags & SbAttributes::DIRECTORY);
                rRTLData.pDir = std::make_unique<Directory>(aDirURL);
                FileBase::RC nRet = rRTLData.pDir->open();
                if( nRet != FileBase::E_None )
                {
                    rRTLData.pDir.reset();
                    rPar.Get(0)->PutString(OUString());
                    return;
                }

                // #86950 Add "." and ".." directories for VB compatibility
                rRTLData.nCurDirPos = 0;
                if( bIncludeFolders )
                {
                    bool bRoot = isRootDir( aDirURL );

                    // If it's no root directory we flag the need for
                    // the "." and ".." directories by the value -2
                    // for the actual position. Later for -2 will be
                    // returned "." and for -1 ".."
                    if( !bRoot )
                    {
                        rRTLData.nCurDirPos = -2;
                    }
                }

            }

            if (rRTLData.pDir)
            {
                bool bFolderFlag = bool(rRTLData.nDirFlags & SbAttributes::DIRECTORY);
                for( ;; )
                {
                    if (rRTLData.nCurDirPos < 0)
                    {
                        if (rRTLData.nCurDirPos == -2)
                        {
                            aPath = ".";
                        }
                        else if (rRTLData.nCurDirPos == -1)
                        {
                            aPath = "..";
                        }
                        rRTLData.nCurDirPos++;
                    }
                    else
                    {
                        DirectoryItem aItem;
                        FileBase::RC nRet = rRTLData.pDir->getNextItem(aItem);
                        if( nRet != FileBase::E_None )
                        {
                            rRTLData.pDir.reset();
                            aPath.clear();
                            break;
                        }

                        // Handle flags
                        FileStatus aFileStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName );
                        nRet = aItem.getFileStatus( aFileStatus );
                        if( nRet != FileBase::E_None )
                        {
                            SAL_WARN("basic", "getFileStatus failed");
                            continue;
                        }

                        // Only directories?
                        if( bFolderFlag )
                        {
                            FileStatus::Type aType = aFileStatus.getFileType();
                            bool bFolder = isFolder( aType );
                            if( !bFolder )
                            {
                                continue;
                            }
                        }

                        aPath = aFileStatus.getFileName();
                    }

                    bool bMatch = implCheckWildcard(aPath, rRTLData);
                    if( !bMatch )
                    {
                        continue;
                    }
                    break;
                }
            }
            rPar.Get(0)->PutString(aPath);
        }
    }
}


void SbRtl_GetAttr(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        sal_Int16 nFlags = SbAttributes::NORMAL;

        // In Windows, we want to use Windows API to get the file attributes
        // for VBA interoperability.
    #if defined(_WIN32)
        if( SbiRuntime::isVBAEnabled() )
        {
            OUString aPathURL = getFullPath(rPar.Get(1)->GetOUString());
            OUString aPath;
            FileBase::getSystemPathFromFileURL( aPathURL, aPath );
            DWORD nRealFlags = GetFileAttributesW (o3tl::toW(aPath.getStr()));
            if (nRealFlags != 0xffffffff)
            {
                if (nRealFlags == FILE_ATTRIBUTE_NORMAL)
                {
                    nRealFlags = 0;
                }
                nFlags = static_cast<sal_Int16>(nRealFlags);
            }
            else
            {
                StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
            }
            rPar.Get(0)->PutInteger(nFlags);

            return;
        }
    #endif

        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    OUString aPath = getFullPath(rPar.Get(1)->GetOUString());
                    bool bExists = false;
                    try { bExists = xSFI->exists( aPath ); }
                    catch(const Exception & ) {}
                    if( !bExists )
                    {
                        return StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
                    }

                    bool bReadOnly = xSFI->isReadOnly( aPath );
                    bool bHidden = xSFI->isHidden( aPath );
                    bool bDirectory = xSFI->isFolder( aPath );
                    if( bReadOnly )
                    {
                        nFlags |= SbAttributes::READONLY;
                    }
                    if( bHidden )
                    {
                        nFlags |= SbAttributes::HIDDEN;
                    }
                    if( bDirectory )
                    {
                        nFlags |= SbAttributes::DIRECTORY;
                    }
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            DirectoryItem aItem;
            (void)DirectoryItem::get(getFullPath(rPar.Get(1)->GetOUString()), aItem);
            FileStatus aFileStatus( osl_FileStatus_Mask_Attributes | osl_FileStatus_Mask_Type );
            (void)aItem.getFileStatus( aFileStatus );
            sal_uInt64 nAttributes = aFileStatus.getAttributes();
            bool bReadOnly = (nAttributes & osl_File_Attribute_ReadOnly) != 0;

            FileStatus::Type aType = aFileStatus.getFileType();
            bool bDirectory = isFolder( aType );
            if( bReadOnly )
            {
                nFlags |= SbAttributes::READONLY;
            }
            if( bDirectory )
            {
                nFlags |= SbAttributes::DIRECTORY;
            }
        }
        rPar.Get(0)->PutInteger(nFlags);
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}


void SbRtl_FileDateTime(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        OUString aPath = rPar.Get(1)->GetOUString();
        tools::Time aTime( tools::Time::EMPTY );
        Date aDate( Date::EMPTY );
        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    util::DateTime aUnoDT = xSFI->getDateTimeModified( aPath );
                    aTime = tools::Time( aUnoDT );
                    aDate = Date( aUnoDT );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            bool bSuccess = false;
            do
            {
                DirectoryItem aItem;
                if (DirectoryItem::get( getFullPath( aPath ), aItem ) != FileBase::E_None)
                    break;

                FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
                if (aItem.getFileStatus( aFileStatus ) != FileBase::E_None)
                    break;

                TimeValue aTimeVal = aFileStatus.getModifyTime();
                oslDateTime aDT;
                if (!osl_getDateTimeFromTimeValue( &aTimeVal, &aDT ))
                    // Strictly spoken this is not an i/o error but some other failure.
                    break;

                aTime = tools::Time( aDT.Hours, aDT.Minutes, aDT.Seconds, aDT.NanoSeconds );
                aDate = Date( aDT.Day, aDT.Month, aDT.Year );
                bSuccess = true;
            }
            while(false);

            if (!bSuccess)
                StarBASIC::Error( ERRCODE_IO_GENERAL );
        }

        // An empty date shall not result in a formatted null-date (1899-12-30
        // or 1900-01-01) or even worse -0001-12-03 or some such due to how
        // GetDayDiff() treats things. There should be an error set in this
        // case anyway because of a missing file or other error above, but... so
        // do not even bother to use the number formatter.
        OUString aRes;
        if (aDate.IsEmpty())
        {
            aRes = "0000-00-00 00:00:00";
        }
        else
        {
            double fSerial = static_cast<double>(GetDayDiff( aDate ));
            tools::Long nSeconds = aTime.GetHour();
            nSeconds *= 3600;
            nSeconds += aTime.GetMin() * 60;
            nSeconds += aTime.GetSec();
            double nDays = static_cast<double>(nSeconds) / (24.0*3600.0);
            fSerial += nDays;

            const Color* pCol;

            std::shared_ptr<SvNumberFormatter> pFormatter;
            sal_uInt32 nIndex;
            if( GetSbData()->pInst )
            {
                pFormatter = GetSbData()->pInst->GetNumberFormatter();
                nIndex = GetSbData()->pInst->GetStdDateTimeIdx();
            }
            else
            {
                sal_uInt32 n;
                pFormatter = SbiInstance::PrepareNumberFormatter( n, n, nIndex );
            }

            pFormatter->GetOutputString( fSerial, nIndex, aRes, &pCol );
        }
        rPar.Get(0)->PutString(aRes);
    }
}


void SbRtl_EOF(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        bool beof;
        SvStream* pSvStrm = pSbStrm->GetStrm();
        if ( pSbStrm->IsText() )
        {
            char cBla;
            (*pSvStrm).ReadChar( cBla ); // can we read another character?
            beof = pSvStrm->eof();
            if ( !beof )
            {
                pSvStrm->SeekRel( -1 );
            }
        }
        else
        {
            beof = pSvStrm->eof();  // for binary data!
        }
        rPar.Get(0)->PutBool(beof);
    }
}

void SbRtl_FileAttr(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    // #57064 Although this function doesn't operate with DirEntry, it is
    // not touched by the adjustment to virtual URLs, as it only works on
    // already opened files and the name doesn't matter there.

    if (rPar.Count() != 3)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        sal_Int16 nRet;
        if (rPar.Get(2)->GetInteger() == 1)
        {
            nRet = static_cast<sal_Int16>(pSbStrm->GetMode());
        }
        else
        {
            nRet = 0; // System file handle not supported
        }
        rPar.Get(0)->PutInteger(nRet);
    }
}
void SbRtl_Loc(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        SvStream* pSvStrm = pSbStrm->GetStrm();
        std::size_t nPos;
        if( pSbStrm->IsRandom())
        {
            short nBlockLen = pSbStrm->GetBlockLen();
            nPos = nBlockLen ? (pSvStrm->Tell() / nBlockLen) : 0;
            nPos++; // block positions starting at 1
        }
        else if ( pSbStrm->IsText() )
        {
            nPos = pSbStrm->GetLine();
        }
        else if( pSbStrm->IsBinary() )
        {
            nPos = pSvStrm->Tell();
        }
        else if ( pSbStrm->IsSeq() )
        {
            nPos = ( pSvStrm->Tell()+1 ) / 128;
        }
        else
        {
            nPos = pSvStrm->Tell();
        }
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
    }
}

void SbRtl_Lof(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        sal_Int16 nChannel = rPar.Get(1)->GetInteger();
        SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
        SbiStream* pSbStrm = pIO->GetStream( nChannel );
        if ( !pSbStrm )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
        }
        SvStream* pSvStrm = pSbStrm->GetStrm();
        sal_uInt64 const nLen = pSvStrm->TellEnd();
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nLen));
    }
}


void SbRtl_Seek(StarBASIC *, SbxArray & rPar, bool)
{
    // No changes for UCB
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs < 2 || nArgs > 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int16 nChannel = rPar.Get(1)->GetInteger();
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    SbiStream* pSbStrm = pIO->GetStream( nChannel );
    if ( !pSbStrm )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_CHANNEL );
    }
    SvStream* pStrm = pSbStrm->GetStrm();

    if ( nArgs == 2 )   // Seek-Function
    {
        sal_uInt64 nPos = pStrm->Tell();
        if( pSbStrm->IsRandom() )
        {
            nPos = nPos / pSbStrm->GetBlockLen();
        }
        nPos++; // Basic counts from 1
        rPar.Get(0)->PutLong(static_cast<sal_Int32>(nPos));
    }
    else                // Seek-Statement
    {
        sal_Int32 nPos = rPar.Get(2)->GetLong();
        if ( nPos < 1 )
        {
            return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        }
        nPos--; // Basic counts from 1, SvStreams count from 0
        pSbStrm->SetExpandOnWriteTo( 0 );
        if ( pSbStrm->IsRandom() )
        {
            nPos *= pSbStrm->GetBlockLen();
        }
        pStrm->Seek( static_cast<sal_uInt64>(nPos) );
        pSbStrm->SetExpandOnWriteTo( nPos );
    }
}

void SbRtl_Format(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if ( nArgCount < 2 || nArgCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        OUString aResult;
        if( nArgCount == 2 )
        {
            rPar.Get(1)->Format(aResult);
        }
        else
        {
            OUString aFmt(rPar.Get(2)->GetOUString());
            rPar.Get(1)->Format(aResult, &aFmt);
        }
        rPar.Get(0)->PutString(aResult);
    }
}

static bool IsMissing(SbxArray& rPar, const sal_uInt32 i)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if (nArgCount <= i)
        return true;

    SbxVariable* aPar = rPar.Get(i);
    return (aPar->GetType() == SbxERROR && SbiRuntime::IsMissing(aPar, 1));
}

static sal_Int16 GetOptionalIntegerParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
                                                  const sal_Int16 defaultValue)
{
    return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetInteger();
}

static OUString GetOptionalOUStringParamOrDefault(SbxArray& rPar, const sal_uInt32 i,
                                                  const OUString& defaultValue)
{
    return IsMissing(rPar, i) ? defaultValue : rPar.Get(i)->GetOUString();
}

static void lcl_FormatNumberPercent(SbxArray& rPar, bool isPercent)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if (nArgCount < 2 || nArgCount > 6)
    {
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
    }

    // The UI locale never changes -> we can use static value here
    static const LocaleDataWrapper localeData(Application::GetSettings().GetUILanguageTag());
    sal_Int16 nNumDigitsAfterDecimal = -1;
    if (nArgCount > 2 && !rPar.Get(2)->IsEmpty())
    {
        nNumDigitsAfterDecimal = rPar.Get(2)->GetInteger();
        if (nNumDigitsAfterDecimal < -1)
        {
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
        else if (nNumDigitsAfterDecimal > 255)
            nNumDigitsAfterDecimal %= 256;
    }
    if (nNumDigitsAfterDecimal == -1)
        nNumDigitsAfterDecimal = LocaleDataWrapper::getNumDigits();

    bool bIncludeLeadingDigit = LocaleDataWrapper::isNumLeadingZero();
    if (nArgCount > 3 && !rPar.Get(3)->IsEmpty())
    {
        switch (rPar.Get(3)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
                bIncludeLeadingDigit = false;
                break;
            case ooo::vba::VbTriState::vbTrue:
                bIncludeLeadingDigit = true;
                break;
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }

    bool bUseParensForNegativeNumbers = false;
    if (nArgCount > 4 && !rPar.Get(4)->IsEmpty())
    {
        switch (rPar.Get(4)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing
                break;
            case ooo::vba::VbTriState::vbTrue:
                bUseParensForNegativeNumbers = true;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }

    bool bGroupDigits = false;
    if (nArgCount > 5 && !rPar.Get(5)->IsEmpty())
    {
        switch (rPar.Get(5)->GetInteger())
        {
            case ooo::vba::VbTriState::vbFalse:
            case ooo::vba::VbTriState::vbUseDefault:
                // do nothing
                break;
            case ooo::vba::VbTriState::vbTrue:
                bGroupDigits = true;
                break;
            default:
                return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);
        }
    }

    double fVal = rPar.Get(1)->GetDouble();
    if (isPercent)
        fVal *= 100;
    const bool bNegative = fVal < 0;
    if (bNegative)
        fVal = fabs(fVal); // Always work with non-negatives, to easily handle leading zero

    static const sal_Unicode decSep = localeData.getNumDecimalSep().toChar();
    OUStringBuffer aResult;
    rtl::math::doubleToUStringBuffer(aResult,
        fVal, rtl_math_StringFormat_F, nNumDigitsAfterDecimal, decSep,
        bGroupDigits ? localeData.getDigitGrouping().getConstArray() : nullptr,
        localeData.getNumThousandSep().toChar());

    if (!bIncludeLeadingDigit && aResult.getLength() > 1)
        aResult.stripStart('0');

    if (nNumDigitsAfterDecimal > 0)
    {
        const sal_Int32 nSepPos = aResult.indexOf(decSep);

        // VBA allows up to 255 digits; rtl::math::doubleToUString outputs up to 15 digits
        // for ~small numbers, so pad them as appropriate.
        if (nSepPos >= 0)
            comphelper::string::padToLength(aResult, nSepPos + nNumDigitsAfterDecimal + 1, '0');
    }

    if (bNegative)
    {
        if (bUseParensForNegativeNumbers)
            aResult.insert(0, '(').append(')');
        else
            aResult.insert(0, '-');
    }
    if (isPercent)
        aResult.append('%');
    rPar.Get(0)->PutString(aResult.makeStringAndClear());
}

// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatnumber-function
void SbRtl_FormatNumber(StarBASIC*, SbxArray& rPar, bool)
{
    return lcl_FormatNumberPercent(rPar, false);
}

// https://docs.microsoft.com/en-us/office/vba/Language/Reference/User-Interface-Help/formatpercent-function
void SbRtl_FormatPercent(StarBASIC*, SbxArray& rPar, bool)
{
    return lcl_FormatNumberPercent(rPar, true);
}

namespace {

// note: BASIC does not use comphelper::random, because
// Randomize(int) must be supported and should not affect non-BASIC random use
struct RandomNumberGenerator
{
    std::mt19937 global_rng;

    RandomNumberGenerator()
    {
        try
        {
            std::random_device rd;
            // initialises the state of the global random number generator
            // should only be called once.
            // (note, a few std::variate_generator<> (like normal) have their
            // own state which would need a reset as well to guarantee identical
            // sequence of numbers, e.g. via myrand.distribution().reset())
            global_rng.seed(rd() ^ time(nullptr));
        }
        catch (std::runtime_error& e)
        {
            SAL_WARN("basic", "Using std::random_device failed: " << e.what());
            global_rng.seed(time(nullptr));
        }
    }
};

RandomNumberGenerator& theRandomNumberGenerator()
{
    static RandomNumberGenerator theGenerator;
    return theGenerator;
}

}

void SbRtl_Randomize(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() > 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    if (rPar.Count() == 2)
    {
        int nSeed = static_cast<int>(rPar.Get(1)->GetInteger());
        theRandomNumberGenerator().global_rng.seed(nSeed);
    }
    // without parameter, no need to do anything - RNG is seeded at first use
}

void SbRtl_Rnd(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() > 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        std::uniform_real_distribution<double> dist(0.0, 1.0);
        double const tmp(dist(theRandomNumberGenerator().global_rng));
        rPar.Get(0)->PutDouble(tmp);
    }
}


//  Syntax: Shell("Path",[ Window-Style,[ "Params", [ bSync = sal_False ]]])
//  WindowStyles (VBA compatible):
//      2 == Minimized
//      3 == Maximized
//     10 == Full-Screen (text mode applications OS/2, WIN95, WNT)
//     HACK: The WindowStyle will be passed to
//     Application::StartApp in Creator. Format: "xxxx2"


void SbRtl_Shell(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if ( nArgCount < 2 || nArgCount > 5 )
    {
        rPar.Get(0)->PutLong(0);
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        // Just go straight to error in this case
        if (comphelper::LibreOfficeKit::isActive())
        {
            StarBASIC::Error(ERRCODE_BASIC_FILE_NOT_FOUND);
            return;
        }

        oslProcessOption nOptions = osl_Process_SEARCHPATH | osl_Process_DETACHED;

        OUString aCmdLine = rPar.Get(1)->GetOUString();
        // attach additional parameters - everything must be parsed anyway
        if( nArgCount >= 4 )
        {
            OUString tmp = rPar.Get(3)->GetOUString().trim();
            if (!tmp.isEmpty())
            {
                aCmdLine += " " + tmp;
            }
        }
        sal_Int32 nLen = aCmdLine.getLength();

        // #55735 if there are parameters, they have to be separated
        // #72471 also separate the single parameters
        std::vector<OUString> aTokenVector;
        for (sal_Int32 i = 0; i < nLen;)
        {
            sal_Unicode c = aCmdLine[i];
            if (c == ' ' || c == '\t')
            {
                ++i;
                continue;
            }

            OUString aToken;
            if( c == '\"' || c == '\'' )
            {
                sal_Int32 iFoundPos = aCmdLine.indexOf( c, i + 1 );

                if( iFoundPos < 0 )
                {
                    aToken = aCmdLine.copy( i);
                    i = nLen;
                }
                else
                {
                    aToken = aCmdLine.copy( i + 1, (iFoundPos - i - 1) );
                    i = iFoundPos + 1;
                }
            }
            else
            {
                sal_Int32 iFoundSpacePos = aCmdLine.indexOf( ' ', i );
                sal_Int32 iFoundTabPos = aCmdLine.indexOf( '\t', i );
                sal_Int32 iFoundPos = iFoundSpacePos >= 0 ? iFoundTabPos >= 0 ? std::min( iFoundSpacePos, iFoundTabPos ) : iFoundSpacePos : -1;

                if( iFoundPos < 0 )
                {
                    aToken = aCmdLine.copy( i );
                    i = nLen;
                }
                else
                {
                    aToken = aCmdLine.copy( i, (iFoundPos - i) );
                    i = iFoundPos;
                }
            }

            // insert into the list
            aTokenVector.push_back( aToken );
        }
        // #55735 / #72471 end

        if (aTokenVector.empty())
            return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);

        sal_Int16 nWinStyle = 0;
        if( nArgCount >= 3 )
        {
            nWinStyle = rPar.Get(2)->GetInteger();
            switch( nWinStyle )
            {
            case 2:
                nOptions |= osl_Process_MINIMIZED;
                break;
            case 3:
                nOptions |= osl_Process_MAXIMIZED;
                break;
            case 10:
                nOptions |= osl_Process_FULLSCREEN;
                break;
            }

            bool bSync = false;
            if( nArgCount >= 5 )
            {
                bSync = rPar.Get(4)->GetBool();
            }
            if( bSync )
            {
                nOptions |= osl_Process_WAIT;
            }
        }

        // #72471 work parameter(s) up
        std::vector<OUString>::const_iterator iter = aTokenVector.begin();
        OUString aOUStrProgURL = getFullPath( *iter );

        ++iter;

        sal_uInt16 nParamCount = sal::static_int_cast< sal_uInt16 >(aTokenVector.size() - 1 );
        std::unique_ptr<rtl_uString*[]> pParamList;
        if( nParamCount )
        {
            pParamList.reset( new rtl_uString*[nParamCount]);
            for(int iVector = 0; iter != aTokenVector.end(); ++iVector, ++iter)
            {
                const OUString& rParamStr = *iter;
                pParamList[iVector] = nullptr;
                rtl_uString_assign(&(pParamList[iVector]), rParamStr.pData);
            }
        }

        oslProcess pApp;
        bool bSucc = osl_executeProcess(
                    aOUStrProgURL.pData,
                    pParamList.get(),
                    nParamCount,
                    nOptions,
                    nullptr,
                    nullptr,
                    nullptr, 0,
                    &pApp ) == osl_Process_E_None;

        // 53521 only free process handle on success
        if (bSucc)
        {
            osl_freeProcessHandle( pApp );
        }

        for(int j = 0; j < nParamCount; ++j)
        {
            rtl_uString_release(pParamList[j]);
        }

        if( !bSucc )
        {
            StarBASIC::Error( ERRCODE_BASIC_FILE_NOT_FOUND );
        }
        else
        {
            rPar.Get(0)->PutLong(0);
        }
    }
}

void SbRtl_VarType(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxDataType eType = rPar.Get(1)->GetType();
        rPar.Get(0)->PutInteger(static_cast<sal_Int16>(eType));
    }
}

// Exported function
const OUString & getBasicTypeName( SbxDataType eType )
{
    static constexpr OUString pTypeNames[] =
    {
        u"Empty"_ustr,            // SbxEMPTY
        u"Null"_ustr,             // SbxNULL
        u"Integer"_ustr,          // SbxINTEGER
        u"Long"_ustr,             // SbxLONG
        u"Single"_ustr,           // SbxSINGLE
        u"Double"_ustr,           // SbxDOUBLE
        u"Currency"_ustr,         // SbxCURRENCY
        u"Date"_ustr,             // SbxDATE
        u"String"_ustr,           // SbxSTRING
        u"Object"_ustr,           // SbxOBJECT
        u"Error"_ustr,            // SbxERROR
        u"Boolean"_ustr,          // SbxBOOL
        u"Variant"_ustr,          // SbxVARIANT
        u"DataObject"_ustr,       // SbxDATAOBJECT
        u"Unknown Type"_ustr,
        u"Unknown Type"_ustr,
        u"Char"_ustr,             // SbxCHAR
        u"Byte"_ustr,             // SbxBYTE
        u"UShort"_ustr,           // SbxUSHORT
        u"ULong"_ustr,            // SbxULONG
        u"Long64"_ustr,           // SbxLONG64
        u"ULong64"_ustr,          // SbxULONG64
        u"Int"_ustr,              // SbxINT
        u"UInt"_ustr,             // SbxUINT
        u"Void"_ustr,             // SbxVOID
        u"HResult"_ustr,          // SbxHRESULT
        u"Pointer"_ustr,          // SbxPOINTER
        u"DimArray"_ustr,         // SbxDIMARRAY
        u"CArray"_ustr,           // SbxCARRAY
        u"Userdef"_ustr,          // SbxUSERDEF
        u"Lpstr"_ustr,            // SbxLPSTR
        u"Lpwstr"_ustr,           // SbxLPWSTR
        u"Unknown Type"_ustr,     // SbxCoreSTRING
        u"WString"_ustr,          // SbxWSTRING
        u"WChar"_ustr,            // SbxWCHAR
        u"Int64"_ustr,            // SbxSALINT64
        u"UInt64"_ustr,           // SbxSALUINT64
        u"Decimal"_ustr,          // SbxDECIMAL
    };

    size_t nPos = static_cast<size_t>(eType) & 0x0FFF;
    const size_t nTypeNameCount = std::size( pTypeNames );
    if ( nPos >= nTypeNameCount )
    {
        nPos = nTypeNameCount - 1;
    }
    return pTypeNames[nPos];
}

static OUString getObjectTypeName( SbxVariable* pVar )
{
    OUString sRet( u"Object"_ustr );
    if ( pVar )
    {
        SbxBase* pBaseObj = pVar->GetObject();
        if( !pBaseObj )
        {
           sRet = "Nothing";
        }
        else
        {
            SbUnoObject* pUnoObj = dynamic_cast<SbUnoObject*>( pVar  );
            if ( !pUnoObj )
            {
                pUnoObj = dynamic_cast<SbUnoObject*>( pBaseObj  );
            }
            if ( pUnoObj )
            {
                Any aObj = pUnoObj->getUnoAny();
                // For upstreaming unless we start to build oovbaapi by default
                // we need to get detect the vba-ness of the object in some
                // other way
                // note: Automation objects do not support XServiceInfo
                uno::Reference< XServiceInfo > xServInfo( aObj, uno::UNO_QUERY );
                if ( xServInfo.is() )
                {
                    // is this a VBA object ?
                    Sequence< OUString > sServices = xServInfo->getSupportedServiceNames();
                    if ( sServices.hasElements() )
                    {
                        sRet = sServices[ 0 ];
                    }
                }
                else
                {
                    uno::Reference< bridge::oleautomation::XAutomationObject > xAutoMation( aObj, uno::UNO_QUERY );
                    if ( xAutoMation.is() )
                    {
                        uno::Reference< script::XInvocation > xInv( aObj, uno::UNO_QUERY );
                        if ( xInv.is() )
                        {
                            try
                            {
                                xInv->getValue( u"$GetTypeName"_ustr ) >>= sRet;
                            }
                            catch(const Exception& )
                            {
                            }
                        }
                    }
                }
                sal_Int32 nDot = sRet.lastIndexOf( '.' );
                if ( nDot != -1 && nDot < sRet.getLength() )
                {
                    sRet = sRet.copy( nDot + 1 );
                }
            }
        }
    }
    return sRet;
}

void SbRtl_TypeName(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else
    {
        SbxDataType eType = rPar.Get(1)->GetType();
        bool bIsArray = ( ( eType & SbxARRAY ) != 0 );

        OUString aRetStr;
        if ( SbiRuntime::isVBAEnabled() && eType == SbxOBJECT )
        {
            aRetStr = getObjectTypeName(rPar.Get(1));
        }
        else
        {
            aRetStr = getBasicTypeName( eType );
        }
        if( bIsArray )
        {
            aRetStr += "()";
        }
        rPar.Get(0)->PutString(aRetStr);
    }
}

void SbRtl_Len(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    const OUString aStr = rPar.Get(1)->GetOUString();
    rPar.Get(0)->PutLong(aStr.getLength());
}

void SbRtl_DDEInitiate(StarBASIC *, SbxArray & rPar, bool)
{
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    const OUString aApp = rPar.Get(1)->GetOUString();
    const OUString aTopic = rPar.Get(2)->GetOUString();

    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    size_t nChannel;
    ErrCode nDdeErr = pDDE->Initiate( aApp, aTopic, nChannel );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
    else
    {
        rPar.Get(0)->PutInteger(static_cast<sal_Int16>(nChannel));
    }
}

void SbRtl_DDETerminate(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 2 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        return;
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Terminate( nChannel );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}

void SbRtl_DDETerminateAll(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 1 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->TerminateAll();
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}

void SbRtl_DDERequest(StarBASIC *, SbxArray & rPar, bool)
{
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aItem = rPar.Get(2)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    OUString aResult;
    ErrCode nDdeErr = pDDE->Request( nChannel, aItem, aResult );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
    else
    {
        rPar.Get(0)->PutString(aResult);
    }
}

void SbRtl_DDEExecute(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aCommand = rPar.Get(2)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Execute( nChannel, aCommand );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}

void SbRtl_DDEPoke(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    int nArgs = static_cast<int>(rPar.Count());
    if ( nArgs != 4 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    size_t nChannel = rPar.Get(1)->GetInteger();
    const OUString aItem = rPar.Get(2)->GetOUString();
    const OUString aData = rPar.Get(3)->GetOUString();
    SbiDdeControl* pDDE = GetSbData()->pInst->GetDdeControl();
    ErrCode nDdeErr = pDDE->Poke( nChannel, aItem, aData );
    if( nDdeErr )
    {
        StarBASIC::Error( nDdeErr );
    }
}


void SbRtl_FreeFile(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 1)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    short nChannel = 1;
    while( nChannel < CHANNELS )
    {
        SbiStream* pStrm = pIO->GetStream( nChannel );
        if( !pStrm )
        {
            rPar.Get(0)->PutInteger(nChannel);
            return;
        }
        nChannel++;
    }
    StarBASIC::Error( ERRCODE_BASIC_TOO_MANY_FILES );
}

void SbRtl_LBound(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nParCount = rPar.Count();
    if ( nParCount != 3 && nParCount != 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxBase* pParObj = rPar.Get(1)->GetObject();
    SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
    if( !pArr )
        return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );

    sal_Int32 nLower, nUpper;
    short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
    if (!pArr->GetDim(nDim, nLower, nUpper))
        return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
    rPar.Get(0)->PutLong(nLower);
}

void SbRtl_UBound(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nParCount = rPar.Count();
    if ( nParCount != 3 && nParCount != 2 )
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    SbxBase* pParObj = rPar.Get(1)->GetObject();
    SbxDimArray* pArr = dynamic_cast<SbxDimArray*>( pParObj );
    if( !pArr )
        return StarBASIC::Error( ERRCODE_BASIC_MUST_HAVE_DIMS );

    sal_Int32 nLower, nUpper;
    short nDim = (nParCount == 3) ? static_cast<short>(rPar.Get(2)->GetInteger()) : 1;
    if (!pArr->GetDim(nDim, nLower, nUpper))
        return StarBASIC::Error( ERRCODE_BASIC_OUT_OF_RANGE );
    rPar.Get(0)->PutLong(nUpper);
}

void SbRtl_RGB(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 4)
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );

    sal_Int32 nRed = rPar.Get(1)->GetInteger() & 0xFF;
    sal_Int32 nGreen = rPar.Get(2)->GetInteger() & 0xFF;
    sal_Int32 nBlue = rPar.Get(3)->GetInteger() & 0xFF;
    sal_Int32 nRGB;

    SbiInstance* pInst = GetSbData()->pInst;
    bool bCompatibility = ( pInst && pInst->IsCompatibility() );
    // See discussion in tdf#145725, here's the quotation from a link indicated in the bugtracker
    // which explains why we need to manage RGB differently according to VB compatibility
    // "In other words, the individual color components are stored in the opposite order one would expect.
    //  VB stores the red color component in the low-order byte of the long integer's low-order word,
    //  the green color in the high-order byte of the low-order word, and the blue color in the low-order byte of the high-order word"
    if( bCompatibility )
    {
        nRGB   = (nBlue << 16) | (nGreen << 8) | nRed;
    }
    else
    {
        nRGB   = (nRed << 16) | (nGreen << 8) | nBlue;
    }
    rPar.Get(0)->PutLong(nRGB);
}

void SbRtl_QBColor(StarBASIC *, SbxArray & rPar, bool)
{
    static const sal_Int32 pRGB[] =
    {
        0x000000,
        0x800000,
        0x008000,
        0x808000,
        0x000080,
        0x800080,
        0x008080,
        0xC0C0C0,
        0x808080,
        0xFF0000,
        0x00FF00,
        0xFFFF00,
        0x0000FF,
        0xFF00FF,
        0x00FFFF,
        0xFFFFFF,
    };

    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    sal_Int16 nCol = rPar.Get(1)->GetInteger();
    if( nCol < 0 || nCol > 15 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    sal_Int32 nRGB = pRGB[ nCol ];
    rPar.Get(0)->PutLong(nRGB);
}

static std::vector<sal_uInt8> byteArray2Vec(SbxArray* pArr)
{
    std::vector<sal_uInt8> result;
    if (pArr)
    {
        const sal_uInt32 nCount = pArr->Count();
        result.reserve(nCount + 1); // to avoid reallocation when padding in vbFromUnicode
        for (sal_uInt32 i = 0; i < nCount; i++)
            result.push_back(pArr->Get(i)->GetByte());
    }
    return result;
}

// Makes sure to get the byte array if passed, or the string converted to the bytes using
// StringToByteArray in basic/source/sbx/sbxstr.cxx
static std::vector<sal_uInt8> getByteArray(const SbxValue& val)
{
    if (val.GetFullType() == SbxOBJECT)
        if (auto pObj = val.GetObject())
            if (pObj->GetType() == (SbxARRAY | SbxBYTE))
                if (auto pArr = dynamic_cast<SbxArray*>(pObj))
                    return byteArray2Vec(pArr);

    // Convert to string
    tools::SvRef<SbxValue> pStringValue(new SbxValue(SbxSTRING));
    *pStringValue = val;

    // Convert string to byte array
    tools::SvRef<SbxValue> pValue(new SbxValue(SbxOBJECT));
    pValue->PutObject(new SbxArray(SbxBYTE));
    *pValue = *pStringValue; // Does the magic of conversion of strings to byte arrays
    return byteArray2Vec(dynamic_cast<SbxArray*>(pValue->GetObject()));
}

// StrConv(string, conversion, LCID)
void SbRtl_StrConv(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count() - 1;
    if( nArgCount < 2 || nArgCount > 3 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    sal_Int32 nConversion = rPar.Get(2)->GetLong();
    LanguageType nLanguage = LANGUAGE_SYSTEM;
    if (nArgCount == 3)
    {
        sal_Int32 lcid = rPar.Get(3)->GetLong();
        nLanguage = LanguageType(lcid);
    }

    if (nConversion == ooo::vba::VbStrConv::vbUnicode) // This mode does not combine
    {
        // Assume that the passed byte array is encoded in the defined encoding, convert to
        // UTF-16 and store as string. Passed strings are converted to byte array first.
        auto inArray = getByteArray(*rPar.Get(1));
        std::string_view s(reinterpret_cast<char*>(inArray.data()), inArray.size() / sizeof(char));
        const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
        OUString aOUStr = OStringToOUString(s, encoding);
        rPar.Get(0)->PutString(aOUStr);
        return;
    }

    if (nConversion == ooo::vba::VbStrConv::vbFromUnicode) // This mode does not combine
    {
        // Assume that the passed byte array is UTF-16-encoded (system-endian), convert to specified
        // encoding and store as byte array. Passed strings are converted to byte array first.
        auto inArray = getByteArray(*rPar.Get(1));
        while (inArray.size() % sizeof(sal_Unicode))
            inArray.push_back('\0');
        std::u16string_view s(reinterpret_cast<sal_Unicode*>(inArray.data()),
                              inArray.size() / sizeof(sal_Unicode));
        const auto encoding = utl_getWinTextEncodingFromLangStr(LanguageTag(nLanguage).getBcp47());
        OString aOStr = OUStringToOString(s, encoding);
        const sal_Int32 lb = IsBaseIndexOne() ? 1 : 0;
        const sal_Int32 ub = lb + aOStr.getLength() - 1;
        SbxDimArray* pArray = new SbxDimArray(SbxBYTE);
        pArray->unoAddDim(lb, ub);

        for (sal_Int32 i = 0; i < aOStr.getLength(); ++i)
        {
            SbxVariable* pNew = new SbxVariable(SbxBYTE);
            pNew->PutByte(aOStr[i]);
            pArray->Put(pNew, i);
        }

        SbxVariable* retVar = rPar.Get(0);
        SbxFlagBits nFlags = retVar->GetFlags();
        retVar->ResetFlag(SbxFlagBits::Fixed);
        retVar->PutObject(pArray);
        retVar->SetFlags(nFlags);
        retVar->SetParameters(nullptr);
        return;
    }

    std::vector<TransliterationFlags> aTranslitSet;
    auto check = [&nConversion, &aTranslitSet](sal_Int32 conv, TransliterationFlags flag)
    {
        if ((nConversion & conv) != conv)
            return false;

        aTranslitSet.push_back(flag);
        nConversion &= ~conv;
        return true;
    };

    // Check mutually exclusive bits together

    if (!check(ooo::vba::VbStrConv::vbProperCase, TransliterationFlags::TITLE_CASE))
        if (!check(ooo::vba::VbStrConv::vbUpperCase, TransliterationFlags::LOWERCASE_UPPERCASE))
            check(ooo::vba::VbStrConv::vbLowerCase, TransliterationFlags::UPPERCASE_LOWERCASE);

    if (!check(ooo::vba::VbStrConv::vbWide, TransliterationFlags::HALFWIDTH_FULLWIDTH))
        check(ooo::vba::VbStrConv::vbNarrow, TransliterationFlags::FULLWIDTH_HALFWIDTH);

    if (!check(ooo::vba::VbStrConv::vbKatakana, TransliterationFlags::HIRAGANA_KATAKANA))
        check(ooo::vba::VbStrConv::vbHiragana, TransliterationFlags::KATAKANA_HIRAGANA);

    if (nConversion) // unknown / incorrectly combined bits
        return StarBASIC::Error(ERRCODE_BASIC_BAD_ARGUMENT);

    OUString aStr = rPar.Get(1)->GetOUString();
    if (!aStr.isEmpty() && !aTranslitSet.empty())
    {
        const uno::Reference< uno::XComponentContext >& xContext = getProcessComponentContext();

        for (auto transliterationFlag : aTranslitSet)
        {
            if (transliterationFlag == TransliterationFlags::TITLE_CASE)
            {
                // TransliterationWrapper only handles the first character of the passed string
                // when handling TITLE_CASE; see Transliteration_titlecase::transliterateImpl in
                // i18npool/source/transliteration/transliteration_body.cxx
                CharClass aCharClass{ xContext, LanguageTag(nLanguage) };
                aStr = aCharClass.titlecase(aCharClass.lowercase(aStr));
            }
            else
            {
                utl::TransliterationWrapper aWrapper(xContext, transliterationFlag);
                aStr = aWrapper.transliterate(aStr, nLanguage, 0, aStr.getLength(), nullptr);
            }
        }
    }

    rPar.Get(0)->PutString(aStr);
}


void SbRtl_Beep(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 1)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    Sound::Beep();
}

void SbRtl_Load(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }


    SbxBase* pObj = rPar.Get(1)->GetObject();
    if ( !pObj )
        return;

    if (SbUserFormModule* pModule = dynamic_cast<SbUserFormModule*>(pObj))
    {
        pModule->Load();
    }
    else if (SbxObject* pSbxObj = dynamic_cast<SbxObject*>(pObj))
    {
        SbxVariable* pVar = pSbxObj->Find(u"Load"_ustr, SbxClassType::Method);
        if( pVar )
        {
            pVar->GetInteger();
        }
    }
}

void SbRtl_Unload(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }


    SbxBase* pObj = rPar.Get(1)->GetObject();
    if ( !pObj )
        return;

    if (SbUserFormModule* pFormModule = dynamic_cast<SbUserFormModule*>(pObj))
    {
        pFormModule->Unload();
    }
    else if (SbxObject *pSbxObj = dynamic_cast<SbxObject*>(pObj))
    {
        SbxVariable* pVar = pSbxObj->Find(u"Unload"_ustr, SbxClassType::Method);
        if( pVar )
        {
            pVar->GetInteger();
        }
    }
}

void SbRtl_LoadPicture(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 2)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    OUString aFileURL = getFullPath(rPar.Get(1)->GetOUString());
    std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aFileURL, StreamMode::READ ));
    if( pStream )
    {
        Bitmap aBmp;
        ReadDIB(aBmp, *pStream, true);
        BitmapEx aBitmapEx(aBmp);
        Graphic aGraphic(aBitmapEx);

        SbxObjectRef xRef = new SbStdPicture;
        static_cast<SbStdPicture*>(xRef.get())->SetGraphic( aGraphic );
        rPar.Get(0)->PutObject(xRef.get());
    }
}

void SbRtl_SavePicture(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() != 3)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    SbxBase* pObj = rPar.Get(1)->GetObject();
    if (SbStdPicture *pPicture = dynamic_cast<SbStdPicture*>(pObj))
    {
        SvFileStream aOStream(rPar.Get(2)->GetOUString(), StreamMode::WRITE | StreamMode::TRUNC);
        const Graphic& aGraphic = pPicture->GetGraphic();
        TypeSerializer aSerializer(aOStream);
        aSerializer.writeGraphic(aGraphic);
    }
}

void SbRtl_MsgBox(StarBASIC *, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if( nArgCount < 2 || nArgCount > 6 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    // tdf#147529 - check for missing parameters
    if (IsMissing(rPar, 1))
    {
        return StarBASIC::Error(ERRCODE_BASIC_NOT_OPTIONAL);
    }

    // tdf#151012 - initialize optional parameters with their default values (number of buttons)
    sal_Int16 nType = GetOptionalIntegerParamOrDefault(rPar, 2, SbMB::OK);

    OUString aMsg = rPar.Get(1)->GetOUString();
    // tdf#151012 - initialize optional parameters with their default values (title of dialog box)
    OUString aTitle = GetOptionalOUStringParamOrDefault(rPar, 3, Application::GetDisplayName());

    sal_Int16 nDialogType = nType & (SbMB::ICONSTOP | SbMB::ICONQUESTION | SbMB::ICONINFORMATION);

    SolarMutexGuard aSolarGuard;
    weld::Widget* pParent = Application::GetDefDialogParent();

    VclMessageType eType = VclMessageType::Other;

    switch (nDialogType)
    {
        case SbMB::ICONSTOP:
            eType = VclMessageType::Error;
            break;
        case SbMB::ICONQUESTION:
            eType = VclMessageType::Question;
            break;
        case SbMB::ICONEXCLAMATION:
            eType = VclMessageType::Warning;
            break;
        case SbMB::ICONINFORMATION:
            eType = VclMessageType::Info;
            break;
    }

    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
                eType, VclButtonsType::NONE, aMsg, GetpApp()));

    std::vector<std::pair<StandardButtonType, sal_Int16>> buttons;
    switch (nType & 0x0F) // delete bits 4-16
    {
        case SbMB::OK:
        default:
            buttons.emplace_back(StandardButtonType::OK, SbMB::Response::OK);
            break;
        case SbMB::OKCANCEL:
            buttons.emplace_back(StandardButtonType::OK, SbMB::Response::OK);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
        case SbMB::ABORTRETRYIGNORE:
            buttons.emplace_back(StandardButtonType::Abort, SbMB::Response::ABORT);
            buttons.emplace_back(StandardButtonType::Retry, SbMB::Response::RETRY);
            buttons.emplace_back(StandardButtonType::Ignore, SbMB::Response::IGNORE);
            break;
        case SbMB::YESNOCANCEL:
            buttons.emplace_back(StandardButtonType::Yes, SbMB::Response::YES);
            buttons.emplace_back(StandardButtonType::No, SbMB::Response::NO);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
        case SbMB::YESNO:
            buttons.emplace_back(StandardButtonType::Yes, SbMB::Response::YES);
            buttons.emplace_back(StandardButtonType::No, SbMB::Response::NO);
            break;
        case SbMB::RETRYCANCEL:
            buttons.emplace_back(StandardButtonType::Retry, SbMB::Response::RETRY);
            buttons.emplace_back(StandardButtonType::Cancel, SbMB::Response::CANCEL);
            break;
    }

    for (auto [buttonType, buttonResponse] : buttons)
        xBox->add_button(GetStandardText(buttonType), buttonResponse);

    std::size_t default_button = 0;
    if (nType & SbMB::DEFBUTTON2)
        default_button = 1;
    else if (nType & SbMB::DEFBUTTON3)
        default_button = 2;
    xBox->set_default_response(buttons[std::min(default_button, buttons.size() - 1)].second);

    xBox->set_title(aTitle);
    sal_Int16 nRet = xBox->run();
    rPar.Get(0)->PutInteger(nRet);
}

void SbRtl_SetAttr(StarBASIC *, SbxArray & rPar, bool)
{
    rPar.Get(0)->PutEmpty();
    if (rPar.Count() == 3)
    {
        OUString aStr = rPar.Get(1)->GetOUString();
        sal_Int16 nFlags = rPar.Get(2)->GetInteger();

        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    bool bReadOnly = bool(nFlags & SbAttributes::READONLY);
                    xSFI->setReadOnly( aStr, bReadOnly );
                    bool bHidden = bool(nFlags & SbAttributes::HIDDEN);
                    xSFI->setHidden( aStr, bHidden );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}

void SbRtl_Reset(StarBASIC *, SbxArray &, bool)
{
    SbiIoSystem* pIO = GetSbData()->pInst->GetIoSystem();
    if (pIO)
    {
        pIO->CloseAll();
    }
}

void SbRtl_DumpAllObjects(StarBASIC * pBasic, SbxArray & rPar, bool)
{
    const sal_uInt32 nArgCount = rPar.Count();
    if( nArgCount < 2 || nArgCount > 3 )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
    else if( !pBasic )
    {
        StarBASIC::Error( ERRCODE_BASIC_INTERNAL_ERROR );
    }
    else
    {
        SbxObject* p = pBasic;
        while( p->GetParent() )
        {
            p = p->GetParent();
        }
        SvFileStream aStrm(rPar.Get(1)->GetOUString(),
                            StreamMode::WRITE | StreamMode::TRUNC );
        p->Dump(aStrm, rPar.Get(2)->GetBool());
        aStrm.Close();
        if( aStrm.GetError() != ERRCODE_NONE )
        {
            StarBASIC::Error( ERRCODE_BASIC_IO_ERROR );
        }
    }
}


void SbRtl_FileExists(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() == 2)
    {
        OUString aStr = rPar.Get(1)->GetOUString();
        bool bExists = false;

        if( hasUno() )
        {
            const uno::Reference< ucb::XSimpleFileAccess3 >& xSFI = getFileAccess();
            if( xSFI.is() )
            {
                try
                {
                    bExists = xSFI->exists( aStr );
                }
                catch(const Exception & )
                {
                    StarBASIC::Error( ERRCODE_IO_GENERAL );
                }
            }
        }
        else
        {
            DirectoryItem aItem;
            FileBase::RC nRet = DirectoryItem::get( getFullPath( aStr ), aItem );
            bExists = (nRet == FileBase::E_None);
        }
        rPar.Get(0)->PutBool(bExists);
    }
    else
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }
}

void SbRtl_Partition(StarBASIC *, SbxArray & rPar, bool)
{
    if (rPar.Count() != 5)
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    sal_Int32 nNumber = rPar.Get(1)->GetLong();
    sal_Int32 nStart = rPar.Get(2)->GetLong();
    sal_Int32 nStop = rPar.Get(3)->GetLong();
    sal_Int32 nInterval = rPar.Get(4)->GetLong();

    if( nStart < 0 || nStop <= nStart || nInterval < 1 )
    {
        return StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
    }

    // the Partition function inserts leading spaces before lowervalue and uppervalue
    // so that they both have the same number of characters as the string
    // representation of the value (Stop + 1). This ensures that if you use the output
    // of the Partition function with several values of Number, the resulting text
    // will be handled properly during any subsequent sort operation.

    // calculate the maximum number of characters before lowervalue and uppervalue
    OUString aBeforeStart = OUString::number( nStart - 1 );
    OUString aAfterStop = OUString::number( nStop + 1 );
    sal_Int32 nLen1 = aBeforeStart.getLength();
    sal_Int32 nLen2 = aAfterStop.getLength();
    sal_Int32 nLen = nLen1 >= nLen2 ? nLen1:nLen2;

    OUStringBuffer aRetStr( nLen * 2 + 1);
    OUString aLowerValue;
    OUString aUpperValue;
    if( nNumber < nStart )
    {
        aUpperValue = aBeforeStart;
    }
    else if( nNumber > nStop )
    {
        aLowerValue = aAfterStop;
    }
    else
    {
        sal_Int32 nLowerValue = nNumber;
        sal_Int32 nUpperValue = nLowerValue;
        if( nInterval > 1 )
        {
            nLowerValue = ((( nNumber - nStart ) / nInterval ) * nInterval ) + nStart;
            nUpperValue = nLowerValue + nInterval - 1;
        }
        aLowerValue = OUString::number( nLowerValue );
        aUpperValue = OUString::number( nUpperValue );
    }

    nLen1 = aLowerValue.getLength();
    nLen2 = aUpperValue.getLength();

    if( nLen > nLen1 )
    {
        // appending the leading spaces for the lowervalue
        for ( sal_Int32 i= nLen - nLen1; i > 0; --i )
        {
            aRetStr.append(" ");
        }
    }
    aRetStr.append( aLowerValue + ":");
    if( nLen > nLen2 )
    {
        // appending the leading spaces for the uppervalue
        for ( sal_Int32 i= nLen - nLen2; i > 0; --i )
        {
            aRetStr.append(" ");
        }
    }
    aRetStr.append( aUpperValue );
    rPar.Get(0)->PutString(aRetStr.makeStringAndClear());
}

#endif

sal_Int16 implGetDateYear( double aDate )
{
    Date aRefDate(1899'12'30);
    sal_Int32 nDays = static_cast<sal_Int32>(aDate);
    aRefDate.AddDays( nDays );
    sal_Int16 nRet = aRefDate.GetYear();
    return nRet;
}

bool implDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
        bool bUseTwoDigitYear, SbDateCorrection eCorr, double& rdRet )
{
    // XXX NOTE: For VBA years<0 are invalid and years in the range 0..29 and
    // 30..99 can not be input as they are 2-digit for 2000..2029 and
    // 1930..1999, VBA mode overrides bUseTwoDigitYear (as if that was always
    // true). For VBA years > 9999 are invalid.
    // For StarBASIC, if bUseTwoDigitYear==true then years in the range 0..99
    // can not be input as they are 2-digit for 1900..1999, years<0 are
    // accepted. If bUseTwoDigitYear==false then all years are accepted, but
    // year 0 is invalid (last day BCE -0001-12-31, first day CE 0001-01-01).
#if HAVE_FEATURE_SCRIPTING
    if ( (nYear < 0 || 9999 < nYear) && SbiRuntime::isVBAEnabled() )
    {
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
        return false;
    }
    else if ( nYear < 30 && SbiRuntime::isVBAEnabled() )
    {
        nYear += 2000;
    }
    else
#endif
    {
        if ( 0 <= nYear && nYear < 100 &&
#if HAVE_FEATURE_SCRIPTING
                                          (bUseTwoDigitYear || SbiRuntime::isVBAEnabled())
#else
                                          bUseTwoDigitYear
#endif
           )
        {
            nYear += 1900;
        }
    }

    sal_Int32 nAddMonths = 0;
    sal_Int32 nAddDays = 0;
    // Always sanitize values to set date and to use for validity detection.
    if (nMonth < 1 || 12 < nMonth)
    {
        sal_Int16 nM = ((nMonth < 1) ? (12 + (nMonth % 12)) : (nMonth % 12));
        nAddMonths = nMonth - nM;
        nMonth = nM;
    }
    // Day 0 would already be normalized during Date::Normalize(), include
    // it in negative days, also to detect non-validity. The actual day of
    // month is 1+(nDay-1)
    if (nDay < 1)
    {
        nAddDays = nDay - 1;
        nDay = 1;
    }
    else if (nDay > 31)
    {
        nAddDays = nDay - 31;
        nDay = 31;
    }

    Date aCurDate( nDay, nMonth, nYear );

    /* TODO: we could enable the same rollover mechanism for StarBASIC to be
     * compatible with VBA (just with our wider supported date range), then
     * documentation would need to be adapted. As is, the DateSerial() runtime
     * function works as dumb as documented... (except that the resulting date
     * is checked for validity now and not just day<=31 and month<=12).
     * If change wanted then simply remove overriding RollOver here and adapt
     * documentation.*/
#if HAVE_FEATURE_SCRIPTING
    if (eCorr == SbDateCorrection::RollOver && !SbiRuntime::isVBAEnabled())
        eCorr = SbDateCorrection::None;
#endif

    if (nYear == 0 || (eCorr == SbDateCorrection::None && (nAddMonths || nAddDays || !aCurDate.IsValidDate())))
    {
#if HAVE_FEATURE_SCRIPTING
        StarBASIC::Error( ERRCODE_BASIC_BAD_ARGUMENT );
#endif
        return false;
    }

    if (eCorr != SbDateCorrection::None)
    {
        aCurDate.Normalize();
        if (nAddMonths)
            aCurDate.AddMonths( nAddMonths);
        if (nAddDays)
            aCurDate.AddDays( nAddDays);
        if (eCorr == SbDateCorrection::TruncateToMonth && aCurDate.GetMonth() != nMonth)
        {
            if (aCurDate.GetYear() == SAL_MAX_INT16 && nMonth == 12)
            {
                // Roll over and back not possible, hard max.
                aCurDate.SetMonth(12);
                aCurDate.SetDay(31);
            }
            else
            {
                aCurDate.SetMonth(nMonth);
                aCurDate.SetDay(1);
                aCurDate.AddMonths(1);
                aCurDate.AddDays(-1);
            }
        }
    }

    rdRet = GetDayDiff(aCurDate);
    return true;
}

double implTimeSerial(sal_Int16 nHours, sal_Int16 nMinutes, sal_Int16 nSeconds,
                      sal_Int32 nMilliSeconds)
{
    return (nHours * ::tools::Time::milliSecPerHour + nMinutes * ::tools::Time::milliSecPerMinute
            + nSeconds * ::tools::Time::milliSecPerSec + nMilliSeconds)
           / static_cast<double>(::tools::Time::milliSecPerDay);
}

bool implDateTimeSerial(sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, sal_Int16 nHour,
                        sal_Int16 nMinute, sal_Int16 nSecond, sal_Int32 nMilliSecond, double& rdRet)
{
    double dDate;
    if(!implDateSerial(nYear, nMonth, nDay, false/*bUseTwoDigitYear*/, SbDateCorrection::None, dDate))
        return false;
    rdRet += dDate + implTimeSerial(nHour, nMinute, nSecond, nMilliSecond);
    return true;
}

sal_Int16 implGetMinute( double dDate )
{
    double nFrac = (dDate - floor(dDate)) * ::tools::Time::milliSecPerDay;
    sal_uInt64 nMilliSeconds = static_cast<sal_uInt64>(nFrac + 0.5);
    return static_cast<sal_Int16>((nMilliSeconds / ::tools::Time::milliSecPerMinute)
                                  % ::tools::Time::minutePerHour);
}

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