/* -*- 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 <svl/numformat.hxx>
#include <rtl/math.hxx>
#include <o3tl/float_int_conversion.hxx>
#include <osl/diagnose.h>
#include <osl/thread.h>

#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Sequence.hxx>
#include <comphelper/string.hxx>
#include <rangeseq.hxx>
#include <document.hxx>
#include <dociter.hxx>
#include <scmatrix.hxx>
#include <formulacell.hxx>

using namespace com::sun::star;

static bool lcl_HasErrors( ScDocument& rDoc, const ScRange& rRange )
{
    // no need to look at empty cells - just use ScCellIterator
    ScCellIterator aIter( rDoc, rRange );
    for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
    {
        if (aIter.getType() != CELLTYPE_FORMULA)
            continue;

        ScFormulaCell* pCell = aIter.getFormulaCell();
        if (pCell->GetErrCode() != FormulaError::NONE)
            return true;
    }
    return false;   // no error found
}

static tools::Long lcl_DoubleToLong( double fVal )
{
    double fInt = (fVal >= 0.0) ? ::rtl::math::approxFloor( fVal ) :
                                  ::rtl::math::approxCeil( fVal );
    if ( o3tl::convertsToAtLeast(fInt, LONG_MIN) && o3tl::convertsToAtMost(fInt, LONG_MAX) )
        return static_cast<tools::Long>(fInt);
    else
        return 0;       // out of range
}

bool ScRangeToSequence::FillLongArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange )
{
    SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col();
    sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row();

    uno::Sequence< uno::Sequence<sal_Int32> > aRowSeq( nRowCount );
    uno::Sequence<sal_Int32>* pRowAry = aRowSeq.getArray();
    for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<sal_Int32> aColSeq( nColCount );
        sal_Int32* pColAry = aColSeq.getArray();
        for (sal_Int32 nCol = 0; nCol < nColCount; nCol++)
            pColAry[nCol] = lcl_DoubleToLong( rDoc.GetValue(
                ScAddress( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab ) ) );

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return !lcl_HasErrors( rDoc, rRange );
}

bool ScRangeToSequence::FillLongArray( uno::Any& rAny, const ScMatrix* pMatrix )
{
    if (!pMatrix)
        return false;

    SCSIZE nColCount;
    SCSIZE nRowCount;
    pMatrix->GetDimensions( nColCount, nRowCount );

    uno::Sequence< uno::Sequence<sal_Int32> > aRowSeq( static_cast<sal_Int32>(nRowCount) );
    uno::Sequence<sal_Int32>* pRowAry = aRowSeq.getArray();
    for (SCSIZE nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<sal_Int32> aColSeq( static_cast<sal_Int32>(nColCount) );
        sal_Int32* pColAry = aColSeq.getArray();
        for (SCSIZE nCol = 0; nCol < nColCount; nCol++)
            if ( pMatrix->IsStringOrEmpty( nCol, nRow ) )
                pColAry[nCol] = 0;
            else
                pColAry[nCol] = lcl_DoubleToLong( pMatrix->GetDouble( nCol, nRow ) );

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return true;
}

bool ScRangeToSequence::FillDoubleArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange )
{
    SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col();
    sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row();

    uno::Sequence< uno::Sequence<double> > aRowSeq( nRowCount );
    uno::Sequence<double>* pRowAry = aRowSeq.getArray();
    for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<double> aColSeq( nColCount );
        double* pColAry = aColSeq.getArray();
        for (sal_Int32 nCol = 0; nCol < nColCount; nCol++)
            pColAry[nCol] = rDoc.GetValue(
                ScAddress( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab ) );

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return !lcl_HasErrors( rDoc, rRange );
}

bool ScRangeToSequence::FillDoubleArray( uno::Any& rAny, const ScMatrix* pMatrix )
{
    if (!pMatrix)
        return false;

    SCSIZE nColCount;
    SCSIZE nRowCount;
    pMatrix->GetDimensions( nColCount, nRowCount );

    uno::Sequence< uno::Sequence<double> > aRowSeq( static_cast<sal_Int32>(nRowCount) );
    uno::Sequence<double>* pRowAry = aRowSeq.getArray();
    for (SCSIZE nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<double> aColSeq( static_cast<sal_Int32>(nColCount) );
        double* pColAry = aColSeq.getArray();
        for (SCSIZE nCol = 0; nCol < nColCount; nCol++)
            if ( pMatrix->IsStringOrEmpty( nCol, nRow ) )
                pColAry[nCol] = 0.0;
            else
                pColAry[nCol] = pMatrix->GetDouble( nCol, nRow );

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return true;
}

bool ScRangeToSequence::FillStringArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange )
{
    SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col();
    sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row();

    bool bHasErrors = false;

    uno::Sequence< uno::Sequence<OUString> > aRowSeq( nRowCount );
    uno::Sequence<OUString>* pRowAry = aRowSeq.getArray();
    for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<OUString> aColSeq( nColCount );
        OUString* pColAry = aColSeq.getArray();
        for (sal_Int32 nCol = 0; nCol < nColCount; nCol++)
        {
            FormulaError nErrCode = rDoc.GetStringForFormula(
                        ScAddress(static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab),
                        pColAry[nCol] );
            if ( nErrCode != FormulaError::NONE )
                bHasErrors = true;
        }
        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return !bHasErrors;
}

bool ScRangeToSequence::FillStringArray(uno::Any& rAny, const ScMatrix* pMatrix, const ScInterpreterContext& rContext)
{
    if (!pMatrix)
        return false;

    SCSIZE nColCount;
    SCSIZE nRowCount;
    pMatrix->GetDimensions( nColCount, nRowCount );

    uno::Sequence< uno::Sequence<OUString> > aRowSeq( static_cast<sal_Int32>(nRowCount) );
    uno::Sequence<OUString>* pRowAry = aRowSeq.getArray();
    for (SCSIZE nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<OUString> aColSeq( static_cast<sal_Int32>(nColCount) );
        OUString* pColAry = aColSeq.getArray();
        for (SCSIZE nCol = 0; nCol < nColCount; nCol++)
        {
            OUString aStr;
            if ( pMatrix->IsStringOrEmpty( nCol, nRow ) )
            {
                if ( !pMatrix->IsEmpty( nCol, nRow ) )
                    aStr = pMatrix->GetString(nCol, nRow).getString();
            }
            else
            {
                double fVal = pMatrix->GetDouble( nCol, nRow );
                const Color* pColor;
                rContext.NFGetOutputString( fVal, 0, aStr, &pColor );
            }
            pColAry[nCol] = aStr;
        }

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return true;
}

bool ScRangeToSequence::FillMixedArray( uno::Any& rAny, ScDocument& rDoc, const ScRange& rRange,
                                        bool bAllowNV )
{
    SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    sal_Int32 nColCount = rRange.aEnd.Col() + 1 - rRange.aStart.Col();
    sal_Int32 nRowCount = rRange.aEnd.Row() + 1 - rRange.aStart.Row();

    bool bHasErrors = false;

    uno::Sequence< uno::Sequence<uno::Any> > aRowSeq( nRowCount );
    uno::Sequence<uno::Any>* pRowAry = aRowSeq.getArray();
    for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<uno::Any> aColSeq( nColCount );
        uno::Any* pColAry = aColSeq.getArray();
        for (sal_Int32 nCol = 0; nCol < nColCount; nCol++)
        {
            uno::Any& rElement = pColAry[nCol];

            ScAddress aPos( static_cast<SCCOL>(nStartCol+nCol), static_cast<SCROW>(nStartRow+nRow), nTab );
            ScRefCellValue aCell(rDoc, aPos);

            if (aCell.isEmpty())
            {
                rElement <<= OUString();
                continue;
            }

            if (aCell.getType() == CELLTYPE_FORMULA && aCell.getFormula()->GetErrCode() != FormulaError::NONE)
            {
                // if NV is allowed, leave empty for errors
                bHasErrors = true;
            }
            else if (aCell.hasNumeric())
                rElement <<= aCell.getValue();
            else
                rElement <<= aCell.getString(rDoc);
        }
        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return bAllowNV || !bHasErrors;
}

bool ScRangeToSequence::FillMixedArray( uno::Any& rAny, const ScMatrix* pMatrix, bool bDataTypes )
{
    if (!pMatrix)
        return false;

    SCSIZE nColCount;
    SCSIZE nRowCount;
    pMatrix->GetDimensions( nColCount, nRowCount );

    uno::Sequence< uno::Sequence<uno::Any> > aRowSeq( static_cast<sal_Int32>(nRowCount) );
    uno::Sequence<uno::Any>* pRowAry = aRowSeq.getArray();
    for (SCSIZE nRow = 0; nRow < nRowCount; nRow++)
    {
        uno::Sequence<uno::Any> aColSeq( static_cast<sal_Int32>(nColCount) );
        uno::Any* pColAry = aColSeq.getArray();
        for (SCSIZE nCol = 0; nCol < nColCount; nCol++)
        {
            if ( pMatrix->IsStringOrEmpty( nCol, nRow ) )
            {
                OUString aStr;
                if ( !pMatrix->IsEmpty( nCol, nRow ) )
                    aStr = pMatrix->GetString(nCol, nRow).getString();
                pColAry[nCol] <<= aStr;
            }
            else
            {
                double fVal = pMatrix->GetDouble( nCol, nRow );
                if (bDataTypes && pMatrix->IsBoolean( nCol, nRow ))
                    pColAry[nCol] <<= fVal != 0.0;
                else
                    pColAry[nCol] <<= fVal;
            }
        }

        pRowAry[nRow] = std::move(aColSeq);
    }

    rAny <<= aRowSeq;
    return true;
}

bool ScApiTypeConversion::ConvertAnyToDouble( double & o_fVal,
        css::uno::TypeClass & o_eClass,
        const css::uno::Any & rAny )
{
    bool bRet = false;
    o_eClass = rAny.getValueTypeClass();
    switch (o_eClass)
    {
        //TODO: extract integer values
        case uno::TypeClass_ENUM:
        case uno::TypeClass_BOOLEAN:
        case uno::TypeClass_CHAR:
        case uno::TypeClass_BYTE:
        case uno::TypeClass_SHORT:
        case uno::TypeClass_UNSIGNED_SHORT:
        case uno::TypeClass_LONG:
        case uno::TypeClass_UNSIGNED_LONG:
        case uno::TypeClass_FLOAT:
        case uno::TypeClass_DOUBLE:
            rAny >>= o_fVal;
            bRet = true;
            break;
        default:
            ;   // nothing, avoid warning
    }
    if (!bRet)
        o_fVal = 0.0;
    return bRet;
}

ScMatrixRef ScSequenceToMatrix::CreateMixedMatrix( const css::uno::Any & rAny )
{
    ScMatrixRef xMatrix;
    uno::Sequence< uno::Sequence< uno::Any > > aSequence;
    if ( rAny >>= aSequence )
    {
        sal_Int32 nRowCount = aSequence.getLength();
        sal_Int32 nMaxColCount = 0;
        if (nRowCount)
        {
            auto pRow = std::max_element(std::cbegin(aSequence), std::cend(aSequence),
                [](const uno::Sequence<uno::Any>& a, const uno::Sequence<uno::Any>& b) {
                    return a.getLength() < b.getLength(); });
            nMaxColCount = pRow->getLength();
        }
        if ( nMaxColCount && nRowCount )
        {
            const uno::Sequence<uno::Any>* pRowArr = aSequence.getConstArray();
            OUString aUStr;
            xMatrix = new ScMatrix(
                    static_cast<SCSIZE>(nMaxColCount),
                    static_cast<SCSIZE>(nRowCount), 0.0);
            SCSIZE nCols, nRows;
            xMatrix->GetDimensions( nCols, nRows);
            if (nCols != static_cast<SCSIZE>(nMaxColCount) || nRows != static_cast<SCSIZE>(nRowCount))
            {
                OSL_FAIL( "ScSequenceToMatrix::CreateMixedMatrix: matrix exceeded max size, returning NULL matrix");
                return nullptr;
            }
            for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
            {
                sal_Int32 nColCount = pRowArr[nRow].getLength();
                const uno::Any* pColArr = pRowArr[nRow].getConstArray();
                for (sal_Int32 nCol=0; nCol<nColCount; nCol++)
                {
                    double fVal;
                    uno::TypeClass eClass;
                    if (ScApiTypeConversion::ConvertAnyToDouble( fVal, eClass, pColArr[nCol]))
                    {
                        if (eClass == uno::TypeClass_BOOLEAN)
                            xMatrix->PutBoolean( fVal != 0.0,
                                    static_cast<SCSIZE>(nCol),
                                    static_cast<SCSIZE>(nRow) );
                        else
                            xMatrix->PutDouble( fVal,
                                    static_cast<SCSIZE>(nCol),
                                    static_cast<SCSIZE>(nRow) );
                    }
                    else
                    {
                        // Try string, else use empty as last resort.

                        if ( pColArr[nCol] >>= aUStr )
                        {
                            xMatrix->PutString(
                                svl::SharedString(aUStr), static_cast<SCSIZE>(nCol), static_cast<SCSIZE>(nRow));
                        }
                        else
                            xMatrix->PutEmpty(
                                    static_cast<SCSIZE>(nCol),
                                    static_cast<SCSIZE>(nRow) );
                    }
                }
                for (sal_Int32 nCol=nColCount; nCol<nMaxColCount; nCol++)
                {
                    xMatrix->PutEmpty(
                            static_cast<SCSIZE>(nCol),
                            static_cast<SCSIZE>(nRow) );
                }
            }
        }
    }
    return xMatrix;
}

bool ScByteSequenceToString::GetString( OUString& rString, const uno::Any& rAny )
{
    bool bResult = false;
    if (rAny >>= rString)
    {
        bResult = true;
    }
    else if (uno::Sequence<sal_Int8> aSeq; rAny >>= aSeq)
    {
        rString = OUString( reinterpret_cast<const char*>(aSeq.getConstArray()),
                            aSeq.getLength(), osl_getThreadTextEncoding() );
        bResult = true;
    }
    if (bResult)
        rString = comphelper::string::stripEnd(rString, 0);
    return bResult;
}

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