/* -*- 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 <comphelper/string.hxx>
#include <comphelper/processfactory.hxx>

#include <config_gpgme.h>
#if HAVE_FEATURE_GPGME
# include <com/sun/star/xml/crypto/GPGSEInitializer.hpp>
# include <com/sun/star/xml/crypto/XXMLSecurityContext.hpp>
#endif

#include <i18nlangtag/languagetag.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <o3tl/safeint.hxx>
#include <vcl/svapp.hxx>
#include <svl/intitem.hxx>
#include <vcl/settings.hxx>

#include <officecfg/Office/Common.hxx>
#include <unotools/useroptions.hxx>
#include <cuioptgenrl.hxx>
#include <svx/svxids.hrc>
#include <svx/optgenrl.hxx>

using namespace css;

namespace
{

// rows
enum RowType
{
    Row_Company,
    Row_Name,
    Row_Name_Russian,
    Row_Name_Eastern,
    Row_Street,
    Row_Street_Russian,
    Row_City,
    Row_City_US,
    Row_Country,
    Row_TitlePos,
    Row_Phone,
    Row_FaxMail,

    nRowCount
};

// language flags
namespace Lang
{
    unsigned const Others = 1;
    unsigned const Russian = 2;
    unsigned const Eastern = 4;
    unsigned const US = 8;
    unsigned const All = static_cast<unsigned>(-1);
}


// vRowInfo[] -- rows (text + one or more edit boxes)
// The order is the same as in RowType above, which is up to down.

struct
{
    // id of the lockimage
    OUString pLockId;
    // id of the text
    OUString pTextId;
    // language flags (see Lang above):
    // which language is this row for?
    unsigned nLangFlags;
}
const vRowInfo[] =
{
    { "lockcompanyft",  "companyft",   Lang::All },
    { "locknameft",     "nameft",      Lang::All & ~Lang::Russian & ~Lang::Eastern },
    { "lockrusnameft",  "rusnameft",   Lang::Russian },
    { "lockeastnameft", "eastnameft",  Lang::Eastern },
    { "lockstreetft",   "streetft",    Lang::All & ~Lang::Russian },
    { "lockrusstreetft","russtreetft", Lang::Russian },
    { "lockicityft",    "icityft",     Lang::All & ~Lang::US },
    { "lockcityft",     "cityft",      Lang::US },
    { "lockcountryft",  "countryft",   Lang::All },
    { "locktitleft",    "titleft",     Lang::All },
    { "lockphoneft",    "phoneft",     Lang::All },
    { "lockfaxft",      "faxft",       Lang::All },
};


// vFieldInfo[] -- edit boxes
// The order is up to down, and then left to right.

struct
{
    // in which row?
    RowType eRow;
    // id of the edit box
    OUString pEditId;
    // id for SvtUserOptions in unotools/useroptions.hxx
    UserOptToken nUserOptionsId;
    // id for settings the focus (defined in svx/optgenrl.hxx)
    EditPosition nGrabFocusId;
}
const vFieldInfo[] =
{
    // Company
    { Row_Company, "company", UserOptToken::Company, EditPosition::COMPANY },
    // Name
    { Row_Name, "firstname", UserOptToken::FirstName, EditPosition::FIRSTNAME },
    { Row_Name, "lastname", UserOptToken::LastName, EditPosition::LASTNAME  },
    { Row_Name, "shortname", UserOptToken::ID, EditPosition::SHORTNAME },
    // Name (russian)
    { Row_Name_Russian, "ruslastname", UserOptToken::LastName, EditPosition::LASTNAME  },
    { Row_Name_Russian, "rusfirstname", UserOptToken::FirstName, EditPosition::FIRSTNAME },
    { Row_Name_Russian, "rusfathersname", UserOptToken::FathersName, EditPosition::UNKNOWN },
    { Row_Name_Russian, "russhortname", UserOptToken::ID, EditPosition::SHORTNAME },
    // Name (eastern: reversed name ord
    { Row_Name_Eastern, "eastlastname", UserOptToken::LastName, EditPosition::LASTNAME  },
    { Row_Name_Eastern, "eastfirstname", UserOptToken::FirstName, EditPosition::FIRSTNAME },
    { Row_Name_Eastern, "eastshortname", UserOptToken::ID, EditPosition::SHORTNAME },
    // Street
    { Row_Street, "street", UserOptToken::Street, EditPosition::STREET },
    // Street (russian)
    { Row_Street_Russian, "russtreet", UserOptToken::Street, EditPosition::STREET },
    { Row_Street_Russian, "apartnum", UserOptToken::Apartment, EditPosition::UNKNOWN },
    // City
    { Row_City, "izip", UserOptToken::Zip, EditPosition::PLZ },
    { Row_City, "icity", UserOptToken::City, EditPosition::CITY },
    // City (US)
    { Row_City_US, "city", UserOptToken::City, EditPosition::CITY },
    { Row_City_US, "state", UserOptToken::State, EditPosition::STATE },
    { Row_City_US, "zip", UserOptToken::Zip, EditPosition::PLZ },
    // Country
    { Row_Country, "country", UserOptToken::Country, EditPosition::COUNTRY },
    // Title/Position
    { Row_TitlePos, "title", UserOptToken::Title,    EditPosition::TITLE },
    { Row_TitlePos, "position", UserOptToken::Position, EditPosition::POSITION },
    // Phone
    { Row_Phone, "home", UserOptToken::TelephoneHome, EditPosition::TELPRIV },
    { Row_Phone, "work", UserOptToken::TelephoneWork, EditPosition::TELCOMPANY },
    // Fax/Mail
    { Row_FaxMail, "fax", UserOptToken::Fax, EditPosition::FAX },
    { Row_FaxMail, "email", UserOptToken::Email, EditPosition::EMAIL },
};


} // namespace


// Row

struct SvxGeneralTabPage::Row
{
    // row lockdown icon
    std::unique_ptr<weld::Widget> xLockImg;
    // row label
    std::unique_ptr<weld::Label> xLabel;
    // first and last field in the row (last is exclusive)
    unsigned nFirstField, nLastField;

public:
    explicit Row (std::unique_ptr<weld::Widget> xLockImg_, std::unique_ptr<weld::Label> xLabel_)
        : xLockImg(std::move(xLockImg_))
        , xLabel(std::move(xLabel_))
        , nFirstField(0)
        , nLastField(0)
    {
        xLabel->show();
    }
};


// Field

struct SvxGeneralTabPage::Field
{
    // which field is this? (in vFieldInfo[] above)
    unsigned iField;
    // edit box
    std::unique_ptr<weld::Entry> xEdit;
    std::unique_ptr<weld::Container> xParent;

public:
    Field (std::unique_ptr<weld::Entry> xEdit_, unsigned iField_)
        : iField(iField_)
        , xEdit(std::move(xEdit_))
        , xParent(xEdit->weld_parent())
    {
        //We want all widgets inside a container, so each row of the toplevel
        //grid has another container in it. To avoid adding spacing to these
        //empty grids they all default to invisible, so show them if their
        //children are visible
        xParent->show();
        xEdit->show();
    }
};

SvxGeneralTabPage::SvxGeneralTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rCoreSet)
    : SfxTabPage(pPage, pController, "cui/ui/optuserpage.ui", "OptUserPage", &rCoreSet)
    , m_xUseDataCB(m_xBuilder->weld_check_button("usefordocprop"))
    , m_xUseDataImg(m_xBuilder->weld_widget("lockusefordocprop"))
    , m_xCryptoFrame(m_xBuilder->weld_widget( "cryptography"))
    , m_xSigningKeyLB(m_xBuilder->weld_combo_box("signingkey"))
    , m_xSigningKeyFT(m_xBuilder->weld_label("signingkeylabel"))
    , m_xSigningKeyImg(m_xBuilder->weld_widget("locksigningkey"))
    , m_xEncryptionKeyLB(m_xBuilder->weld_combo_box("encryptionkey"))
    , m_xEncryptionKeyFT(m_xBuilder->weld_label("encryptionkeylabel"))
    , m_xEncryptionKeyImg(m_xBuilder->weld_widget("lockencryptionkey"))
    , m_xEncryptToSelfCB(m_xBuilder->weld_check_button("encrypttoself"))
    , m_xEncryptToSelfImg(m_xBuilder->weld_widget("lockencrypttoself"))
{
    InitControls();
#if HAVE_FEATURE_GPGME
    InitCryptography();
#else
    m_xCryptoFrame->hide();
#endif

    SetExchangeSupport(); // this page needs ExchangeSupport
    SetLinks();
}

SvxGeneralTabPage::~SvxGeneralTabPage()
{
}

// Initializes the titles and the edit boxes,
// according to vRowInfo[] and vFieldInfo[] above.
void SvxGeneralTabPage::InitControls ()
{
    // which language bit do we use? (see Lang and vRowInfo[] above)
    unsigned LangBit;
    LanguageType l = Application::GetSettings().GetUILanguageTag().getLanguageType();
    if (l == LANGUAGE_ENGLISH_US)
        LangBit = Lang::US;
    else if (l == LANGUAGE_RUSSIAN)
        LangBit = Lang::Russian;
    else
    {
        if (MsLangId::isFamilyNameFirst(l))
            LangBit = Lang::Eastern;
        else
            LangBit = Lang::Others;
    }

    // creating rows
    unsigned iField = 0;
    for (unsigned iRow = 0; iRow != nRowCount; ++iRow)
    {
        RowType const eRow = static_cast<RowType>(iRow);
        // is the row visible?
        if (!(vRowInfo[iRow].nLangFlags & LangBit))
            continue;
        // creating row
        vRows.push_back(std::make_shared<Row>(m_xBuilder->weld_widget(vRowInfo[iRow].pLockId),
            m_xBuilder->weld_label(vRowInfo[iRow].pTextId)));
        Row& rRow = *vRows.back();
        // fields in the row
        static unsigned const nFieldCount = std::size(vFieldInfo);
        // skipping other (invisible) rows
        while (iField != nFieldCount && vFieldInfo[iField].eRow != eRow)
            ++iField;
        // fields in the row
        rRow.nFirstField = vFields.size();
        for ( ; iField != nFieldCount && vFieldInfo[iField].eRow == eRow; ++iField)
        {
            // creating edit field
            vFields.push_back(std::make_shared<Field>(
                m_xBuilder->weld_entry(vFieldInfo[iField].pEditId), iField));
            // "short name" field?
            if (vFieldInfo[iField].nUserOptionsId == UserOptToken::ID)
            {
                nNameRow = vRows.size() - 1;
                nShortNameField = vFields.size() - 1;
            }
        }
        rRow.nLastField = vFields.size();
    }
}

void SvxGeneralTabPage::InitCryptography()
{
#if HAVE_FEATURE_GPGME
    m_xCryptoFrame->show();

    uno::Reference< xml::crypto::XSEInitializer > xSEInitializer;
    try
    {
        xSEInitializer = xml::crypto::GPGSEInitializer::create( comphelper::getProcessComponentContext() );
        uno::Reference<xml::crypto::XXMLSecurityContext> xSC = xSEInitializer->createSecurityContext( OUString() );
        if (xSC.is())
        {
            uno::Reference<xml::crypto::XSecurityEnvironment> xSE = xSC->getSecurityEnvironment();
            uno::Sequence<uno::Reference<security::XCertificate>> xCertificates = xSE->getPersonalCertificates();

            if (xCertificates.hasElements())
            {
                for (auto& xCert : asNonConstRange(xCertificates))
                {
                    m_xSigningKeyLB->append_text( xCert->getIssuerName());
                    m_xEncryptionKeyLB->append_text( xCert->getIssuerName());
                }
            }

             //tdf#115015: wrap checkbox text and listboxes if necessary
            int nPrefWidth(m_xEncryptToSelfCB->get_preferred_size().Width());
            int nMaxWidth = m_xEncryptToSelfCB->get_approximate_digit_width() * 40;
            if (nPrefWidth > nMaxWidth)
            {
                 m_xSigningKeyLB->set_size_request(nMaxWidth, -1);
                 m_xEncryptionKeyLB->set_size_request(nMaxWidth, -1);
                 m_xEncryptToSelfCB->set_label_wrap(true);
                 m_xEncryptToSelfCB->set_size_request(nMaxWidth, -1);
            }
        }
    }
    catch ( uno::Exception const & )
    {}
#endif

}

void SvxGeneralTabPage::SetLinks ()
{
    // link for updating the initials
    Link<weld::Entry&,void> aLink = LINK( this, SvxGeneralTabPage, ModifyHdl_Impl );
    Row& rNameRow = *vRows[nNameRow];
    for (unsigned i = rNameRow.nFirstField; i != rNameRow.nLastField - 1; ++i)
        vFields[i]->xEdit->connect_changed(aLink);
}


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

OUString SvxGeneralTabPage::GetAllStrings()
{
    OUString sAllStrings;
    OUString labels[]
        = { "label1",     "companyft",         "nameft",          "rusnameft",
            "eastnameft", "streetft",          "russtreetft",     "icityft",
            "cityft",     "countryft",         "titleft",         "phoneft",
            "faxft",      "cryptographylabel", "signingkeylabel", "encryptionkeylabel" };

    for (const auto& label : labels)
    {
        if (const auto& pString = m_xBuilder->weld_label(label))
            sAllStrings += pString->get_label() + " ";
    }

    sAllStrings += m_xUseDataCB->get_label() + " " + m_xEncryptToSelfCB->get_label() + " ";

    return sAllStrings.replaceAll("_", "");
}

bool SvxGeneralTabPage::FillItemSet( SfxItemSet* )
{
    // remove leading and trailing whitespaces
    for (auto const & i: vFields)
        i->xEdit->set_text(comphelper::string::strip(i->xEdit->get_text(), ' '));

    bool bModified = false;
    bModified |= GetData_Impl();
    if (m_xUseDataCB->get_active() != officecfg::Office::Common::Save::Document::UseUserData::get())
    {
        auto xChanges = comphelper::ConfigurationChanges::create();
        officecfg::Office::Common::Save::Document::UseUserData::set(m_xUseDataCB->get_active(), xChanges);
        xChanges->commit();
        bModified = true;
    }
    return bModified;
}

void SvxGeneralTabPage::Reset( const SfxItemSet* rSet )
{
    SetData_Impl();

    if (rSet->GetItemState(SID_FIELD_GRABFOCUS) == SfxItemState::SET)
    {
        EditPosition nField = static_cast<EditPosition>(rSet->Get(SID_FIELD_GRABFOCUS).GetValue());
        if (nField != EditPosition::UNKNOWN)
        {
            for (auto const & i: vFields)
                if (nField == vFieldInfo[i->iField].nGrabFocusId)
                    i->xEdit->grab_focus();
        }
        else
            vFields.front()->xEdit->grab_focus();
    }

    m_xUseDataCB->set_active(officecfg::Office::Common::Save::Document::UseUserData::get());
}


// ModifyHdl_Impl()
// This handler updates the initials (short name)
// when one of the name fields was updated.
IMPL_LINK( SvxGeneralTabPage, ModifyHdl_Impl, weld::Entry&, rEdit, void )
{
    // short name field and row
    Field& rShortName = *vFields[nShortNameField];
    Row& rNameRow = *vRows[nNameRow];
    // number of initials
    unsigned const nInits = rNameRow.nLastField - rNameRow.nFirstField - 1;
    // which field was updated? (in rNameRow)
    unsigned nField = nInits;
    for (unsigned i = 0; i != nInits; ++i)
    {
        if (vFields[rNameRow.nFirstField + i]->xEdit.get() == &rEdit)
            nField = i;
    }
    // updating the initial
    if (!(nField < nInits && rShortName.xEdit->get_sensitive()))
        return;

    OUString sShortName = rShortName.xEdit->get_text();
    // clear short name if it contains more characters than the number of initials
    if (o3tl::make_unsigned(sShortName.getLength()) > nInits)
    {
        rShortName.xEdit->set_text(OUString());
    }
    while (o3tl::make_unsigned(sShortName.getLength()) < nInits)
        sShortName += " ";
    OUString sName = rEdit.get_text();
    OUString sLetter = sName.isEmpty()
        ? OUString(u' ') : sName.copy(0, 1);
    rShortName.xEdit->set_text(sShortName.replaceAt(nField, 1, sLetter).trim());
}


bool SvxGeneralTabPage::GetData_Impl()
{
    // updating
    SvtUserOptions aUserOpt;
    for (auto const & i: vFields)
        aUserOpt.SetToken(
            vFieldInfo[i->iField].nUserOptionsId,
            i->xEdit->get_text()
        );

    // modified?
    bool bModified = false;
    for (auto const & i: vFields)
    {
        if (i->xEdit->get_value_changed_from_saved())
        {
            bModified = true;
            break;
        }
    }

#if HAVE_FEATURE_GPGME
    OUString aSK = m_xSigningKeyLB->get_active() == 0 ? OUString() //i.e. no key
                       : m_xSigningKeyLB->get_active_text();
    OUString aEK = m_xEncryptionKeyLB->get_active() == 0 ? OUString()
                       : m_xEncryptionKeyLB->get_active_text();

    aUserOpt.SetToken( UserOptToken::SigningKey, aSK );
    aUserOpt.SetToken( UserOptToken::EncryptionKey, aEK );
    aUserOpt.SetBoolValue( UserOptToken::EncryptToSelf, m_xEncryptToSelfCB->get_active() );

    bModified |= m_xSigningKeyLB->get_value_changed_from_saved() ||
                 m_xEncryptionKeyLB->get_value_changed_from_saved() ||
                 m_xEncryptToSelfCB->get_state_changed_from_saved();
#endif

    return bModified;
}


void SvxGeneralTabPage::SetData_Impl()
{
    // updating and disabling edit boxes
    SvtUserOptions aUserOpt;
    for (auto const & i: vRows)
    {
        Row& rRow = *i;
        // the label is enabled if any of its edit fields are enabled
        bool bEnableLabel = false;
        for (unsigned iField = rRow.nFirstField; iField != rRow.nLastField; ++iField)
        {
            Field& rField = *vFields[iField];
            // updating content
            UserOptToken const nToken = vFieldInfo[rField.iField].nUserOptionsId;
            rField.xEdit->set_text(aUserOpt.GetToken(nToken));
            // is enabled?
            bool const bEnableEdit = !aUserOpt.IsTokenReadonly(nToken);
            rField.xEdit->set_sensitive(bEnableEdit);
            bEnableLabel = bEnableLabel || bEnableEdit;
        }
        rRow.xLabel->set_sensitive(bEnableLabel);
        rRow.xLockImg->set_visible(!bEnableLabel);
    }

    // saving
    for (auto const & i: vFields)
        i->xEdit->save_value();

    //enabling and disabling remaining fields
    bool bEnable = !officecfg::Office::Common::Save::Document::UseUserData::isReadOnly();
    m_xUseDataCB->set_sensitive(bEnable);
    m_xUseDataImg->set_visible(!bEnable);

#if HAVE_FEATURE_GPGME
    bEnable = !aUserOpt.IsTokenReadonly(UserOptToken::SigningKey);
    m_xSigningKeyLB->set_sensitive(bEnable);
    m_xSigningKeyFT->set_sensitive(bEnable);
    m_xSigningKeyImg->set_visible(!bEnable);

    bEnable = !aUserOpt.IsTokenReadonly(UserOptToken::EncryptionKey);
    m_xEncryptionKeyLB->set_sensitive(bEnable);
    m_xEncryptionKeyFT->set_sensitive(bEnable);
    m_xEncryptionKeyImg->set_visible(!bEnable);

    bEnable = !aUserOpt.IsTokenReadonly(UserOptToken::EncryptToSelf);
    m_xEncryptToSelfCB->set_sensitive(bEnable);
    m_xEncryptToSelfImg->set_visible(!bEnable);

    OUString aSK = aUserOpt.GetToken(UserOptToken::SigningKey);
    aSK.isEmpty() ? m_xSigningKeyLB->set_active( 0 ) //i.e. 'No Key'
                  : m_xSigningKeyLB->set_active_text( aSK );

    OUString aEK = aUserOpt.GetToken(UserOptToken::EncryptionKey);
    aEK.isEmpty() ? m_xEncryptionKeyLB->set_active( 0 ) //i.e. 'No Key'
                  : m_xEncryptionKeyLB->set_active_text( aEK );

    m_xEncryptToSelfCB->set_active( aUserOpt.GetEncryptToSelf() );
#endif
}


DeactivateRC SvxGeneralTabPage::DeactivatePage( SfxItemSet* pSet_ )
{
    if ( pSet_ )
        FillItemSet( pSet_ );
    return DeactivateRC::LeavePage;
}

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