/* -*- 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 "dbfindex.hxx"
#include <comphelper/processfactory.hxx>
#include <osl/file.hxx>
#include <osl/thread.hxx>
#include <tools/config.hxx>
#include <osl/diagnose.h>
#include <unotools/localfilehelper.hxx>
#include <tools/urlobj.hxx>
#include <unotools/pathoptions.hxx>
#include <ucbhelper/content.hxx>
#include <svl/filenotation.hxx>
#include <rtl/strbuf.hxx>
#include <utility>

namespace dbaui
{
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using namespace ::svt;

constexpr OString aGroupIdent("dBase III"_ostr);


ODbaseIndexDialog::ODbaseIndexDialog(weld::Window * pParent, OUString aDataSrcName)
    : GenericDialogController(pParent, u"dbaccess/ui/dbaseindexdialog.ui"_ustr, u"DBaseIndexDialog"_ustr)
    , m_aDSN(std::move(aDataSrcName))
    , m_xPB_OK(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xCB_Tables(m_xBuilder->weld_combo_box(u"table"_ustr))
    , m_xIndexes(m_xBuilder->weld_widget(u"frame"_ustr))
    , m_xLB_TableIndexes(m_xBuilder->weld_tree_view(u"tableindex"_ustr))
    , m_xLB_FreeIndexes(m_xBuilder->weld_tree_view(u"freeindex"_ustr))
    , m_xAdd(m_xBuilder->weld_button(u"add"_ustr))
    , m_xRemove(m_xBuilder->weld_button(u"remove"_ustr))
    , m_xAddAll(m_xBuilder->weld_button(u"addall"_ustr))
    , m_xRemoveAll(m_xBuilder->weld_button(u"removeall"_ustr))
{
    int nWidth = m_xLB_TableIndexes->get_approximate_digit_width() * 18;
    int nHeight = m_xLB_TableIndexes->get_height_rows(10);
    m_xLB_TableIndexes->set_size_request(nWidth, nHeight);
    m_xLB_FreeIndexes->set_size_request(nWidth, nHeight);

    m_xCB_Tables->connect_changed( LINK(this, ODbaseIndexDialog, TableSelectHdl) );
    m_xAdd->connect_clicked( LINK(this, ODbaseIndexDialog, AddClickHdl) );
    m_xRemove->connect_clicked( LINK(this, ODbaseIndexDialog, RemoveClickHdl) );
    m_xAddAll->connect_clicked( LINK(this, ODbaseIndexDialog, AddAllClickHdl) );
    m_xRemoveAll->connect_clicked( LINK(this, ODbaseIndexDialog, RemoveAllClickHdl) );
    m_xPB_OK->connect_clicked( LINK(this, ODbaseIndexDialog, OKClickHdl) );

    m_xLB_FreeIndexes->connect_changed( LINK(this, ODbaseIndexDialog, OnListEntrySelected) );
    m_xLB_TableIndexes->connect_changed( LINK(this, ODbaseIndexDialog, OnListEntrySelected) );

    Init();
    SetCtrls();
}

ODbaseIndexDialog::~ODbaseIndexDialog()
{
}

void ODbaseIndexDialog::checkButtons()
{
    m_xAdd->set_sensitive(0 != m_xLB_FreeIndexes->count_selected_rows());
    m_xAddAll->set_sensitive(0 != m_xLB_FreeIndexes->n_children());

    m_xRemove->set_sensitive(0 != m_xLB_TableIndexes->count_selected_rows());
    m_xRemoveAll->set_sensitive(0 != m_xLB_TableIndexes->n_children());
}

OTableIndex ODbaseIndexDialog::implRemoveIndex(const OUString& _rName, TableIndexList& _rList, weld::TreeView& _rDisplay, bool _bMustExist)
{
    OTableIndex aReturn;

    TableIndexList::iterator aSearch = std::find_if(_rList.begin(), _rList.end(),
        [&_rName](const OTableIndex& rIndex) { return rIndex.GetIndexFileName() == _rName; });
    if (aSearch != _rList.end())
    {
        sal_Int32 nPos = static_cast<sal_Int32>(std::distance(_rList.begin(), aSearch));

        aReturn = *aSearch;

        _rList.erase(aSearch);
        _rDisplay.remove_text(_rName);

        // adjust selection if necessary
        if (static_cast<sal_uInt32>(nPos) == _rList.size())
            _rDisplay.select(static_cast<sal_uInt16>(nPos)-1);
        else
            _rDisplay.select(static_cast<sal_uInt16>(nPos));
    }
    OSL_ENSURE(!_bMustExist || !aReturn.GetIndexFileName().isEmpty(), "ODbaseIndexDialog::implRemoveIndex : did not find the index!");
    return aReturn;
}

void ODbaseIndexDialog::implInsertIndex(const OTableIndex& _rIndex, TableIndexList& _rList, weld::TreeView& _rDisplay)
{
    _rList.push_front(_rIndex);
    _rDisplay.append_text(_rIndex.GetIndexFileName());
    _rDisplay.select(0);
}

OTableIndex ODbaseIndexDialog::RemoveTableIndex( std::u16string_view _rTableName, const OUString& _rIndexName )
{
    OTableIndex aReturn;

    // does the table exist ?
    TableInfoList::iterator aTablePos = std::find_if(m_aTableInfoList.begin(), m_aTableInfoList.end(),
                                           [&] (const OTableInfo& arg) { return arg.aTableName == _rTableName; });

    if (aTablePos == m_aTableInfoList.end())
        return aReturn;

    return implRemoveIndex(_rIndexName, aTablePos->aIndexList, *m_xLB_TableIndexes, true/*_bMustExist*/);
}

void ODbaseIndexDialog::InsertTableIndex( std::u16string_view _rTableName, const OTableIndex& _rIndex)
{
    TableInfoList::iterator aTablePos = std::find_if(m_aTableInfoList.begin(), m_aTableInfoList.end(),
                                           [&] (const OTableInfo& arg) { return arg.aTableName == _rTableName; });

    if (aTablePos == m_aTableInfoList.end())
        return;

    implInsertIndex(_rIndex, aTablePos->aIndexList, *m_xLB_TableIndexes);
}

IMPL_LINK_NOARG(ODbaseIndexDialog, OKClickHdl, weld::Button&, void)
{
    // let all tables write their INF file

    for (auto const& tableInfo : m_aTableInfoList)
        tableInfo.WriteInfFile(m_aDSN);

    m_xDialog->response(RET_OK);
}

IMPL_LINK_NOARG(ODbaseIndexDialog, AddClickHdl, weld::Button&, void)
{
    OUString aSelection = m_xLB_FreeIndexes->get_selected_text();
    OUString aTableName = m_xCB_Tables->get_active_text();
    OTableIndex aIndex = RemoveFreeIndex( aSelection, true );
    InsertTableIndex( aTableName, aIndex );

    checkButtons();
}

IMPL_LINK_NOARG(ODbaseIndexDialog, RemoveClickHdl, weld::Button&, void)
{
    OUString aSelection = m_xLB_TableIndexes->get_selected_text();
    OUString aTableName = m_xCB_Tables->get_active_text();
    OTableIndex aIndex = RemoveTableIndex( aTableName, aSelection );
    InsertFreeIndex( aIndex );

    checkButtons();
}

IMPL_LINK_NOARG(ODbaseIndexDialog, AddAllClickHdl, weld::Button&, void)
{
    const sal_Int32 nCnt = m_xLB_FreeIndexes->n_children();
    OUString aTableName = m_xCB_Tables->get_active_text();

    for (sal_Int32 nPos = 0; nPos < nCnt; ++nPos)
        InsertTableIndex(aTableName, RemoveFreeIndex(m_xLB_FreeIndexes->get_text(0), true));

    checkButtons();
}

IMPL_LINK_NOARG(ODbaseIndexDialog, RemoveAllClickHdl, weld::Button&, void)
{
    const sal_Int32 nCnt = m_xLB_TableIndexes->n_children();
    OUString aTableName = m_xCB_Tables->get_active_text();

    for (sal_Int32 nPos = 0; nPos < nCnt; ++nPos)
        InsertFreeIndex(RemoveTableIndex(aTableName, m_xLB_TableIndexes->get_text(0)));

    checkButtons();
}

IMPL_LINK_NOARG(ODbaseIndexDialog, OnListEntrySelected, weld::TreeView&, void)
{
    checkButtons();
}

IMPL_LINK(ODbaseIndexDialog, TableSelectHdl, weld::ComboBox&, rComboBox, void)
{
    // search the table
    TableInfoList::iterator aTablePos = std::find_if(m_aTableInfoList.begin(), m_aTableInfoList.end(),
                                           [&] (const OTableInfo& arg) { return arg.aTableName == rComboBox.get_active_text() ; });

    if (aTablePos == m_aTableInfoList.end())
        return;

    // fill the listbox for the indexes
    m_xLB_TableIndexes->clear();
    for (auto const& index : aTablePos->aIndexList)
        m_xLB_TableIndexes->append_text(index.GetIndexFileName());

    if (!aTablePos->aIndexList.empty())
        m_xLB_TableIndexes->select(0);

    checkButtons();
}

void ODbaseIndexDialog::Init()
{
    m_xPB_OK->set_sensitive(false);
    m_xIndexes->set_sensitive(false);

    // All indices are first added to a list of free indices.
    // Afterwards, check the index of each table in the Inf-file.
    // These indices are removed from the list of free indices and
    // entered in the indexlist of the table.

    // if the string does not contain a path, cut the string
    INetURLObject aURL;
    aURL.SetSmartProtocol(INetProtocol::File);
    {
        SvtPathOptions aPathOptions;
        m_aDSN = aPathOptions.SubstituteVariable(m_aDSN);
    }
    aURL.SetSmartURL(m_aDSN);

    //  String aFileName = aURL.PathToFileName();
    m_aDSN = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
    ::ucbhelper::Content aFile;
    bool bFolder=true;
    try
    {
        aFile = ::ucbhelper::Content(m_aDSN,Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext());
        bFolder = aFile.isFolder();
    }
    catch(Exception&)
    {
        return;
    }

    // first assume for all indexes they're free

    std::vector< OUString > aUsedIndexes;

    aURL.SetSmartProtocol(INetProtocol::File);
    const Sequence<OUString> aFolderUrls = ::utl::LocalFileHelper::GetFolderContents(m_aDSN, bFolder);
    for(const OUString& rURL : aFolderUrls)
    {
        OUString aName;
        osl::FileBase::getSystemPathFromFileURL(rURL,aName);
        aURL.SetSmartURL(aName);
        OUString aExt = aURL.getExtension();
        if (aExt == "ndx")
        {
            m_aFreeIndexList.emplace_back(aURL.getName() );
        }
        else if (aExt == "dbf")
        {
            m_aTableInfoList.emplace_back(aURL.getName() );
            OTableInfo& rTabInfo = m_aTableInfoList.back();

            // open the INF file
            aURL.setExtension(u"inf");
            OFileNotation aTransformer(aURL.GetURLNoPass(), OFileNotation::N_URL);
            Config aInfFile( aTransformer.get(OFileNotation::N_SYSTEM) );
            aInfFile.SetGroup( aGroupIdent );

            // fill the indexes list
            OString aNDX;
            sal_uInt16 nKeyCnt = aInfFile.GetKeyCount();
            OString aKeyName;
            OUString aEntry;

            for( sal_uInt16 nKey = 0; nKey < nKeyCnt; nKey++ )
            {
                // does the key point to an index file ?
                aKeyName = aInfFile.GetKeyName( nKey );
                aNDX = aKeyName.copy(0,3);

                // yes -> add to the tables index list
                if (aNDX == "NDX")
                {
                    aEntry = OStringToOUString(aInfFile.ReadKey(aKeyName), osl_getThreadTextEncoding());
                    rTabInfo.aIndexList.emplace_back( aEntry );

                    // and remove it from the free index list
                    aUsedIndexes.push_back(aEntry);
                        // do this later below. We may not have encountered the index file, yet, thus we may not
                        // know the index as being free, yet
                }
            }
        }
    }

    for (auto const& usedIndex : aUsedIndexes)
        RemoveFreeIndex( usedIndex, false );

    if (!m_aTableInfoList.empty())
    {
        m_xPB_OK->set_sensitive(true);
        m_xIndexes->set_sensitive(true);
    }

    checkButtons();
}

void ODbaseIndexDialog::SetCtrls()
{
    // ComboBox tables
    for (auto const& tableInfo : m_aTableInfoList)
        m_xCB_Tables->append_text(tableInfo.aTableName);

    // put the first dataset into Edit
    if (!m_aTableInfoList.empty())
    {
        const OTableInfo& rTabInfo = m_aTableInfoList.front();
        m_xCB_Tables->set_entry_text(rTabInfo.aTableName);

        // build ListBox of the table indices
        for (auto const& index : rTabInfo.aIndexList)
            m_xLB_TableIndexes->append_text(index.GetIndexFileName());

        if (!rTabInfo.aIndexList.empty())
            m_xLB_TableIndexes->select(0);
    }

    // ListBox of the free indices
    for (auto const& freeIndex : m_aFreeIndexList)
        m_xLB_FreeIndexes->append_text(freeIndex.GetIndexFileName());

    if (!m_aFreeIndexList.empty())
        m_xLB_FreeIndexes->select(0);

    TableSelectHdl(*m_xCB_Tables);
    checkButtons();
}

void OTableInfo::WriteInfFile( const OUString& rDSN ) const
{
    // open INF file
    INetURLObject aURL;
    aURL.SetSmartProtocol(INetProtocol::File);
    OUString aDsn = rDSN;
    {
        SvtPathOptions aPathOptions;
        aDsn = aPathOptions.SubstituteVariable(aDsn);
    }
    aURL.SetSmartURL(aDsn);
    aURL.Append(aTableName);
    aURL.setExtension(u"inf");

    OFileNotation aTransformer(aURL.GetURLNoPass(), OFileNotation::N_URL);
    Config aInfFile( aTransformer.get(OFileNotation::N_SYSTEM) );
    aInfFile.SetGroup( aGroupIdent );

    // first, delete all table indices
    sal_uInt16 nKeyCnt = aInfFile.GetKeyCount();
    sal_uInt16 nKey = 0;

    while( nKey < nKeyCnt )
    {
        // Does the key point to an index file?...
        OString aKeyName = aInfFile.GetKeyName( nKey );
        OString aNDX = aKeyName.copy(0,3);

        //...if yes, delete index file, nKey is at subsequent key
        if (aNDX == "NDX")
        {
            aInfFile.DeleteKey(aKeyName);
            nKeyCnt--;
        }
        else
            nKey++;

    }

    // now add all saved indices
    sal_uInt16 nPos = 0;
    for (auto const& index : aIndexList)
    {
        OStringBuffer aKeyName("NDX");
        if( nPos > 0 )  // first index contains no number
            aKeyName.append(static_cast<sal_Int32>(nPos));
        aInfFile.WriteKey(
            aKeyName.makeStringAndClear(),
            OUStringToOString(index.GetIndexFileName(),
                osl_getThreadTextEncoding()));
        ++nPos;
    }

    aInfFile.Flush();

    // if only [dbase] is left in INF-file, delete file
    if(nPos)
        return;

    try
    {
        ::ucbhelper::Content aContent(aURL.GetURLNoPass(),Reference<XCommandEnvironment>(), comphelper::getProcessComponentContext());
        aContent.executeCommand( u"delete"_ustr, Any( true ) );
    }
    catch (const Exception& )
    {
        // simply silent this. The strange algorithm here does a lot of
        // things even if no files at all were created or accessed, so it's
        // possible that the file we're trying to delete does not even
        // exist, and this is a valid condition.
    }
}

} // namespace

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