/* -*- 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 <flat/ETable.hxx>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/ucb/XContentAccess.hpp>
#include <flat/EConnection.hxx>
#include <flat/EColumns.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <tools/urlobj.hxx>
#include <cppuhelper/queryinterface.hxx>
#include <comphelper/numbers.hxx>
#include <comphelper/servicehelper.hxx>
#include <com/sun/star/util/NumberFormat.hpp>
#include <com/sun/star/util/NumberFormatter.hpp>
#include <com/sun/star/util/NumberFormatsSupplier.hpp>
#include <i18nlangtag/languagetag.hxx>
#include <connectivity/dbconversion.hxx>
#include <connectivity/sdbcx/VColumn.hxx>
#include <file/quotedstring.hxx>
#include <file/FDriver.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/charclass.hxx>

using namespace ::comphelper;
using namespace connectivity;
using namespace connectivity::flat;
using namespace connectivity::file;
using namespace ::cppu;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::util;
using std::vector;
using std::lower_bound;


void OFlatTable::fillColumns(const css::lang::Locale& _aLocale)
{
    m_bNeedToReadLine = true; // we overwrite m_aCurrentLine, seek the stream, ...
    m_pFileStream->Seek(0);
    // tdf#123055 - start to read unicode text in order to avoid the BOM
    m_pFileStream->StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW);
    m_aCurrentLine = QuotedTokenizedString();
    bool bRead = true;

    const OFlatConnection* const pConnection = getFlatConnection();
    const bool bHasHeaderLine = pConnection->isHeaderLine();

    QuotedTokenizedString aHeaderLine;
    const sal_Int32 nPos = static_cast<sal_Int32>(m_pFileStream->Tell());
    TRowPositionInFile rowPos(nPos, nPos);
    sal_Int32 rowNum(0);
    if ( bHasHeaderLine )
    {
        bRead = readLine(&rowPos.second, &rowPos.first, true);
        if(bRead)
            aHeaderLine = m_aCurrentLine;
    }
    setRowPos(rowNum++, rowPos);

    // read first row
    if(bRead)
    {
        bRead = readLine(&rowPos.second, &rowPos.first);
        if(bRead)
            setRowPos(rowNum++, rowPos);
    }

    if ( !bHasHeaderLine || !aHeaderLine.Len())
    {
        // use first non-empty row as headerline because we need the number of columns
        while(bRead && m_aCurrentLine.Len() == 0)
        {
            bRead = readLine(&rowPos.second, &rowPos.first);
            if(bRead)
                setRowPos(rowNum++, rowPos);
        }
        aHeaderLine = m_aCurrentLine;
    }
    // column count
    const sal_Int32 nFieldCount = aHeaderLine.GetTokenCount(m_cFieldDelimiter,m_cStringDelimiter);

    if(!m_aColumns.is())
        m_aColumns = new OSQLColumns();
    else
        m_aColumns->clear();

    m_aTypes.clear();
    m_aPrecisions.clear();
    m_aScales.clear();
    // reserve some space
    m_aColumns->reserve(nFieldCount+1);
    m_aTypes.assign(nFieldCount+1,DataType::SQLNULL);
    m_aPrecisions.assign(nFieldCount+1,-1);
    m_aScales.assign(nFieldCount+1,-1);

    const bool bCase = m_pConnection->getMetaData()->supportsMixedCaseQuotedIdentifiers();
    CharClass aCharClass( pConnection->getDriver()->getComponentContext(), LanguageTag( _aLocale));
    // read description
    const sal_Unicode cDecimalDelimiter  = pConnection->getDecimalDelimiter();
    const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
    ::comphelper::UStringMixEqual aCase(bCase);
    vector<OUString> aColumnNames;
    vector<OUString> aTypeNames;
    aTypeNames.resize(nFieldCount);
    const sal_Int32 nMaxRowsToScan = pConnection->getMaxRowsToScan();
    sal_Int32 nRowCount = 0;

    do
    {
        sal_Int32 nStartPosHeaderLine = 0; // use for efficient way to get the tokens
        sal_Int32 nStartPosFirstLine = 0; // use for efficient way to get the tokens
        sal_Int32 nStartPosFirstLine2 = 0;
        for( sal_Int32 i = 0; i < nFieldCount; i++ )
        {
            if ( nRowCount == 0)
            {
                OUString aColumnName;
                if ( bHasHeaderLine )
                {
                    aColumnName = aHeaderLine.GetTokenSpecial(nStartPosHeaderLine,m_cFieldDelimiter,m_cStringDelimiter);
                }
                if ( aColumnName.isEmpty() )
                {
                    aColumnName = "C" + OUString::number(i+1);
                }
                aColumnNames.push_back(aColumnName);
            }
            if(bRead)
            {
                impl_fillColumnInfo_nothrow(m_aCurrentLine, nStartPosFirstLine, nStartPosFirstLine2,
                                            m_aTypes[i], m_aPrecisions[i], m_aScales[i], aTypeNames[i],
                                            cDecimalDelimiter, cThousandDelimiter, aCharClass);
            }
        }
        ++nRowCount;
        bRead = readLine(&rowPos.second, &rowPos.first);
        if(bRead)
            setRowPos(rowNum++, rowPos);
    }
    while(nRowCount < nMaxRowsToScan && bRead);

    for( sal_Int32 i = 0; i < nFieldCount; i++ )
    {
        // check if the columname already exists
        OUString aAlias(aColumnNames[i]);
        OSQLColumns::const_iterator aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
        sal_Int32 nExprCnt = 0;
        while(aFind != m_aColumns->end())
        {
            aAlias = aColumnNames[i] + OUString::number(++nExprCnt);
            aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
        }

        rtl::Reference<sdbcx::OColumn> pColumn = new sdbcx::OColumn(aAlias,aTypeNames[i],OUString(),OUString(),
                                                ColumnValue::NULLABLE,
                                                m_aPrecisions[i],
                                                m_aScales[i],
                                                m_aTypes[i],
                                                false,
                                                false,
                                                false,
                                                bCase,
                                                m_CatalogName, getSchema(), getName());
        m_aColumns->push_back(pColumn);
    }

    m_pFileStream->Seek(m_aRowPosToFilePos[0].second);
}

void OFlatTable::impl_fillColumnInfo_nothrow(QuotedTokenizedString const & aFirstLine, sal_Int32& nStartPosFirstLine, sal_Int32& nStartPosFirstLine2,
                                             sal_Int32& io_nType, sal_Int32& io_nPrecisions, sal_Int32& io_nScales, OUString& o_sTypeName,
                                             const sal_Unicode cDecimalDelimiter, const sal_Unicode cThousandDelimiter, const CharClass&  aCharClass)
{
    if ( io_nType != DataType::VARCHAR )
    {
        bool bNumeric = io_nType == DataType::SQLNULL || io_nType == DataType::DOUBLE || io_nType == DataType::DECIMAL || io_nType == DataType::INTEGER;
        sal_Int32 nIndex = 0;

        if ( bNumeric )
        {
            // first without fielddelimiter
            OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
            if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
            {
                bNumeric = false;
                if ( m_cStringDelimiter != '\0' )
                    aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    nStartPosFirstLine2 = nStartPosFirstLine;
            }
            else
            {
                OUString aField2;
                if ( m_cStringDelimiter != '\0' )
                    aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    aField2 = aField;

                if (aField2.isEmpty())
                {
                    bNumeric = false;
                }
                else
                {
                    sal_Int32 nDot = 0;
                    sal_Int32 nDecimalDelCount = 0;
                    sal_Int32 nSpaceCount = 0;
                    for( sal_Int32 j = 0; j < aField2.getLength(); j++ )
                    {
                        const sal_Unicode c = aField2[j];
                        if ( j == nSpaceCount && m_cFieldDelimiter != 32 && c == 32 )
                        {
                            ++nSpaceCount;
                            continue;
                        }
                        // just digits, decimal- and thousands-delimiter?
                        if ( ( !cDecimalDelimiter  || c != cDecimalDelimiter )  &&
                             ( !cThousandDelimiter || c != cThousandDelimiter ) &&
                            !aCharClass.isDigit(aField2,j)                      &&
                            ( j != 0 || (c != '+' && c != '-' ) ) )
                        {
                            bNumeric = false;
                            break;
                        }
                        if (cDecimalDelimiter && c == cDecimalDelimiter)
                        {
                            io_nPrecisions = 15; // we have a decimal value
                            io_nScales = 2;
                            ++nDecimalDelCount;
                        } // if (cDecimalDelimiter && c == cDecimalDelimiter)
                        if ( c == '.' )
                            ++nDot;
                    }

                    if (nDecimalDelCount > 1 || nDot > 1 ) // if there is more than one dot it isn't a number
                        bNumeric = false;
                    if (bNumeric && cThousandDelimiter)
                    {
                        // Is the delimiter correct?
                        const std::u16string_view aValue = o3tl::getToken(aField2, 0, cDecimalDelimiter);
                        for( sal_Int32 j = static_cast<sal_Int32>(aValue.size()) - 4; j >= 0; j -= 4)
                        {
                            const sal_Unicode c = aValue[j];
                            // just digits, decimal- and thousands-delimiter?
                            if (c == cThousandDelimiter && j)
                                continue;
                            else
                            {
                                bNumeric = false;
                                break;
                            }
                        }
                    }

                    // now also check for a date field
                    if (!bNumeric)
                    {
                        try
                        {
                            nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
                        }
                        catch(Exception&)
                        {
                        }
                    }
                }
            }
        }
        else if ( io_nType == DataType::DATE || io_nType == DataType::TIMESTAMP || io_nType == DataType::TIME)
        {
            OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
            if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
            {
            }
            else
            {
                OUString aField2;
                if ( m_cStringDelimiter != '\0' )
                    aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
                else
                    aField2 = aField;
                if (!aField2.isEmpty() )
                {
                    try
                    {
                        nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
                    }
                    catch(Exception&)
                    {
                    }
                }
            }
        }

        if (bNumeric)
        {
            if (cDecimalDelimiter)
            {
                if(io_nPrecisions)
                {
                    io_nType = DataType::DECIMAL;
                    o_sTypeName = "DECIMAL";
                }
                else
                {
                    io_nType = DataType::DOUBLE;
                    o_sTypeName = "DOUBLE";
                }
            }
            else
            {
                io_nType = DataType::INTEGER;
                io_nPrecisions = 0;
                io_nScales = 0;
            }
        }
        else
        {
            switch (comphelper::getNumberFormatType(m_xNumberFormatter,nIndex))
            {
                case css::util::NumberFormat::DATE:
                    io_nType = DataType::DATE;
                    o_sTypeName = "DATE";
                    break;
                case css::util::NumberFormat::DATETIME:
                    io_nType = DataType::TIMESTAMP;
                    o_sTypeName = "TIMESTAMP";
                    break;
                case css::util::NumberFormat::TIME:
                    io_nType = DataType::TIME;
                    o_sTypeName = "TIME";
                    break;
                default:
                    io_nType = DataType::VARCHAR;
                    io_nPrecisions = 0; // nyi: Data can be longer!
                    io_nScales = 0;
                    o_sTypeName = "VARCHAR";
            };
        }
    }
    else
    {
        OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
        if (aField.isEmpty() ||
                (m_cStringDelimiter && m_cStringDelimiter == aField[0]))
        {
            if ( m_cStringDelimiter != '\0' )
                aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
            else
                nStartPosFirstLine2 = nStartPosFirstLine;
        }
        else
        {
            if ( m_cStringDelimiter != '\0' )
                aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
        }
    }
}

OFlatTable::OFlatTable(sdbcx::OCollection* _pTables,OFlatConnection* _pConnection,
                    const OUString& Name,
                    const OUString& Type,
                    const OUString& Description ,
                    const OUString& SchemaName,
                    const OUString& CatalogName
                ) : OFlatTable_BASE(_pTables,_pConnection,Name,
                                  Type,
                                  Description,
                                  SchemaName,
                                  CatalogName)
    ,m_nRowPos(0)
    ,m_nMaxRowCount(0)
    ,m_cStringDelimiter(_pConnection->getStringDelimiter())
    ,m_cFieldDelimiter(_pConnection->getFieldDelimiter())
    ,m_bNeedToReadLine(false)
{

}

void OFlatTable::construct()
{
    SvtSysLocale aLocale;
    css::lang::Locale aAppLocale(aLocale.GetLanguageTag().getLocale());

    Reference< XNumberFormatsSupplier > xSupplier = NumberFormatsSupplier::createWithLocale( m_pConnection->getDriver()->getComponentContext(), aAppLocale );
    m_xNumberFormatter.set( NumberFormatter::create( m_pConnection->getDriver()->getComponentContext()), UNO_QUERY_THROW);
    m_xNumberFormatter->attachNumberFormatsSupplier(xSupplier);
    Reference<XPropertySet> xProp = xSupplier->getNumberFormatSettings();
    xProp->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;

    INetURLObject aURL;
    aURL.SetURL(getEntry());

    if(aURL.getExtension() != m_pConnection->getExtension())
        aURL.setExtension(m_pConnection->getExtension());

    OUString aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);

    m_pFileStream = createStream_simpleError( aFileName, StreamMode::READWRITE | StreamMode::NOCREATE | StreamMode::SHARE_DENYWRITE);

    if(!m_pFileStream)
        m_pFileStream = createStream_simpleError( aFileName, StreamMode::READ | StreamMode::NOCREATE | StreamMode::SHARE_DENYNONE);

    if(!m_pFileStream)
        return;

    sal_uInt64 const nSize = m_pFileStream->remainingSize();

    // Buffersize is dependent on the file-size
    m_pFileStream->SetBufferSize(nSize > 1000000 ? 32768 :
                                nSize > 100000  ? 16384 :
                                nSize > 10000   ? 4096  : 1024);

    fillColumns(aAppLocale);

    refreshColumns();
}

OUString OFlatTable::getEntry() const
{
    OUString sURL;
    try
    {
        Reference< XResultSet > xDir = m_pConnection->getDir()->getStaticResultSet();
        Reference< XRow> xRow(xDir,UNO_QUERY);
        OUString sName;
        OUString sExt;

        INetURLObject aURL;
        xDir->beforeFirst();
        while(xDir->next())
        {
            sName = xRow->getString(1);
            aURL.SetSmartProtocol(INetProtocol::File);
            OUString sUrl = m_pConnection->getURL() + "/" + sName;
            aURL.SetSmartURL( sUrl );

            // cut the extension
            sExt = aURL.getExtension();

            // name and extension have to coincide
            if ( m_pConnection->matchesExtension( sExt ) )
            {
                if ( !sExt.isEmpty() )
                    sName = sName.replaceAt(sName.getLength() - (sExt.getLength() + 1), sExt.getLength()+1, u"");
                if ( sName == m_Name )
                {
                    Reference< XContentAccess > xContentAccess( xDir, UNO_QUERY );
                    sURL = xContentAccess->queryContentIdentifierString();
                    break;
                }
            }
        }
        xDir->beforeFirst(); // move back to before first record
    }
    catch(const Exception&)
    {
        OSL_ASSERT(false);
    }
    return sURL;
}

void OFlatTable::refreshColumns()
{
    ::osl::MutexGuard aGuard( m_aMutex );

    ::std::vector< OUString> aVector;
    aVector.reserve(m_aColumns->size());

    for (auto const& column : *m_aColumns)
        aVector.push_back(Reference< XNamed>(column,UNO_QUERY_THROW)->getName());

    if(m_xColumns)
        m_xColumns->reFill(aVector);
    else
        m_xColumns.reset(new OFlatColumns(this,m_aMutex,aVector));
}


void SAL_CALL OFlatTable::disposing()
{
    OFileTable::disposing();
    ::osl::MutexGuard aGuard(m_aMutex);
    m_aColumns = nullptr;
}

Sequence< Type > SAL_CALL OFlatTable::getTypes(  )
{
    Sequence< Type > aTypes = OTable_TYPEDEF::getTypes();
    vector<Type> aOwnTypes;
    aOwnTypes.reserve(aTypes.getLength());
    const Type* pBegin = aTypes.getConstArray();
    const Type* pEnd = pBegin + aTypes.getLength();
    for(;pBegin != pEnd;++pBegin)
    {
        if(!(*pBegin == cppu::UnoType<XKeysSupplier>::get()||
            *pBegin == cppu::UnoType<XRename>::get()||
            *pBegin == cppu::UnoType<XIndexesSupplier>::get()||
            *pBegin == cppu::UnoType<XAlterTable>::get()||
            *pBegin == cppu::UnoType<XDataDescriptorFactory>::get()))
        {
            aOwnTypes.push_back(*pBegin);
        }
    }
    return Sequence< Type >(aOwnTypes.data(), aOwnTypes.size());
}


Any SAL_CALL OFlatTable::queryInterface( const Type & rType )
{
    if( rType == cppu::UnoType<XKeysSupplier>::get()||
        rType == cppu::UnoType<XIndexesSupplier>::get()||
        rType == cppu::UnoType<XRename>::get()||
        rType == cppu::UnoType<XAlterTable>::get()||
        rType == cppu::UnoType<XDataDescriptorFactory>::get())
        return Any();

    Any aRet = OTable_TYPEDEF::queryInterface(rType);
    return aRet;
}


bool OFlatTable::fetchRow(OValueRefRow& _rRow, const OSQLColumns & _rCols, bool bRetrieveData)
{
    *(*_rRow)[0] = m_nFilePos;

    if (!bRetrieveData)
        return true;

    bool result = false;
    if ( m_bNeedToReadLine )
    {
        m_pFileStream->Seek(m_nFilePos);
        TRowPositionInFile rowPos(0, 0);
        if(readLine(&rowPos.second, &rowPos.first))
        {
            setRowPos(m_nRowPos, rowPos);
            m_bNeedToReadLine = false;
            result = true;
        }
        // else let run through so that we set _rRow to all NULL
    }

    const OFlatConnection * const pConnection = getFlatConnection();
    const sal_Unicode cDecimalDelimiter = pConnection->getDecimalDelimiter();
    const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
    // Fields:
    sal_Int32 nStartPos = 0;
    OSQLColumns::const_iterator aIter = _rCols.begin();
    OSQLColumns::const_iterator aEnd = _rCols.end();
    const OValueRefVector::size_type nCount = _rRow->size();
    for (OValueRefVector::size_type i = 1;
         aIter != aEnd && i < nCount;
         ++aIter, i++)
    {
        OUString aStr = m_aCurrentLine.GetTokenSpecial(nStartPos,m_cFieldDelimiter,m_cStringDelimiter);

        if (aStr.isEmpty())
        {
            (*_rRow)[i]->setNull();
        }
        else
        {
            sal_Int32 nType   = m_aTypes[i-1];
            switch(nType)
            {
                case DataType::TIMESTAMP:
                case DataType::DATE:
                case DataType::TIME:
                {
                    try
                    {
                        double nRes = m_xNumberFormatter->convertStringToNumber(css::util::NumberFormat::ALL,aStr);

                        switch(nType)
                        {
                            case DataType::DATE:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDate(nRes,m_aNullDate));
                                break;
                            case DataType::TIMESTAMP:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDateTime(nRes,m_aNullDate));
                                break;
                            default:
                                *(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toTime(nRes));
                        }
                    }
                    catch(Exception&)
                    {
                        (*_rRow)[i]->setNull();
                    }
                }   break;
                case DataType::DOUBLE:
                case DataType::INTEGER:
                case DataType::DECIMAL:
                case DataType::NUMERIC:
                {

                    OUString aStrConverted;
                    if ( DataType::INTEGER != nType )
                    {
                        OSL_ENSURE((cDecimalDelimiter && nType != DataType::INTEGER) ||
                                   (!cDecimalDelimiter && nType == DataType::INTEGER),
                                   "Wrong type");

                        OUStringBuffer aBuf(aStr.getLength());
                        // convert to Standard-Notation (DecimalPOINT without thousands-comma):
                        for (sal_Int32 j = 0; j < aStr.getLength(); ++j)
                        {
                            const sal_Unicode cChar = aStr[j];
                            if (cDecimalDelimiter && cChar == cDecimalDelimiter)
                                aBuf.append('.');
                            else if ( cChar == '.' ) // special case, if decimal separator isn't '.' we have to put the string after it
                                continue;
                            else if (cThousandDelimiter && cChar == cThousandDelimiter)
                            {
                                // leave out
                            }
                            else
                                aBuf.append(cChar);
                        } // for (j = 0; j < aStr.(); ++j)
                        aStrConverted = aBuf.makeStringAndClear();
                    } // if ( DataType::INTEGER != nType )
                    else
                    {
                        if ( cThousandDelimiter )
                            aStrConverted = aStr.replaceAll(OUStringChar(cThousandDelimiter), "");
                        else
                            aStrConverted = aStr;
                    }
                    const double nVal = ::rtl::math::stringToDouble(aStrConverted,'.',',');

                    // #99178# OJ
                    if ( DataType::DECIMAL == nType || DataType::NUMERIC == nType )
                        *(*_rRow)[i] = OUString::number(nVal);
                    else
                        *(*_rRow)[i] = nVal;
                } break;

                default:
                {
                    // Copy Value as String in Row-Variable
                    *(*_rRow)[i] = ORowSetValue(aStr);
                }
                break;
            } // switch(nType)
            (*_rRow)[i]->setTypeKind(nType);
        }
    }
    return result;
}


void OFlatTable::refreshHeader()
{
    SAL_INFO( "connectivity.flat", "flat lionel@mamane.lu OFlatTable::refreshHeader" );
}


namespace
{
    template< typename Tp, typename Te> struct RangeBefore
    {
        bool operator() (const Tp &p, const Te &e)
        {
            assert(p.first <= p.second);
            return p.second <= e;
        }
    };
}

bool OFlatTable::seekRow(IResultSetHelper::Movement eCursorPosition, sal_Int32 nOffset, sal_Int32& nCurPos)
{
    OSL_ENSURE(m_pFileStream,"OFlatTable::seekRow: FileStream is NULL!");


    switch(eCursorPosition)
    {
        case IResultSetHelper::FIRST:
            m_nRowPos = 0;
            [[fallthrough]];
        case IResultSetHelper::NEXT:
            {
                assert(m_nRowPos >= 0);
                if(m_nMaxRowCount != 0 && m_nRowPos > m_nMaxRowCount)
                    return false;
                ++m_nRowPos;
                if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos))
                {
                    m_bNeedToReadLine = true;
                    m_nFilePos  = m_aRowPosToFilePos[m_nRowPos].first;
                    nCurPos     = m_aRowPosToFilePos[m_nRowPos].second;
                }
                else
                {
                    assert(m_aRowPosToFilePos.size() == static_cast< vector< TRowPositionInFile >::size_type >(m_nRowPos));
                    const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
                    // Our ResultSet is allowed to disagree with us only
                    // on the position of the first line
                    // (because of the special case of the header...)
                    assert(m_nRowPos == 1 || nCurPos == lastRowPos.second);

                    m_nFilePos = lastRowPos.second;
                    m_pFileStream->Seek(m_nFilePos);

                    TRowPositionInFile newRowPos;
                    if(!readLine(&newRowPos.second, &newRowPos.first))
                    {
                        m_nMaxRowCount = m_nRowPos - 1;
                        return false;
                    }

                    nCurPos = newRowPos.second;
                    setRowPos(m_nRowPos, newRowPos);
                }
            }

            break;
        case IResultSetHelper::PRIOR:
            assert(m_nRowPos >= 0);

            if(m_nRowPos == 0)
                return false;

            --m_nRowPos;
            {
                assert (m_nRowPos >= 0);
                assert(m_aRowPosToFilePos.size() >= o3tl::make_unsigned(m_nRowPos));
                const TRowPositionInFile &aPositions(m_aRowPosToFilePos[m_nRowPos]);
                m_nFilePos = aPositions.first;
                nCurPos = aPositions.second;
                m_bNeedToReadLine = true;
            }

            break;
        case IResultSetHelper::LAST:
            if (m_nMaxRowCount == 0)
            {
                while(seekRow(IResultSetHelper::NEXT, 1, nCurPos)) ; // run through after last row
            }
            // m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
            return seekRow(IResultSetHelper::ABSOLUTE1, m_nMaxRowCount, nCurPos);
        case IResultSetHelper::RELATIVE1:
            {
                const sal_Int32 nNewRowPos = m_nRowPos + nOffset;
                if (nNewRowPos < 0)
                    return false;
                // ABSOLUTE will take care of case nNewRowPos > nMaxRowCount
                return seekRow(IResultSetHelper::ABSOLUTE1, nNewRowPos, nCurPos);
            }
        case IResultSetHelper::ABSOLUTE1:
            {
                if(nOffset < 0)
                {
                    if (m_nMaxRowCount == 0)
                    {
                        if (!seekRow(IResultSetHelper::LAST, 0, nCurPos))
                            return false;
                    }
                    // m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
                    nOffset = m_nMaxRowCount + nOffset;
                }
                if(nOffset < 0)
                {
                    seekRow(IResultSetHelper::ABSOLUTE1, 0, nCurPos);
                    return false;
                }
                if(m_nMaxRowCount && nOffset > m_nMaxRowCount)
                {
                    m_nRowPos = m_nMaxRowCount + 1;
                    const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
                    m_nFilePos = lastRowPos.second;
                    nCurPos = lastRowPos.second;
                    return false;
                }

                assert(m_nRowPos >=0);
                assert(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos));
                assert(nOffset >= 0);
                if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(nOffset))
                {
                    m_nFilePos  = m_aRowPosToFilePos[nOffset].first;
                    nCurPos     = m_aRowPosToFilePos[nOffset].second;
                    m_nRowPos   = nOffset;
                    m_bNeedToReadLine = true;
                }
                else
                {
                    assert(m_nRowPos < nOffset);
                    while(m_nRowPos < nOffset)
                    {
                        if(!seekRow(IResultSetHelper::NEXT, 1, nCurPos))
                            return false;
                    }
                    assert(m_nRowPos == nOffset);
                }
            }

            break;
        case IResultSetHelper::BOOKMARK:
            {
                vector< TRowPositionInFile >::const_iterator aFind = lower_bound(m_aRowPosToFilePos.begin(),
                                                                                 m_aRowPosToFilePos.end(),
                                                                                 nOffset,
                                                                                 RangeBefore< TRowPositionInFile, sal_Int32 >());

                if(aFind == m_aRowPosToFilePos.end() || aFind->first != nOffset)
                    //invalid bookmark
                    return false;

                m_bNeedToReadLine = true;
                m_nFilePos  = aFind->first;
                nCurPos     = aFind->second;
                m_nRowPos = aFind - m_aRowPosToFilePos.begin();
                break;
            }
    }

    return true;
}


bool OFlatTable::readLine(sal_Int32 * const pEndPos, sal_Int32 * const pStartPos, const bool nonEmpty)
{
    const rtl_TextEncoding nEncoding = m_pConnection->getTextEncoding();
    m_aCurrentLine = QuotedTokenizedString();
    do
    {
        if (pStartPos)
            *pStartPos = static_cast<sal_Int32>(m_pFileStream->Tell());
        m_pFileStream->ReadByteStringLine(m_aCurrentLine, nEncoding);
        if (m_pFileStream->eof())
            return false;

        QuotedTokenizedString sLine = m_aCurrentLine; // check if the string continues on next line
        sal_Int32 nLastOffset = 0;
        bool isQuoted = false;
        bool isFieldStarting = true;
        while (true)
        {
            bool wasQuote = false;
            const sal_Unicode *p = sLine.GetString().getStr() + nLastOffset;
            while (*p)
            {
                if (isQuoted)
                {
                    if (*p == m_cStringDelimiter)
                        wasQuote = !wasQuote;
                    else
                    {
                        if (wasQuote)
                        {
                            wasQuote = false;
                            isQuoted = false;
                            if (*p == m_cFieldDelimiter)
                                isFieldStarting = true;
                        }
                    }
                }
                else
                {
                    if (isFieldStarting)
                    {
                        isFieldStarting = false;
                        if (*p == m_cStringDelimiter)
                            isQuoted = true;
                        else if (*p == m_cFieldDelimiter)
                            isFieldStarting = true;
                    }
                    else if (*p == m_cFieldDelimiter)
                        isFieldStarting = true;
                }
                ++p;
            }

            if (wasQuote)
                isQuoted = false;

            if (isQuoted)
            {
                nLastOffset = sLine.Len();
                m_pFileStream->ReadByteStringLine(sLine,nEncoding);
                if ( !m_pFileStream->eof() )
                {
                    OUString aStr = m_aCurrentLine.GetString() + "\n" + sLine.GetString();
                    m_aCurrentLine.SetString(aStr);
                    sLine = m_aCurrentLine;
                }
                else
                    break;
            }
            else
                break;
        }
    }
    while(nonEmpty && m_aCurrentLine.Len() == 0);

    if(pEndPos)
        *pEndPos = static_cast<sal_Int32>(m_pFileStream->Tell());
    return true;
}


void OFlatTable::setRowPos(const vector<TRowPositionInFile>::size_type rowNum, const TRowPositionInFile &rowPos)
{
    assert(m_aRowPosToFilePos.size() >= rowNum);
    if(m_aRowPosToFilePos.size() == rowNum)
        m_aRowPosToFilePos.push_back(rowPos);
    else
    {
        SAL_WARN_IF(m_aRowPosToFilePos[rowNum] != rowPos,
                    "connectivity.flat",
                    "Setting position for row " << rowNum << " to (" << rowPos.first << ", " << rowPos.second << "), "
                    "but already had different position (" << m_aRowPosToFilePos[rowNum].first << ", " << m_aRowPosToFilePos[rowNum].second << ")");
        m_aRowPosToFilePos[rowNum] = rowPos;
    }
}

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