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

#include <dataprovider.hxx>
#include <com/sun/star/ucb/XSimpleFileAccess3.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <com/sun/star/io/XInputStream.hpp>
#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <tools/stream.hxx>
#include <comphelper/processfactory.hxx>

#include "htmldataprovider.hxx"
#include "xmldataprovider.hxx"
#include "sqldataprovider.hxx"
#include <datamapper.hxx>
#include <dbdata.hxx>
#include <docsh.hxx>
#include <utility>

using namespace com::sun::star;

namespace sc {

std::unique_ptr<SvStream> DataProvider::FetchStreamFromURL(const OUString& rURL, OStringBuffer& rBuffer)
{
    try
    {
        uno::Reference< ucb::XSimpleFileAccess3 > xFileAccess = ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() );

        uno::Reference< io::XInputStream > xStream = xFileAccess->openFileRead( rURL );

        const sal_Int32 BUF_LEN = 8000;
        uno::Sequence< sal_Int8 > buffer( BUF_LEN );

        sal_Int32 nRead = 0;
        while ( ( nRead = xStream->readBytes( buffer, BUF_LEN ) ) == BUF_LEN )
        {
            rBuffer.append( reinterpret_cast< const char* >( buffer.getConstArray() ), nRead );
        }

        if ( nRead > 0 )
        {
            rBuffer.append( reinterpret_cast< const char* >( buffer.getConstArray() ), nRead );
        }

        xStream->closeInput();

        SvStream* pStream = new SvMemoryStream(const_cast<char*>(rBuffer.getStr()), rBuffer.getLength(), StreamMode::READ);
        return std::unique_ptr<SvStream>(pStream);
    }
    catch(...)
    {
        rBuffer.setLength(0);
        return nullptr;
    }
}

ExternalDataSource::ExternalDataSource(OUString aURL,
        OUString aProvider, ScDocument* pDoc)
    : maURL(std::move(aURL))
    , maProvider(std::move(aProvider))
    , mpDoc(pDoc)
{
}

void ExternalDataSource::setID(const OUString& rID)
{
    maID = rID;
}

void ExternalDataSource::setXMLImportParam(const ScOrcusImportXMLParam& rParam)
{
    maParam = rParam;
}



void ExternalDataSource::setURL(const OUString& rURL)
{
    maURL = rURL;
}

void ExternalDataSource::setProvider(const OUString& rProvider)
{
    maProvider = rProvider;
    mpDataProvider.reset();
}

const OUString& ExternalDataSource::getURL() const
{
    return maURL;
}

const OUString& ExternalDataSource::getProvider() const
{
    return maProvider;
}

const OUString& ExternalDataSource::getID() const
{
    return maID;
}

const ScOrcusImportXMLParam& ExternalDataSource::getXMLImportParam() const
{
    return maParam;
}

OUString ExternalDataSource::getDBName() const
{
    if (mpDBDataManager)
    {
        ScDBData* pDBData = mpDBDataManager->getDBData();
        if (pDBData)
            return pDBData->GetName();
    }
    return OUString();
}

void ExternalDataSource::setDBData(const OUString& rDBName)
{
    if (!mpDBDataManager)
    {
        mpDBDataManager = std::make_shared<ScDBDataManager>(rDBName, mpDoc);
    }
    else
    {
        mpDBDataManager->SetDatabase(rDBName);
    }
}

double ExternalDataSource::getUpdateFrequency()
{
    return 0;
}

ScDBDataManager* ExternalDataSource::getDBManager()
{
    return mpDBDataManager.get();
}

void ExternalDataSource::refresh(ScDocument* pDoc, bool bDeterministic)
{
    // no DB data available
    if (!mpDBDataManager)
        return;

    // tdf#169541 An existing mpDataProvider might have a corrupted backreference to `this`.
    // Thus create a new one in any case.
    mpDataProvider = DataProviderFactory::getDataProvider(pDoc, *this);

    // if we have not been able to create one, we can not refresh the data
    if (!mpDataProvider)
        return;

    if (bDeterministic)
        mpDataProvider->setDeterministic();

    mpDataProvider->Import();
}

void ExternalDataSource::AddDataTransformation(
    const std::shared_ptr<sc::DataTransformation>& mpDataTransformation)
{
    maDataTransformations.push_back(mpDataTransformation);
}

const std::vector<std::shared_ptr<sc::DataTransformation>>& ExternalDataSource::getDataTransformation() const
{
    return maDataTransformations;
}

ExternalDataMapper::ExternalDataMapper(ScDocument& /*rDoc*/)
    //mrDoc(rDoc)
{
}

ExternalDataMapper::~ExternalDataMapper()
{
}

void ExternalDataMapper::insertDataSource(const sc::ExternalDataSource& rSource)
{
    maDataSources.push_back(rSource);
}

const std::vector<sc::ExternalDataSource>& ExternalDataMapper::getDataSources() const
{
    return maDataSources;
}

std::vector<sc::ExternalDataSource>& ExternalDataMapper::getDataSources()
{
    return maDataSources;
}

DataProvider::DataProvider(sc::ExternalDataSource& rDataSource):
    mbDeterministic(false),
    mrDataSource(rDataSource)
{
}

void DataProvider::setDeterministic()
{
    mbDeterministic = true;
}

DataProvider::~DataProvider()
{
}

void ScDBDataManager::WriteToDoc(ScDocument& rDoc)
{
    // first apply all data transformations

    bool bShrunk = false;
    SCCOL nStartCol = 0;
    SCROW nStartRow = 0;
    SCCOL nEndCol = rDoc.MaxCol();
    SCROW nEndRow = rDoc.MaxRow();
    rDoc.ShrinkToUsedDataArea(bShrunk, 0, nStartCol, nStartRow, nEndCol, nEndRow, false, true, true);
    ScRange aClipRange(nStartCol, nStartRow, 0, nEndCol, nEndRow, 0);
    rDoc.SetClipArea(aClipRange);

    ScRange aDestRange;
    getDBData()->GetArea(aDestRange);
    SCCOL nColSize = std::min<SCCOL>(aDestRange.aEnd.Col() - aDestRange.aStart.Col(), nEndCol);
    aDestRange.aEnd.SetCol(aDestRange.aStart.Col() + nColSize);

    SCROW nRowSize = std::min<SCROW>(aDestRange.aEnd.Row() - aDestRange.aStart.Row(), nEndRow);
    aDestRange.aEnd.SetRow(aDestRange.aStart.Row() + nRowSize);

    ScMarkData aMark(mpDoc->GetSheetLimits());
    aMark.SelectTable(aDestRange.aStart.Tab(), true);
    mpDoc->CopyFromClip(aDestRange, aMark, InsertDeleteFlags::CONTENTS, nullptr, &rDoc);
    ScDocShell* pDocShell = mpDoc->GetDocumentShell();
    if (pDocShell)
        pDocShell->PostPaint(aDestRange, PaintPartFlags::All);
}

ScDBDataManager::ScDBDataManager(OUString aDBName, ScDocument* pDoc):
    maDBName(std::move(aDBName)),
    mpDoc(pDoc)
{
}

ScDBDataManager::~ScDBDataManager()
{
}

void ScDBDataManager::SetDatabase(const OUString& rDBName)
{
    maDBName = rDBName;
}

ScDBData* ScDBDataManager::getDBData()
{
    ScDBData* pDBData = mpDoc->GetDBCollection()->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(maDBName));
    return pDBData;
}

bool DataProviderFactory::isInternalDataProvider(std::u16string_view rProvider)
{
    return o3tl::starts_with(rProvider, u"org.libreoffice.calc");
}

std::shared_ptr<DataProvider> DataProviderFactory::getDataProvider(ScDocument* pDoc,
        sc::ExternalDataSource& rDataSource)
{
    const OUString& rDataProvider = rDataSource.getProvider();
    bool bInternal = DataProviderFactory::isInternalDataProvider(rDataProvider);
    if (bInternal)
    {
        if (rDataProvider == "org.libreoffice.calc.csv")
            return std::make_shared<CSVDataProvider>(pDoc, rDataSource);
        else if (rDataProvider == "org.libreoffice.calc.html")
            return std::make_shared<HTMLDataProvider>(pDoc, rDataSource);
        else if (rDataProvider == "org.libreoffice.calc.xml")
            return std::make_shared<XMLDataProvider>(pDoc, rDataSource);
        else if (rDataProvider == "org.libreoffice.calc.sql")
            return std::make_shared<SQLDataProvider>(pDoc, rDataSource);
    }
    else
    {
        SAL_WARN("sc", "no external data provider supported yet");
        return std::shared_ptr<DataProvider>();
    }

    return std::shared_ptr<DataProvider>();
}

}

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