/* -*- 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 <sfx2/frame.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <o3tl/string_view.hxx>

#include <swtypes.hxx>
#include <flddinf.hrc>
#include <strings.hrc>
#include <fldbas.hxx>
#include <docufld.hxx>
#include <wrtsh.hxx>
#include <cmdid.h>

#include "flddinf.hxx"
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/util/Time.hpp>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/util/Date.hpp>

#define USER_DATA_VERSION_1 "1"
#define USER_DATA_VERSION USER_DATA_VERSION_1

using namespace com::sun::star;

void FillFieldSelect(weld::TreeView& rListBox)
{
    for (auto const& aID : FLD_SELECT)
        rListBox.append_text(SwResId(aID));
}

SwFieldDokInfPage::SwFieldDokInfPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet *const pCoreSet)
    :  SwFieldPage(pPage, pController, u"modules/swriter/ui/flddocinfopage.ui"_ustr, u"FieldDocInfoPage"_ustr, pCoreSet)
    , m_nOldSel(0)
    , m_nOldFormat(0)
    , m_xTypeList(m_xBuilder->weld_tree_view(u"type-list"_ustr))
    , m_xTypeTree(m_xBuilder->weld_tree_view(u"type-tree"_ustr))
    // tdf#104278 have two tree views, one with expander and one without, the one with is only used
    // when there are custom properties which use the expander, so the common case of no custom
    // properties doesn't have an 'unexplained' expander margin
    , m_pTypeView(m_xTypeTree.get())
    , m_xSelection(m_xBuilder->weld_widget(u"selectframe"_ustr))
    , m_xSelectionLB(m_xBuilder->weld_tree_view(u"select"_ustr))
    , m_xFormat(m_xBuilder->weld_widget(u"formatframe"_ustr))
    , m_xFormatLB(new SwNumFormatTreeView(m_xBuilder->weld_tree_view(u"format"_ustr)))
    , m_xFixedCB(m_xBuilder->weld_check_button(u"fixed"_ustr))
{
    m_xTypeList->make_sorted();
    m_xTypeTree->make_sorted();
    FillFieldSelect(*m_xSelectionLB);

    auto nWidth = m_pTypeView->get_approximate_digit_width() * FIELD_COLUMN_WIDTH;
    auto nHeight = m_pTypeView->get_height_rows(10);
    m_xTypeTree->set_size_request(nWidth, nHeight);
    m_xTypeList->set_size_request(nWidth, nHeight);
    m_xFormatLB->get_widget().set_size_request(nWidth * 2, nHeight);
    m_xSelectionLB->set_size_request(nWidth, nHeight);

    //enable 'active' language selection
    m_xFormatLB->SetShowLanguageControl(true);

    const SfxUnoAnyItem* pItem = pCoreSet
        ? pCoreSet->GetItem(FN_FIELD_DIALOG_DOC_PROPS, false)
        : nullptr;
    if ( pItem )
        pItem->GetValue() >>= m_xCustomPropertySet;

    // uitests
    m_pTypeView->set_buildable_name(u"type-docinf"_ustr);
    m_xSelectionLB->set_buildable_name(m_xSelectionLB->get_buildable_name() + "-docinf");
    m_xFormatLB->set_buildable_name(m_xFormatLB->get_buildable_name() + "-docinf");
}

SwFieldDokInfPage::~SwFieldDokInfPage()
{
}

void SwFieldDokInfPage::Reset(const SfxItemSet* )
{
    Init(); // general initialisation

    uno::Sequence<beans::Property> aCustomProperties;
    if (m_xCustomPropertySet.is())
    {
        uno::Reference<beans::XPropertySetInfo> xSetInfo = m_xCustomPropertySet->getPropertySetInfo();
        aCustomProperties = xSetInfo->getProperties();
    }

    if (aCustomProperties.hasElements())
    {
        m_xTypeList->hide();
        m_xTypeList->set_buildable_name(u"type-list"_ustr);
        m_xTypeTree->show();
        m_pTypeView = m_xTypeTree.get();
    }
    else
    {
        m_xTypeTree->hide();
        m_xTypeTree->set_buildable_name(u"type-tree"_ustr);
        m_xTypeList->show();
        m_pTypeView = m_xTypeList.get();
    }

    m_pTypeView->set_buildable_name(u"type-docinf"_ustr);

    // initialise TypeListBox
    m_pTypeView->freeze();
    m_pTypeView->clear();
    m_xSelEntry.reset();

    // display SubTypes in TypeLB
    SwDocInfoSubType nSubType = SwDocInfoSubType::Max;
    if (IsFieldEdit())
    {
        const SwField* pCurField = GetCurField();
        if (auto const pField = dynamic_cast<SwDocInfoField const*>(pCurField))
        {
            nSubType = pField->GetSubType() & SwDocInfoSubType::LowerMask;
            if( nSubType == SwDocInfoSubType::Custom )
                m_sOldCustomFieldName = pField->GetName();
        }
        m_xFormatLB->SetAutomaticLanguage(pCurField->IsAutomaticLanguage());
        SwWrtShell *pSh = GetWrtShell();
        if(pSh)
        {
            const SvNumberformat* pFormat = pSh->GetNumberFormatter()->GetEntry(pCurField->GetUntypedFormat());
            if(pFormat)
                m_xFormatLB->SetLanguage(pFormat->GetLanguage());
        }
    }

    sal_Int32 nSelEntryData = -1;
    const OUString sUserData = GetUserData();
    sal_Int32 nIdx{ 0 };
    if (o3tl::equalsIgnoreAsciiCase(o3tl::getToken(sUserData, 0, ';', nIdx), u"" USER_DATA_VERSION_1))
    {
        nSelEntryData = o3tl::toInt32(o3tl::getToken(sUserData, 0, ';', nIdx));
    }

    std::vector<OUString> aLst;
    GetFieldMgr().GetSubTypes(SwFieldTypesEnum::DocumentInfo, aLst);
    std::unique_ptr<weld::TreeIter> xEntry(m_pTypeView->make_iterator());
    std::unique_ptr<weld::TreeIter> xExpandEntry;
    for(size_t i = 0; i < aLst.size(); ++i)
    {
        if (!IsFieldEdit() || static_cast<sal_uInt16>(nSubType) == i)
        {
            const OUString sId(OUString::number(i));
            if (static_cast<sal_uInt16>(SwDocInfoSubType::Custom) == i)
            {
                if(m_xCustomPropertySet.is() )
                {
                    if (aCustomProperties.hasElements())
                    {
                        std::unique_ptr<weld::TreeIter> xInfo(m_pTypeView->make_iterator());

                        OUString sText(SwResId(STR_CUSTOM_FIELD));
                        OUString sEntryId(OUString::number(USHRT_MAX));
                        m_pTypeView->insert(nullptr, -1, &sText, &sEntryId, nullptr,
                                            nullptr, false, xInfo.get());
                        for (const auto& rProperty : aCustomProperties)
                        {
                            const OUString sEntry = rProperty.Name;

                            m_pTypeView->insert(xInfo.get(), -1, &sEntry, &sId,
                                               nullptr, nullptr, false, xEntry.get());
                            if (m_sOldCustomFieldName == sEntry)
                            {
                                m_xSelEntry = m_pTypeView->make_iterator(xEntry.get());
                                xExpandEntry = m_pTypeView->make_iterator(xInfo.get());
                            }
                        }
                    }
                }
            }
            else
            {
                if (!(IsFieldDlgHtmlMode()
                      && (i == static_cast<sal_uInt16>(SwDocInfoSubType::Edit)
                          || i == static_cast<sal_uInt16>(SwDocInfoSubType::Subject)
                          || i == static_cast<sal_uInt16>(SwDocInfoSubType::Print))))
                {
                    m_pTypeView->insert(nullptr, -1, &aLst[i], &sId,
                                        nullptr, nullptr, false, xEntry.get());
                }
            }
            if (static_cast<size_t>(nSelEntryData) == i)
                m_xSelEntry = std::move(xEntry);
        }
    }

    m_pTypeView->thaw();

    if (xExpandEntry)
        m_pTypeView->expand_row(*xExpandEntry);

    // select old Pos
    if (m_xSelEntry)
    {
        m_pTypeView->select(*m_xSelEntry);
        nSubType = static_cast<SwDocInfoSubType>(m_pTypeView->get_id(*m_xSelEntry).toUInt32());
    }
    else
    {
        m_xSelEntry = m_pTypeView->make_iterator();
        if (m_pTypeView->get_iter_first(*m_xSelEntry))
            nSubType = static_cast<SwDocInfoSubType>(m_pTypeView->get_id(*m_xSelEntry).toUInt32());
        else
            m_xSelEntry.reset();
    }

    FillSelectionLB(nSubType);
    if (m_xSelEntry)
        TypeHdl(*m_pTypeView);

    m_pTypeView->connect_selection_changed(LINK(this, SwFieldDokInfPage, TypeHdl));
    m_pTypeView->connect_row_activated(LINK(this, SwFieldDokInfPage, TreeViewInsertHdl));
    m_xSelectionLB->connect_selection_changed(LINK(this, SwFieldDokInfPage, SubTypeHdl));
    m_xSelectionLB->connect_row_activated(LINK(this, SwFieldDokInfPage, TreeViewInsertHdl));
    m_xFormatLB->connect_row_activated(LINK(this, SwFieldDokInfPage, TreeViewInsertHdl));

    if (IsFieldEdit())
    {
        m_nOldSel = m_xSelectionLB->get_selected_index();
        m_nOldFormat = GetCurField()->GetUntypedFormat();
        m_xFixedCB->save_state();
    }
}

IMPL_LINK_NOARG(SwFieldDokInfPage, TypeHdl, weld::TreeView&, void)
{
    // current ListBoxPos
    if (!m_pTypeView->get_selected(m_xSelEntry.get()) &&
        m_pTypeView->get_iter_first(*m_xSelEntry))
    {
        m_pTypeView->select(*m_xSelEntry);
    }
    FillSelectionLB(static_cast<SwDocInfoSubType>(m_pTypeView->get_id(*m_xSelEntry).toUInt32()));
    SubTypeHdl(*m_xSelectionLB);
}

IMPL_LINK_NOARG(SwFieldDokInfPage, SubTypeHdl, weld::TreeView&, void)
{
    SwDocInfoSubType nSubType = static_cast<SwDocInfoSubType>(m_pTypeView->get_id(*m_xSelEntry).toUInt32());
    sal_Int32 nPos = m_xSelectionLB->get_selected_index();
    SwDocInfoSubType nExtSubType;
    SvNumFormatType nNewType = SvNumFormatType::ALL;

    if (nSubType != SwDocInfoSubType::Edit)
    {
        if (nPos == -1)
        {
            if (!m_xSelectionLB->n_children())
            {
                m_xFormatLB->clear();
                m_xFormat->set_sensitive(false);
                if( nSubType == SwDocInfoSubType::Custom )
                {
                    //find out which type the custom field has - for a start set to DATE format
                    const OUString sName = m_pTypeView->get_text(*m_xSelEntry);
                    try
                    {
                        uno::Any aVal = m_xCustomPropertySet->getPropertyValue( sName );
                        const uno::Type& rValueType = aVal.getValueType();
                        if( rValueType == ::cppu::UnoType<util::DateTime>::get())
                        {
                            nNewType = SvNumFormatType::DATETIME;
                        }
                        else if( rValueType == ::cppu::UnoType<util::Date>::get())
                        {
                            nNewType = SvNumFormatType::DATE;
                        }
                        else if( rValueType == ::cppu::UnoType<util::Time>::get())
                        {
                            nNewType = SvNumFormatType::TIME;
                        }
                    }
                    catch( const uno::Exception& )
                    {
                    }
                }
                else
                    return;
            }
            nPos = 0;
        }

        nExtSubType = static_cast<SwDocInfoSubType>(m_xSelectionLB->get_id(nPos).toUInt32());
    }
    else
        nExtSubType = SwDocInfoSubType::SubTime;

    SvNumFormatType nOldType = SvNumFormatType::ALL;
    bool bEnable = false;
    bool bOneArea = false;

    if (m_xFormatLB->get_active())
        nOldType = m_xFormatLB->GetFormatType();

    switch (nExtSubType)
    {
        case SwDocInfoSubType::SubAuthor:
            break;

        case SwDocInfoSubType::SubDate:
            nNewType = SvNumFormatType::DATE;
            bOneArea = true;
            break;

        case SwDocInfoSubType::SubTime:
            nNewType = SvNumFormatType::TIME;
            bOneArea = true;
            break;

        default: break;
    }
    if (nNewType == SvNumFormatType::ALL)
    {
        m_xFormatLB->clear();
    }
    else
    {
        if (nOldType != nNewType)
        {
            m_xFormatLB->SetFormatType(nNewType);
            m_xFormatLB->SetOneArea(bOneArea);
        }
        bEnable = true;
    }

    sal_uInt32 nFormat = 0;

    SwDocInfoSubType nOldSubType = SwDocInfoSubType::Title;

    if (IsFieldEdit())
    {
        if (auto const pField = dynamic_cast<SwDocInfoField const*>(GetCurField()))
        {
            nFormat = static_cast<sal_uInt16>(pField->GetFormat());
            nOldSubType = pField->GetSubType() & SwDocInfoSubType::UpperMask;
        }
        nPos = m_xSelectionLB->get_selected_index();
        if (nPos != -1)
        {
            nSubType = static_cast<SwDocInfoSubType>(m_xSelectionLB->get_id(nPos).toUInt32());

            nOldSubType &= ~SwDocInfoSubType::SubFixed;
            if (nOldSubType == nSubType)
            {
                if (!nFormat && (nNewType == SvNumFormatType::DATE || nNewType == SvNumFormatType::TIME))
                {
                    SwWrtShell *pSh = GetWrtShell();
                    if(pSh)
                    {
                        SvNumberFormatter* pFormatter = pSh->GetNumberFormatter();
                        LanguageType eLang = m_xFormatLB->GetCurLanguage();
                        if (nNewType == SvNumFormatType::DATE)
                            nFormat = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLang);
                        else if (nNewType == SvNumFormatType::TIME)
                            nFormat = pFormatter->GetFormatIndex( NF_TIME_HHMM, eLang);
                    }
                }
                m_xFormatLB->SetDefFormat(nFormat);
            }
        }
        else if( (nSubType == SwDocInfoSubType::Custom)  && (nNewType != SvNumFormatType::ALL) )
        {
            m_xFormatLB->SetDefFormat(nFormat);
        }
    }

    // Always allow Fixed Content to be turned off if it is currently on
    m_xFormat->set_sensitive(bEnable || m_xFixedCB->get_active());

    if (!bEnable)
        m_xFormatLB->clear();
    else if (m_xFormatLB->get_selected_index() == -1)
        m_xFormatLB->select(0);
}

sal_Int32 SwFieldDokInfPage::FillSelectionLB(SwDocInfoSubType nSubType)
{
    // fill Format-Listbox
    SwFieldTypesEnum nTypeId = SwFieldTypesEnum::DocumentInfo;

    EnableInsert(nSubType != SwDocInfoSubType::Max, IsCurrentPage());

    if (nSubType == SwDocInfoSubType::Max)  // Info-Text
        nSubType = SwDocInfoSubType::SubtypeBegin;

    m_xSelectionLB->clear();

    sal_uInt16 nSize = 0;
    sal_Int32 nSelPos = -1;
    sal_uInt16 nExtSubIdx = 0;

    if (IsFieldEdit())
    {
        SwDocInfoSubType nExtSubType = SwDocInfoSubType::Title;
        if (auto const pField = dynamic_cast<SwDocInfoField const*>(GetCurField()))
        {
            nExtSubType = pField->GetSubType() & SwDocInfoSubType::UpperMask;
        }
        m_xFixedCB->set_active(bool(nExtSubType & SwDocInfoSubType::SubFixed));
        nExtSubIdx = (static_cast<sal_uInt16>(nExtSubType & ~SwDocInfoSubType::SubFixed) >> 8) - 1;
    }

    if (nSubType < SwDocInfoSubType::Create
        || nSubType == SwDocInfoSubType::DocNo
        || nSubType == SwDocInfoSubType::Edit
        || nSubType == SwDocInfoSubType::Custom )
    {
        // Format Box is empty for Title and Time
    }
    else
    {
        nSize = GetFieldMgr().GetFormatCount(nTypeId, IsFieldDlgHtmlMode());
        for (sal_uInt16 i = 0; i < nSize; ++i)
        {
            OUString sId(OUString::number(GetFieldMgr().GetFormatId(nTypeId, i)));
            m_xSelectionLB->append(sId, GetFieldMgr().GetFormatStr(nTypeId, i));
            if (IsFieldEdit() && i == nExtSubIdx)
                nSelPos = i;
        }
    }

    bool bEnable = nSize != 0;

    if (nSize)
    {
        if (m_xSelectionLB->get_selected_index() == -1)
            m_xSelectionLB->select(nSelPos == USHRT_MAX ? 0 : nSelPos);
        bEnable = true;
    }

    m_xSelection->set_sensitive(bEnable);

    return nSize;
}

bool SwFieldDokInfPage::FillItemSet(SfxItemSet* )
{
    if (!m_xSelEntry)
        return false;

    SwDocInfoSubType nSubType = static_cast<SwDocInfoSubType>(m_pTypeView->get_id(*m_xSelEntry).toUInt32());
    if (nSubType == SwDocInfoSubType::Max)
        return false;

    sal_uInt32 nFormat = 0;

    sal_Int32 nPos = m_xSelectionLB->get_selected_index();

    OUString aName;
    if (SwDocInfoSubType::Custom == nSubType)
        aName = m_pTypeView->get_text(*m_xSelEntry);

    if (nPos != -1)
        nSubType |= static_cast<SwDocInfoSubType>(m_xSelectionLB->get_id(nPos).toUInt32());

    if (m_xFixedCB->get_active())
        nSubType |= SwDocInfoSubType::SubFixed;

    nPos = m_xFormatLB->get_selected_index();
    if(nPos != -1)
        nFormat = m_xFormatLB->GetFormat();

    if (!IsFieldEdit() || m_nOldSel != m_xSelectionLB->get_selected_index() ||
        m_nOldFormat != nFormat || m_xFixedCB->get_state_changed_from_saved()
        || (SwDocInfoSubType::Custom == nSubType && aName != m_sOldCustomFieldName ))
    {
        InsertField(SwFieldTypesEnum::DocumentInfo, static_cast<sal_uInt16>(nSubType), aName, OUString(), nFormat,
                ' ', m_xFormatLB->IsAutomaticLanguage());
    }

    return false;
}

std::unique_ptr<SfxTabPage> SwFieldDokInfPage::Create( weld::Container* pPage, weld::DialogController* pController,
                                            const SfxItemSet *const pAttrSet)
{
    return std::make_unique<SwFieldDokInfPage>(pPage, pController, pAttrSet);
}

SwFieldGroup SwFieldDokInfPage::GetGroup()
{
    return SwFieldGroup::DocInfos;
}

void SwFieldDokInfPage::FillUserData()
{
    int nEntry = m_pTypeView->get_selected_index();
    sal_uInt16 nTypeSel = nEntry != -1 ? m_pTypeView->get_id(nEntry).toUInt32() : USHRT_MAX;
    SetUserData(USER_DATA_VERSION ";" + OUString::number( nTypeSel ));
}

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