/* -*- 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 <SdtHelper.hxx>
#include <com/sun/star/drawing/XControlShape.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <cppuhelper/exc_hlp.hxx>
#include <editeng/unoprnms.hxx>
#include <vcl/svapp.hxx>
#include <unotools/datetime.hxx>
#include <comphelper/sequence.hxx>

namespace writerfilter
{
namespace dmapper
{

using namespace ::com::sun::star;

/// w:sdt's w:dropDownList doesn't have width, so guess the size based on the longest string.
awt::Size lcl_getOptimalWidth(StyleSheetTablePtr pStyleSheet, OUString& rDefault, std::vector<OUString>& rItems)
{
    OUString aLongest = rDefault;
    sal_Int32 nHeight = 0;
    for (size_t i = 0; i < rItems.size(); ++i)
        if (rItems[i].getLength() > aLongest.getLength())
            aLongest = rItems[i];

    MapMode aMap(MAP_100TH_MM);
    OutputDevice* pOut = Application::GetDefaultDevice();
    pOut->Push(PushFlags::FONT | PushFlags::MAPMODE);

    PropertyMapPtr pDefaultCharProps = pStyleSheet->GetDefaultCharProps();
    vcl::Font aFont(pOut->GetFont());
    boost::optional<PropertyMap::Property> aFontName = pDefaultCharProps->getProperty(PROP_CHAR_FONT_NAME);
    if (aFontName)
        aFont.SetName(aFontName->second.get<OUString>());
    boost::optional<PropertyMap::Property> aHeight = pDefaultCharProps->getProperty(PROP_CHAR_HEIGHT);
    if (aHeight)
    {
        nHeight = aHeight->second.get<double>() * 35; // points -> mm100
        aFont.SetSize(Size(0, nHeight));
    }
    pOut->SetFont(aFont);
    pOut->SetMapMode(aMap);
    sal_Int32 nWidth = pOut->GetTextWidth(aLongest);

    pOut->Pop();

    // Border: see PDFWriterImpl::drawFieldBorder(), border size is font height / 4,
    // so additional width / height needed is height / 2.
    sal_Int32 nBorder = nHeight / 2;

    // Width: space for the text + the square having the dropdown arrow.
    return awt::Size(nWidth + nBorder + nHeight, nHeight + nBorder);
}

SdtHelper::SdtHelper(DomainMapper_Impl& rDM_Impl)
    : m_rDM_Impl(rDM_Impl)
    , m_bHasElements(false)
    , m_bOutsideAParagraph(false)
{
}

SdtHelper::~SdtHelper()
{
}

void SdtHelper::createDropDownControl()
{
    OUString aDefaultText = m_aSdtTexts.makeStringAndClear();
    uno::Reference<awt::XControlModel> xControlModel(m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.form.component.ComboBox"), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
    xPropertySet->setPropertyValue("DefaultText", uno::makeAny(aDefaultText));
    xPropertySet->setPropertyValue("Dropdown", uno::makeAny(sal_True));
    xPropertySet->setPropertyValue("StringItemList", uno::makeAny(comphelper::containerToSequence(m_aDropDownItems)));

    createControlShape(lcl_getOptimalWidth(m_rDM_Impl.GetStyleSheetTable(), aDefaultText, m_aDropDownItems), xControlModel);
    m_aDropDownItems.clear();
}

void SdtHelper::createDateControl(OUString& rContentText, const beans::PropertyValue& rCharFormat)
{
    uno::Reference<awt::XControlModel> xControlModel;
    try
    {
        xControlModel.set(m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.form.component.DateField"), uno::UNO_QUERY_THROW);
    }
    catch (css::uno::RuntimeException&)
    {
        throw;
    }
    catch (css::uno::Exception& e)
    {
        css::uno::Any a(cppu::getCaughtException());
        throw css::lang::WrappedTargetRuntimeException("wrapped " + a.getValueTypeName() + ": " + e.Message, css::uno::Reference<css::uno::XInterface>(), a);
    }
    uno::Reference<beans::XPropertySet> xPropertySet(
        xControlModel, uno::UNO_QUERY_THROW);

    xPropertySet->setPropertyValue("Dropdown", uno::makeAny(sal_True));

    // See com/sun/star/awt/UnoControlDateFieldModel.idl, DateFormat; sadly there are no constants
    sal_Int16 nDateFormat = 0;
    OUString sDateFormat = m_sDateFormat.makeStringAndClear();
    if (sDateFormat == "M/d/yyyy" || sDateFormat == "M.d.yyyy")
        // Approximate with MM.dd.yyy
        nDateFormat = 8;
    else
        // Set default format, so at least the date picker is created.
        SAL_WARN("writerfilter", "unhandled w:dateFormat value");
    xPropertySet->setPropertyValue("DateFormat", uno::makeAny(nDateFormat));

    util::Date aDate;
    util::DateTime aDateTime;
    if (utl::ISO8601parseDateTime(m_sDate.makeStringAndClear(), aDateTime))
    {
        utl::extractDate(aDateTime, aDate);
        xPropertySet->setPropertyValue("Date", uno::makeAny(aDate));
    }
    else
        xPropertySet->setPropertyValue("HelpText", uno::makeAny(rContentText));

    // append date format to grab bag
    comphelper::SequenceAsHashMap aGrabBag;
    aGrabBag["OriginalDate"] <<= aDate;
    aGrabBag["OriginalContent"] <<= rContentText;
    aGrabBag["DateFormat"] <<= sDateFormat;
    aGrabBag["Locale"] <<= m_sLocale.makeStringAndClear();
    aGrabBag["CharFormat"] <<= rCharFormat.Value;
    // merge in properties like ooxml:CT_SdtPr_alias and friends.
    aGrabBag.update(comphelper::SequenceAsHashMap(comphelper::containerToSequence(m_aGrabBag)));
    // and empty the property list, so they won't end up on the next sdt as well
    m_aGrabBag.clear();

    std::vector<OUString> aItems;
    createControlShape(lcl_getOptimalWidth(m_rDM_Impl.GetStyleSheetTable(), rContentText, aItems), xControlModel, aGrabBag.getAsConstPropertyValueList());
}

void SdtHelper::createControlShape(awt::Size aSize, uno::Reference<awt::XControlModel> const& xControlModel)
{
    createControlShape(aSize, xControlModel, uno::Sequence<beans::PropertyValue>());
}

void SdtHelper::createControlShape(awt::Size aSize, uno::Reference<awt::XControlModel> const& xControlModel, const uno::Sequence<beans::PropertyValue>& rGrabBag)
{
    uno::Reference<drawing::XControlShape> xControlShape(m_rDM_Impl.GetTextFactory()->createInstance("com.sun.star.drawing.ControlShape"), uno::UNO_QUERY);
    xControlShape->setSize(aSize);
    xControlShape->setControl(xControlModel);

    uno::Reference<beans::XPropertySet> xPropertySet(xControlShape, uno::UNO_QUERY);
    xPropertySet->setPropertyValue("VertOrient", uno::makeAny(text::VertOrientation::CENTER));

    if (rGrabBag.hasElements())
        xPropertySet->setPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, uno::makeAny(rGrabBag));

    uno::Reference<text::XTextContent> xTextContent(xControlShape, uno::UNO_QUERY);
    m_rDM_Impl.appendTextContent(xTextContent, uno::Sequence< beans::PropertyValue >());
    m_bHasElements = true;
}

void SdtHelper::appendToInteropGrabBag(const beans::PropertyValue& rValue)
{
    m_aGrabBag.push_back(rValue);
}

uno::Sequence<beans::PropertyValue> SdtHelper::getInteropGrabBagAndClear()
{
    uno::Sequence<beans::PropertyValue> aRet = comphelper::containerToSequence(m_aGrabBag);
    m_aGrabBag.clear();
    return aRet;
}

bool SdtHelper::isInteropGrabBagEmpty()
{
    return m_aGrabBag.empty();
}

sal_Int32 SdtHelper::getInteropGrabBagSize()
{
    return m_aGrabBag.size();
}

bool SdtHelper::containedInInteropGrabBag(const OUString& rValueName)
{
    for (size_t i=0; i < m_aGrabBag.size(); ++i)
        if (m_aGrabBag[i].Name == rValueName)
            return true;

    return false;
}

} // namespace dmapper
} // namespace writerfilter

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