/* -*- 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 "vbadocumentproperties.hxx"
#include <cppuhelper/implbase.hxx>
#include <sal/log.hxx>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/XPropertyContainer.hpp>
#include <ooo/vba/word/WdBuiltInProperty.hpp>
#include <ooo/vba/office/MsoDocProperties.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <memory>
#include "wordvbahelper.hxx"
#include <fesh.hxx>
#include <docsh.hxx>
#include <unotxdoc.hxx>
#include <utility>

using namespace ::ooo::vba;
using namespace css;

/// @throws lang::IllegalArgumentException
static sal_Int8 lcl_toMSOPropType( const uno::Type& aType )
{
    sal_Int16 msoType = office::MsoDocProperties::msoPropertyTypeString;

    switch ( aType.getTypeClass() )
    {
        case uno::TypeClass_BOOLEAN:
            msoType =  office::MsoDocProperties::msoPropertyTypeBoolean;
            break;
        case uno::TypeClass_FLOAT:
            msoType =  office::MsoDocProperties::msoPropertyTypeFloat;
            break;
        case uno::TypeClass_STRUCT: // Assume date
            msoType =  office::MsoDocProperties::msoPropertyTypeDate;
            break;
        case  uno::TypeClass_BYTE:
        case  uno::TypeClass_SHORT:
        case  uno::TypeClass_LONG:
        case  uno::TypeClass_HYPER:
            msoType =  office::MsoDocProperties::msoPropertyTypeNumber;
            break;
        default:
            throw lang::IllegalArgumentException();
    }
    return msoType;
}

namespace {

class PropertGetSetHelper
{
protected:
    rtl::Reference< SwXTextDocument > m_xModel;
    uno::Reference<document::XDocumentProperties> m_xDocProps;
public:
    explicit PropertGetSetHelper( rtl::Reference< SwXTextDocument > xModel ) : m_xModel(std::move( xModel ))
    {
        m_xDocProps.set(m_xModel->getDocumentProperties(), uno::UNO_SET_THROW);
    }
    virtual ~PropertGetSetHelper() {}
    virtual uno::Any getPropertyValue( const OUString& rPropName ) = 0;
    virtual void setPropertyValue( const OUString& rPropName, const uno::Any& aValue ) = 0;
    uno::Reference< beans::XPropertySet > getUserDefinedProperties() {
        return uno::Reference<beans::XPropertySet>(
                m_xDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
    }

};

class BuiltinPropertyGetSetHelper : public PropertGetSetHelper
{
public:
    explicit BuiltinPropertyGetSetHelper( const rtl::Reference< SwXTextDocument >& xModel ) :PropertGetSetHelper( xModel )
    {
    }
    virtual uno::Any getPropertyValue( const OUString& rPropName ) override
    {
        if ( rPropName == "EditingDuration" )
        {
            sal_Int32 const nSecs = m_xDocProps->getEditingDuration();
            return uno::Any( nSecs/60 ); // minutes
        }
        else if ("Title" == rPropName)
        {
            return uno::Any(m_xDocProps->getTitle());
        }
        else if ("Subject" == rPropName)
        {
            return uno::Any(m_xDocProps->getSubject());
        }
        else if ("Author" == rPropName)
        {
            return uno::Any(m_xDocProps->getAuthor());
        }
        else if ("Keywords" == rPropName)
        {
            return uno::Any(m_xDocProps->getKeywords());
        }
        else if ("Description" == rPropName)
        {
            return uno::Any(m_xDocProps->getDescription());
        }
        else if ("Template" == rPropName)
        {
            return uno::Any(m_xDocProps->getTemplateName());
        }
        else if ("ModifiedBy" == rPropName)
        {
            return uno::Any(m_xDocProps->getModifiedBy());
        }
        else if ("Generator" == rPropName)
        {
            return uno::Any(m_xDocProps->getGenerator());
        }
        else if ("PrintDate" == rPropName)
        {
            return uno::Any(m_xDocProps->getPrintDate());
        }
        else if ("CreationDate" == rPropName)
        {
            return uno::Any(m_xDocProps->getCreationDate());
        }
        else if ("ModifyDate" == rPropName)
        {
            return uno::Any(m_xDocProps->getModificationDate());
        }
        else if ("AutoloadURL" == rPropName)
        {
            return uno::Any(m_xDocProps->getAutoloadURL());
        }
        else
        {
            // fall back to user-defined properties
            return getUserDefinedProperties()->getPropertyValue(rPropName);
        }
    }
    virtual void setPropertyValue( const OUString& rPropName, const uno::Any& aValue ) override
    {
        if ("EditingDuration" == rPropName)
        {
            sal_Int32 nMins = 0;
            if (aValue >>= nMins)
            {
                m_xDocProps->setEditingDuration(nMins * 60); // convert minutes
            }
        }
        else if ("Title" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setTitle(str);
            }
        }
        else if ("Subject" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setSubject(str);
            }
        }
        else if ("Author" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setAuthor(str);
            }
        }
        else if ("Keywords" == rPropName)
        {
            uno::Sequence<OUString> keywords;
            if (aValue >>= keywords)
            {
                m_xDocProps->setKeywords(keywords);
            }
        }
        else if ("Description" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setDescription(str);
            }
        }
        else if ("Template" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setTemplateName(str);
            }
        }
        else if ("ModifiedBy" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setModifiedBy(str);
            }
        }
        else if ("Generator" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                return m_xDocProps->setGenerator(str);
            }
        }
        else if ("PrintDate" == rPropName)
        {
            util::DateTime dt;
            if (aValue >>= dt)
            {
                m_xDocProps->setPrintDate(dt);
            }
        }
        else if ("CreationDate" == rPropName)
        {
            util::DateTime dt;
            if (aValue >>= dt)
            {
                m_xDocProps->setCreationDate(dt);
            }
        }
        else if ("ModifyDate" == rPropName)
        {
            util::DateTime dt;
            if (aValue >>= dt)
            {
                m_xDocProps->setModificationDate(dt);
            }
        }
        else if ("AutoloadURL" == rPropName)
        {
            OUString str;
            if (aValue >>= str)
            {
                m_xDocProps->setAutoloadURL(str);
            }
        }
        else
        {
            // fall back to user-defined properties
            getUserDefinedProperties()->setPropertyValue(rPropName, aValue);
        }
    }
};

class CustomPropertyGetSetHelper : public BuiltinPropertyGetSetHelper
{
public:
    explicit CustomPropertyGetSetHelper( const rtl::Reference< SwXTextDocument >& xModel ) :BuiltinPropertyGetSetHelper( xModel )
    {
    }
    virtual uno::Any getPropertyValue( const OUString& rPropName ) override
    {
        return getUserDefinedProperties()->getPropertyValue(rPropName);
    }
    virtual void setPropertyValue(
            const OUString& rPropName, const uno::Any& rValue) override
    {
        return getUserDefinedProperties()->setPropertyValue(rPropName, rValue);
    }
};

class StatisticPropertyGetSetHelper : public PropertGetSetHelper
{
    SwDocShell* mpDocShell;
public:
    explicit StatisticPropertyGetSetHelper( const rtl::Reference< SwXTextDocument >& xModel ) :PropertGetSetHelper( xModel ) , mpDocShell( nullptr )
    {
        mpDocShell = m_xModel->GetDocShell();
    }
    virtual uno::Any getPropertyValue( const OUString& rPropName ) override
    {
        try
        {
            // Characters, ParagraphCount & WordCount are available from
            // the model ( and additionally these also update the statics object )
            return m_xModel->getPropertyValue( rPropName );
        }
        catch (const uno::Exception&)
        {
            TOOLS_WARN_EXCEPTION("sw.vba", "");
        }
        uno::Any aReturn;
        if ( rPropName == "LineCount" ) // special processing needed
        {
            if ( mpDocShell )
            {
                if (SwFEShell* pFEShell = mpDocShell->GetFEShell())
                    aReturn <<= pFEShell->GetLineCount();
            }
        }
        else
        {
            uno::Sequence< beans::NamedValue > const stats(
                m_xDocProps->getDocumentStatistics());

            auto pStat = std::find_if(stats.begin(), stats.end(),
                [&rPropName](const beans::NamedValue& rStat) { return rPropName == rStat.Name; });
            if (pStat == stats.end())
                throw uno::RuntimeException(); // bad Property

            aReturn = pStat->Value;
        }
        return aReturn;
    }

    virtual void setPropertyValue( const OUString& rPropName, const uno::Any& aValue ) override
    {
        uno::Sequence< beans::NamedValue > stats(
                m_xDocProps->getDocumentStatistics());

        auto [begin, end] = asNonConstRange(stats);
        auto pStat = std::find_if(begin, end,
            [&rPropName](const beans::NamedValue& rStat) { return rPropName == rStat.Name; });
        if (pStat != end)
        {
            pStat->Value = aValue;
            m_xDocProps->setDocumentStatistics(stats);
        }
    }
};

class DocPropInfo
{
public:
    OUString msMSODesc;
    OUString msOOOPropName;
    std::shared_ptr< PropertGetSetHelper > mpPropGetSetHelper;

    static DocPropInfo createDocPropInfo( const OUString& sDesc, const OUString& sPropName, std::shared_ptr< PropertGetSetHelper > const & rHelper )
    {
        DocPropInfo aItem;
        aItem.msMSODesc = sDesc;
        aItem.msOOOPropName = sPropName;
        aItem.mpPropGetSetHelper = rHelper;
        return aItem;
    }

    static DocPropInfo createDocPropInfo( const char* sDesc, const char* sPropName, std::shared_ptr< PropertGetSetHelper > const & rHelper )
    {
        return createDocPropInfo( OUString::createFromAscii( sDesc ), OUString::createFromAscii( sPropName ), rHelper );
    }
    uno::Any getValue()
    {
        if ( mpPropGetSetHelper )
            return mpPropGetSetHelper->getPropertyValue( msOOOPropName );
        return uno::Any();
    }
    void setValue( const uno::Any& rValue )
    {
        if ( mpPropGetSetHelper )
            mpPropGetSetHelper->setPropertyValue( msOOOPropName, rValue );
    }
    uno::Reference< beans::XPropertySet > getUserDefinedProperties()
    {
        uno::Reference< beans::XPropertySet > xProps;
        if ( mpPropGetSetHelper )
            return mpPropGetSetHelper->getUserDefinedProperties();
        return xProps;
    }
};

}

typedef std::unordered_map< sal_Int32, DocPropInfo > MSOIndexToOODocPropInfo;

namespace {

class BuiltInIndexHelper
{
    MSOIndexToOODocPropInfo m_docPropInfoMap;

public:
    explicit BuiltInIndexHelper( const rtl::Reference< SwXTextDocument >& xModel )
    {
        auto aStandardHelper = std::make_shared<BuiltinPropertyGetSetHelper>( xModel );
        auto aUsingStatsHelper = std::make_shared<StatisticPropertyGetSetHelper>( xModel );

        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyTitle ] = DocPropInfo::createDocPropInfo( "Title", "Title", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertySubject ] = DocPropInfo::createDocPropInfo( "Subject", "Subject", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyAuthor ] = DocPropInfo::createDocPropInfo( "Author", "Author", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyKeywords ] = DocPropInfo::createDocPropInfo( "Keywords", "Keywords", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyComments ] = DocPropInfo::createDocPropInfo( "Comments", "Description", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyTemplate ] = DocPropInfo::createDocPropInfo( "Template", "Template", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyLastAuthor ] = DocPropInfo::createDocPropInfo( "Last author", "ModifiedBy", aStandardHelper ); // doesn't seem to exist - throw or return nothing ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyRevision ] = DocPropInfo::createDocPropInfo( "Revision number", "EditingCycles", aStandardHelper ); // doesn't seem to exist - throw or return nothing ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyAppName ] = DocPropInfo::createDocPropInfo( "Application name", "Generator", aStandardHelper ); // doesn't seem to exist - throw or return nothing ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyTimeLastPrinted ] = DocPropInfo::createDocPropInfo( "Last print date", "PrintDate", aStandardHelper ); // doesn't seem to exist - throw or return nothing ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyTimeCreated ] = DocPropInfo::createDocPropInfo( "Creation date", "CreationDate", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyTimeLastSaved ] = DocPropInfo::createDocPropInfo( "Last save time", "ModifyDate", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyVBATotalEdit ] = DocPropInfo::createDocPropInfo( "Total editing time", "EditingDuration", aStandardHelper ); // Not sure if this is correct
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyPages ] = DocPropInfo::createDocPropInfo( "Number of pages", "PageCount", aUsingStatsHelper ); // special handling required ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyWords ] = DocPropInfo::createDocPropInfo( "Number of words", "WordCount", aUsingStatsHelper ); // special handling require ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyCharacters ] = DocPropInfo::createDocPropInfo( "Number of characters", "CharacterCount", aUsingStatsHelper ); // special handling required ?
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertySecurity ] = DocPropInfo::createDocPropInfo( "Security", "", aStandardHelper ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyCategory ] = DocPropInfo::createDocPropInfo( "Category", "Category", aStandardHelper ); // hacked in
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyFormat ] = DocPropInfo::createDocPropInfo( "Format", "", aStandardHelper ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyManager ] = DocPropInfo::createDocPropInfo( "Manager", "Manager", aStandardHelper ); // hacked in
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyCompany ] = DocPropInfo::createDocPropInfo( "Company", "Company", aStandardHelper ); // hacked in
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyBytes ] = DocPropInfo::createDocPropInfo( "Number of bytes", "", aStandardHelper ); // doesn't seem to exist - size on disk exists ( for an already saved document ) perhaps it will do ( or we need something else )
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyLines ] = DocPropInfo::createDocPropInfo( "Number of lines", "LineCount", aUsingStatsHelper ); // special handling
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyParas ] = DocPropInfo::createDocPropInfo( "Number of paragraphs", "ParagraphCount", aUsingStatsHelper ); // special handling
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertySlides ] = DocPropInfo::createDocPropInfo( "Number of slides", "" , aStandardHelper ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyNotes ] = DocPropInfo::createDocPropInfo( "Number of notes", "", aStandardHelper ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyHiddenSlides ] = DocPropInfo::createDocPropInfo("Number of hidden Slides", "", aStandardHelper  ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyMMClips ] = DocPropInfo::createDocPropInfo( "Number of multimedia clips", "", aStandardHelper ); // doesn't seem to exist
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyHyperlinkBase ] = DocPropInfo::createDocPropInfo( "Hyperlink base", "AutoloadURL", aStandardHelper );
        m_docPropInfoMap[ word::WdBuiltInProperty::wdPropertyCharsWSpaces ] = DocPropInfo::createDocPropInfo( "Number of characters (with spaces)", "", aStandardHelper ); // doesn't seem to be supported
    }

    MSOIndexToOODocPropInfo& getDocPropInfoMap() { return m_docPropInfoMap; }
};

}

typedef InheritedHelperInterfaceWeakImpl< ooo::vba::XDocumentProperty > SwVbaDocumentProperty_BASE;

namespace {

class SwVbaBuiltInDocumentProperty : public SwVbaDocumentProperty_BASE
{
protected:
    DocPropInfo mPropInfo;
public:
    SwVbaBuiltInDocumentProperty(  const uno::Reference< ov::XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, DocPropInfo  rInfo );
    // XDocumentProperty
    virtual void SAL_CALL Delete(  ) override;
    virtual OUString SAL_CALL getName(  ) override;
    virtual void SAL_CALL setName( const OUString& Name ) override;
    virtual ::sal_Int8 SAL_CALL getType(  ) override;
    virtual void SAL_CALL setType( ::sal_Int8 Type ) override;
    virtual sal_Bool SAL_CALL getLinkToContent(  ) override;
    virtual void SAL_CALL setLinkToContent( sal_Bool LinkToContent ) override;
    virtual uno::Any SAL_CALL getValue(  ) override;
    virtual void SAL_CALL setValue( const uno::Any& Value ) override;
    virtual OUString SAL_CALL getLinkSource(  ) override;
    virtual void SAL_CALL setLinkSource( const OUString& LinkSource ) override;
    //XDefaultProperty
    virtual OUString SAL_CALL getDefaultPropertyName(  ) override { return u"Value"_ustr; }
    // XHelperInterface
    virtual OUString getServiceImplName() override;
    virtual uno::Sequence<OUString> getServiceNames() override;
};

class SwVbaCustomDocumentProperty : public SwVbaBuiltInDocumentProperty
{
public:

    SwVbaCustomDocumentProperty(  const uno::Reference< ov::XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const DocPropInfo& rInfo );

    virtual sal_Bool SAL_CALL getLinkToContent(  ) override;
    virtual void SAL_CALL setLinkToContent( sal_Bool LinkToContent ) override;

    virtual OUString SAL_CALL getLinkSource(  ) override;
    virtual void SAL_CALL setLinkSource( const OUString& LinkSource ) override;
    virtual void SAL_CALL Delete(  ) override;
    virtual void SAL_CALL setName( const OUString& Name ) override;
    virtual void SAL_CALL setType( ::sal_Int8 Type ) override;

};

}

SwVbaCustomDocumentProperty::SwVbaCustomDocumentProperty(  const uno::Reference< ov::XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const DocPropInfo& rInfo ) : SwVbaBuiltInDocumentProperty( xParent, xContext, rInfo )
{
}

sal_Bool
SwVbaCustomDocumentProperty::getLinkToContent(  )
{
    // #FIXME we need to store the link content somewhere
    return false;
}

void
SwVbaCustomDocumentProperty::setLinkToContent( sal_Bool /*bLinkContent*/ )
{
}

OUString
SwVbaCustomDocumentProperty::getLinkSource(  )
{
    // #FIXME we need to store the link content somewhere
    return OUString();
}

void
SwVbaCustomDocumentProperty::setLinkSource( const OUString& /*rsLinkContent*/ )
{
    // #FIXME we need to store the link source somewhere
}

void SAL_CALL
SwVbaCustomDocumentProperty::setName( const OUString& /*Name*/ )
{
    // setName on existing property ?
    // #FIXME
    // do we need to delete existing property and create a new one?
}

void SAL_CALL
SwVbaCustomDocumentProperty::setType( ::sal_Int8 /*Type*/ )
{
    // setType, do we need to do a conversion?
    // #FIXME the underlying value needs to be changed to the new type
}

void SAL_CALL
SwVbaCustomDocumentProperty::Delete(  )
{
    uno::Reference< beans::XPropertyContainer > xContainer(
            mPropInfo.getUserDefinedProperties(), uno::UNO_QUERY_THROW);
    xContainer->removeProperty( getName() );
}

SwVbaBuiltInDocumentProperty::SwVbaBuiltInDocumentProperty( const uno::Reference< ov::XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, DocPropInfo  rInfo ) : SwVbaDocumentProperty_BASE( xParent, xContext ), mPropInfo(std::move( rInfo ))
{
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::Delete(  )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

OUString SAL_CALL
SwVbaBuiltInDocumentProperty::getName(  )
{
    return mPropInfo.msMSODesc;
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::setName( const OUString& )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

::sal_Int8 SAL_CALL
SwVbaBuiltInDocumentProperty::getType(  )
{
    return lcl_toMSOPropType( getValue().getValueType() );
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::setType( ::sal_Int8 /*Type*/ )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

sal_Bool SAL_CALL
SwVbaBuiltInDocumentProperty::getLinkToContent(  )
{
    return false; // built-in always false
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::setLinkToContent( sal_Bool /*LinkToContent*/ )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

uno::Any SAL_CALL
SwVbaBuiltInDocumentProperty::getValue(  )
{
    uno::Any aRet = mPropInfo.getValue();
    if ( !aRet.hasValue() )
        throw uno::RuntimeException();
    return aRet;
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::setValue( const uno::Any& Value )
{
    mPropInfo.setValue( Value );
}

OUString SAL_CALL
SwVbaBuiltInDocumentProperty::getLinkSource(  )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

void SAL_CALL
SwVbaBuiltInDocumentProperty::setLinkSource( const OUString& /*LinkSource*/ )
{
    // not valid for Builtin
    throw uno::RuntimeException();
}

OUString
SwVbaBuiltInDocumentProperty::getServiceImplName()
{
    return u"SwVbaBuiltinDocumentProperty"_ustr;
}

uno::Sequence<OUString>
SwVbaBuiltInDocumentProperty::getServiceNames()
{
    static uno::Sequence< OUString > const aServiceNames
    {
        u"ooo.vba.word.DocumentProperty"_ustr
    };
    return aServiceNames;
}
typedef ::cppu::WeakImplHelper< css::container::XIndexAccess
        ,css::container::XNameAccess
        ,css::container::XEnumerationAccess
        > PropertiesImpl_BASE;

typedef std::unordered_map< sal_Int32, uno::Reference< XDocumentProperty > > DocProps;

namespace {

class DocPropEnumeration : public ::cppu::WeakImplHelper< css::container::XEnumeration >
{
    DocProps mDocProps;
    DocProps::iterator mIt;
public:

    explicit DocPropEnumeration( DocProps&& rProps ) : mDocProps( std::move(rProps) ), mIt( mDocProps.begin() ) {}
    virtual sal_Bool SAL_CALL hasMoreElements(  ) override
    {
        return mIt != mDocProps.end();
    }
    virtual uno::Any SAL_CALL nextElement(  ) override
    {
        if ( !hasMoreElements() )
            throw container::NoSuchElementException();
        return uno::Any( mIt++->second );
    }
};

}

typedef std::unordered_map< OUString, uno::Reference< XDocumentProperty > > DocPropsByName;

namespace {

class BuiltInPropertiesImpl : public PropertiesImpl_BASE
{
protected:

    rtl::Reference< SwXTextDocument > m_xModel;

    DocProps mDocProps;
    DocPropsByName mNamedDocProps;

    public:
    BuiltInPropertiesImpl( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, rtl::Reference< SwXTextDocument >  xModel ) : m_xModel(std::move( xModel ))
    {
        BuiltInIndexHelper builtIns( m_xModel );
        for ( sal_Int32 index = word::WdBuiltInProperty::wdPropertyTitle; index <= word::WdBuiltInProperty::wdPropertyCharsWSpaces; ++index )
        {
            mDocProps[ index ] = new SwVbaBuiltInDocumentProperty( xParent, xContext, builtIns.getDocPropInfoMap()[ index ] );
            mNamedDocProps[ mDocProps[ index ]->getName() ] = mDocProps[ index ];
        }
    }
// XIndexAccess
    virtual ::sal_Int32 SAL_CALL getCount(  ) override
    {
        return mDocProps.size();
    }
    virtual uno::Any SAL_CALL getByIndex( ::sal_Int32 Index ) override
    {
        // correct the correct by the base class for 1 based indices
        DocProps::iterator it = mDocProps.find( ++Index );
        if ( it == mDocProps.end() )
            throw lang::IndexOutOfBoundsException();
        return uno::Any( it->second  );
    }
    virtual uno::Any SAL_CALL getByName( const OUString& aName ) override
    {
        if ( !hasByName( aName ) )
            throw container::NoSuchElementException();
        DocPropsByName::iterator it = mNamedDocProps.find( aName );
        return uno::Any( it->second );

    }
    virtual uno::Sequence< OUString > SAL_CALL getElementNames(  ) override
    {
        uno::Sequence< OUString > aNames( getCount() );
        OUString* pName = aNames.getArray();
        for (const auto& rEntry : mNamedDocProps)
        {
           *pName = rEntry.first;
           ++pName;
        }
        return aNames;
    }

    virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
    {
        DocPropsByName::iterator it = mNamedDocProps.find( aName );
        if ( it == mNamedDocProps.end() )
            return false;
        return true;
    }
// XElementAccess
    virtual uno::Type SAL_CALL getElementType(  ) override
    {
        return  cppu::UnoType<XDocumentProperty>::get();
    }
    virtual sal_Bool SAL_CALL hasElements(  ) override
    {
        return !mDocProps.empty();
    }
    virtual uno::Reference< container::XEnumeration > SAL_CALL createEnumeration(  ) override
    {
        return new DocPropEnumeration( std::unordered_map(mDocProps) );
    }
};

}

SwVbaBuiltinDocumentProperties::SwVbaBuiltinDocumentProperties(
    const uno::Reference< XHelperInterface >& xParent,
    const uno::Reference< uno::XComponentContext >& xContext,
    const rtl::Reference< SwXTextDocument >& xModel )
: SwVbaDocumentproperties_BASE( xParent, xContext,  uno::Reference< container::XIndexAccess >( new BuiltInPropertiesImpl( xParent, xContext, xModel ) ) )
{
}

uno::Reference< XDocumentProperty > SAL_CALL
SwVbaBuiltinDocumentProperties::Add( const OUString& /*Name*/, sal_Bool /*LinkToContent*/, ::sal_Int8 /*Type*/, const uno::Any& /*value*/, const uno::Any& /*LinkSource*/ )
{
    throw uno::RuntimeException( u"not supported for Builtin properties"_ustr );
}

// XEnumerationAccess
uno::Type SAL_CALL
SwVbaBuiltinDocumentProperties::getElementType()
{
    return  cppu::UnoType<XDocumentProperty>::get();
}

uno::Reference< container::XEnumeration > SAL_CALL
SwVbaBuiltinDocumentProperties::createEnumeration()
{
    uno::Reference< container::XEnumerationAccess > xEnumAccess( m_xIndexAccess, uno::UNO_QUERY_THROW );
    return xEnumAccess->createEnumeration();
}

// ScVbaCollectionBaseImpl
uno::Any
SwVbaBuiltinDocumentProperties::createCollectionObject( const uno::Any& aSource )
{
    // pass through
    return aSource;
}

// XHelperInterface
OUString
SwVbaBuiltinDocumentProperties::getServiceImplName()
{
    return u"SwVbaBuiltinDocumentProperties"_ustr;
}

uno::Sequence<OUString>
SwVbaBuiltinDocumentProperties::getServiceNames()
{
    static uno::Sequence< OUString > const aServiceNames
    {
        u"ooo.vba.word.DocumentProperties"_ustr
    };
    return aServiceNames;
}

namespace {

class CustomPropertiesImpl : public PropertiesImpl_BASE
{
    uno::Reference< XHelperInterface > m_xParent;
    uno::Reference< uno::XComponentContext > m_xContext;
    rtl::Reference< SwXTextDocument > m_xModel;
    uno::Reference< beans::XPropertySet > mxUserDefinedProp;
    std::shared_ptr< PropertGetSetHelper > mpPropGetSetHelper;
public:
    CustomPropertiesImpl( uno::Reference< XHelperInterface > xParent,
                          uno::Reference< uno::XComponentContext > xContext,
                          rtl::Reference< SwXTextDocument >  xModel )
        : m_xParent(std::move( xParent )),
          m_xContext(std::move( xContext )),
          m_xModel(std::move( xModel ))
    {
        // suck in the document( custom ) properties
        mpPropGetSetHelper = std::make_shared<CustomPropertyGetSetHelper>( m_xModel );
        mxUserDefinedProp.set(mpPropGetSetHelper->getUserDefinedProperties(),
                uno::UNO_SET_THROW);
    };
    // XIndexAccess
    virtual ::sal_Int32 SAL_CALL getCount(  ) override
    {
        return mxUserDefinedProp->getPropertySetInfo()->getProperties().getLength();
    }

    virtual uno::Any SAL_CALL getByIndex( ::sal_Int32 Index ) override
    {
        uno::Sequence< beans::Property > aProps = mxUserDefinedProp->getPropertySetInfo()->getProperties();
        if ( Index >= aProps.getLength() )
            throw lang::IndexOutOfBoundsException();
        // How to determine type e.g Date? ( com.sun.star.util.DateTime )
        DocPropInfo aPropInfo = DocPropInfo::createDocPropInfo( aProps[ Index ].Name, aProps[ Index ].Name, mpPropGetSetHelper );
        return uno::Any( uno::Reference< XDocumentProperty >( new SwVbaCustomDocumentProperty( m_xParent, m_xContext, aPropInfo ) ) );
    }

    virtual uno::Any SAL_CALL getByName( const OUString& aName ) override
    {
        if ( !hasByName( aName ) )
            throw container::NoSuchElementException();

        DocPropInfo aPropInfo = DocPropInfo::createDocPropInfo( aName, aName, mpPropGetSetHelper );
        return uno::Any( uno::Reference< XDocumentProperty >( new SwVbaCustomDocumentProperty( m_xParent, m_xContext, aPropInfo ) ) );
    }

    virtual uno::Sequence< OUString > SAL_CALL getElementNames(  ) override
    {
        const uno::Sequence< beans::Property > aProps = mxUserDefinedProp->getPropertySetInfo()->getProperties();
        uno::Sequence< OUString > aNames( aProps.getLength() );
        std::transform(aProps.begin(), aProps.end(), aNames.getArray(),
            [](const beans::Property& rProp) -> OUString { return rProp.Name; });
        return aNames;
    }

    virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
    {
        SAL_INFO("sw.vba", "hasByName(" << aName << ") returns " << mxUserDefinedProp->getPropertySetInfo()->hasPropertyByName( aName ) );
        return mxUserDefinedProp->getPropertySetInfo()->hasPropertyByName( aName );
    }

    // XElementAccess
    virtual uno::Type SAL_CALL getElementType(  ) override
    {
        return  cppu::UnoType<XDocumentProperty>::get();
    }

    virtual sal_Bool SAL_CALL hasElements(  ) override
    {
        return getCount() > 0;
    }

    virtual uno::Reference< container::XEnumeration > SAL_CALL createEnumeration(  ) override
    {
        // create a map of properties ( the key doesn't matter )
        SAL_INFO("sw.vba", "Creating an enumeration");
        sal_Int32 key = 0;
        sal_Int32 nElem =  getCount();
        DocProps simpleDocPropSnapShot;
        for ( ; key < nElem; ++key )
             simpleDocPropSnapShot[ key ].set( getByIndex( key ), uno::UNO_QUERY_THROW );
        SAL_INFO("sw.vba", "After creating the enumeration");
        return  new DocPropEnumeration( std::move(simpleDocPropSnapShot) );
    }

    void addProp( const OUString& Name, const uno::Any& Value )
    {
        uno::Reference< beans::XPropertyContainer > xContainer( mxUserDefinedProp, uno::UNO_QUERY_THROW );
        // TODO fixme, perform the necessary Type Value conversions
        xContainer->addProperty( Name, sal_Int16(128), Value );
    }

};

}

SwVbaCustomDocumentProperties::SwVbaCustomDocumentProperties( const uno::Reference< XHelperInterface >& xParent, const uno::Reference< uno::XComponentContext >& xContext, const rtl::Reference< SwXTextDocument >& xModel ) : SwVbaBuiltinDocumentProperties( xParent, xContext, xModel )
{
    // replace the m_xIndexAccess implementation ( we need a virtual init )
    m_xIndexAccess.set( new CustomPropertiesImpl( xParent, xContext, xModel ) );
    m_xNameAccess.set( m_xIndexAccess, uno::UNO_QUERY_THROW );
}

uno::Reference< XDocumentProperty > SAL_CALL
SwVbaCustomDocumentProperties::Add( const OUString& Name, sal_Bool LinkToContent, ::sal_Int8 /*Type*/, const uno::Any& Value, const uno::Any& LinkSource )
{
    CustomPropertiesImpl* pCustomProps = dynamic_cast< CustomPropertiesImpl* > ( m_xIndexAccess.get() );
    uno::Reference< XDocumentProperty > xDocProp;
    if ( pCustomProps )
    {
        OUString sLinkSource;
        pCustomProps->addProp( Name, Value );

        xDocProp.set( m_xNameAccess->getByName( Name ), uno::UNO_QUERY_THROW );
        xDocProp->setLinkToContent( LinkToContent );

        if ( LinkSource >>= sLinkSource )
           xDocProp->setLinkSource( sLinkSource );
    }
    return xDocProp;
}

// XHelperInterface
OUString
SwVbaCustomDocumentProperties::getServiceImplName()
{
    return u"SwVbaCustomDocumentProperties"_ustr;
}

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