/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_xmloff.hxx"

#include <tools/debug.hxx>
#include <tools/inetdef.hxx>
#include <i18npool/mslangid.hxx>
#include <tools/urlobj.hxx>
#include <tools/time.hxx>
#include <rtl/ustrbuf.hxx>

#include <xmloff/xmlmetae.hxx>
#include <xmloff/xmlexp.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/nmspmap.hxx>
#include "xmloff/xmlnmspe.hxx"

#include <com/sun/star/beans/XPropertyAccess.hpp>
#include <com/sun/star/beans/StringPair.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/sax/XSAXSerializable.hpp>

#include <comphelper/sequenceasvector.hxx>
#include <unotools/docinfohelper.hxx>

#include <string.h>


using namespace com::sun::star;
using namespace ::xmloff::token;


//-------------------------------------------------------------------------

void lcl_AddTwoDigits( rtl::OUStringBuffer& rStr, sal_Int32 nVal )
{
    if ( nVal < 10 )
        rStr.append( sal_Unicode('0') );
    rStr.append( nVal );
}

rtl::OUString
SvXMLMetaExport::GetISODateTimeString( const util::DateTime& rDateTime )
{
    //  return ISO date string "YYYY-MM-DDThh:mm:ss"

    rtl::OUStringBuffer sTmp;
    sTmp.append( (sal_Int32) rDateTime.Year );
    sTmp.append( sal_Unicode('-') );
    lcl_AddTwoDigits( sTmp, rDateTime.Month );
    sTmp.append( sal_Unicode('-') );
    lcl_AddTwoDigits( sTmp, rDateTime.Day );
    sTmp.append( sal_Unicode('T') );
    lcl_AddTwoDigits( sTmp, rDateTime.Hours );
    sTmp.append( sal_Unicode(':') );
    lcl_AddTwoDigits( sTmp, rDateTime.Minutes );
    sTmp.append( sal_Unicode(':') );
    lcl_AddTwoDigits( sTmp, rDateTime.Seconds );

    return sTmp.makeStringAndClear();
}

//-------------------------------------------------------------------------

void SvXMLMetaExport::SimpleStringElement( const rtl::OUString& rText,
        sal_uInt16 nNamespace, enum XMLTokenEnum eElementName )
{
    if ( rText.getLength() ) {
        SvXMLElementExport aElem( mrExport, nNamespace, eElementName,
                                  sal_True, sal_False );
        mrExport.Characters( rText );
    }
}

void SvXMLMetaExport::SimpleDateTimeElement( const util::DateTime & rDate,
        sal_uInt16 nNamespace, enum XMLTokenEnum eElementName )
{
    if (rDate.Month != 0) { // invalid dates are 0-0-0
        rtl::OUString sValue = GetISODateTimeString( rDate );
        if ( sValue.getLength() ) {
            SvXMLElementExport aElem( mrExport, nNamespace, eElementName,
                                      sal_True, sal_False );
            mrExport.Characters( sValue );
        }
    }
}

void SvXMLMetaExport::_MExport()
{
    //  generator
    {
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_GENERATOR,
                                  sal_True, sal_True );
        mrExport.Characters( ::utl::DocInfoHelper::GetGeneratorString() );
    }

    //  document title
    SimpleStringElement  ( mxDocProps->getTitle(),
                           XML_NAMESPACE_DC, XML_TITLE );

    //  description
    SimpleStringElement  ( mxDocProps->getDescription(),
                           XML_NAMESPACE_DC, XML_DESCRIPTION );

    //  subject
    SimpleStringElement  ( mxDocProps->getSubject(),
                           XML_NAMESPACE_DC, XML_SUBJECT );

    //  created...
    SimpleStringElement  ( mxDocProps->getAuthor(),
                           XML_NAMESPACE_META, XML_INITIAL_CREATOR );
    SimpleDateTimeElement( mxDocProps->getCreationDate(),
                           XML_NAMESPACE_META, XML_CREATION_DATE );

    //  modified...
    SimpleStringElement  ( mxDocProps->getModifiedBy(),
                           XML_NAMESPACE_DC, XML_CREATOR );
    SimpleDateTimeElement( mxDocProps->getModificationDate(),
                           XML_NAMESPACE_DC, XML_DATE );

    //  printed...
    SimpleStringElement  ( mxDocProps->getPrintedBy(),
                           XML_NAMESPACE_META, XML_PRINTED_BY );
    SimpleDateTimeElement( mxDocProps->getPrintDate(),
                           XML_NAMESPACE_META, XML_PRINT_DATE );

    //  keywords
    const uno::Sequence< ::rtl::OUString > keywords = mxDocProps->getKeywords();
    for (sal_Int32 i = 0; i < keywords.getLength(); ++i) {
        SvXMLElementExport aKwElem( mrExport, XML_NAMESPACE_META, XML_KEYWORD,
                                    sal_True, sal_False );
        mrExport.Characters( keywords[i] );
    }

    //  document language
    {
        const lang::Locale aLocale = mxDocProps->getLanguage();
        ::rtl::OUString sValue = aLocale.Language;
        if (sValue.getLength()) {
            if ( aLocale.Country.getLength() )
            {
                sValue += rtl::OUString::valueOf((sal_Unicode)'-');
                sValue += aLocale.Country;
            }
            SvXMLElementExport aElem( mrExport, XML_NAMESPACE_DC, XML_LANGUAGE,
                                      sal_True, sal_False );
            mrExport.Characters( sValue );
        }
    }

    //  editing cycles
    {
        SvXMLElementExport aElem( mrExport,
                                  XML_NAMESPACE_META, XML_EDITING_CYCLES,
                                  sal_True, sal_False );
        mrExport.Characters( ::rtl::OUString::valueOf(
            static_cast<sal_Int32>(mxDocProps->getEditingCycles()) ) );
    }

    //  editing duration
    //  property is a int32 (seconds)
    {
        sal_Int32 secs = mxDocProps->getEditingDuration();
        SvXMLElementExport aElem( mrExport,
                                  XML_NAMESPACE_META, XML_EDITING_DURATION,
                                  sal_True, sal_False );
        mrExport.Characters( SvXMLUnitConverter::convertTimeDuration(
            Time(secs/3600, (secs%3600)/60, secs%60)) );
    }

    //  default target
    const ::rtl::OUString sDefTarget = mxDocProps->getDefaultTarget();
    if ( sDefTarget.getLength() )
    {
        mrExport.AddAttribute( XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME,
                               sDefTarget );

        //! define strings for xlink:show values
        const XMLTokenEnum eShow =
            sDefTarget.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("_blank"))
                ? XML_NEW : XML_REPLACE;
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_SHOW, eShow );

        SvXMLElementExport aElem( mrExport,
                                  XML_NAMESPACE_META,XML_HYPERLINK_BEHAVIOUR,
                                  sal_True, sal_False );
    }

    //  auto-reload
    const ::rtl::OUString sReloadURL = mxDocProps->getAutoloadURL();
    const sal_Int32 sReloadDelay = mxDocProps->getAutoloadSecs();
    if (sReloadDelay != 0 || sReloadURL.getLength() != 0)
    {
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF,
                              mrExport.GetRelativeReference( sReloadURL ) );

        mrExport.AddAttribute( XML_NAMESPACE_META, XML_DELAY,
            SvXMLUnitConverter::convertTimeDuration(
                Time(sReloadDelay/3600, (sReloadDelay%3600)/60,
                    sReloadDelay%60 )) );

        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_AUTO_RELOAD,
                                  sal_True, sal_False );
    }

    //  template
    const rtl::OUString sTplPath = mxDocProps->getTemplateURL();
    if ( sTplPath.getLength() )
    {
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE );
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONREQUEST );

        //  template URL
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF,
                              mrExport.GetRelativeReference(sTplPath) );

        //  template name
        mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TITLE,
                              mxDocProps->getTemplateName() );

        //  template date
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_DATE,
                GetISODateTimeString( mxDocProps->getTemplateDate() ) );

        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_TEMPLATE,
                                  sal_True, sal_False );
    }

    //  user defined fields
    uno::Reference< beans::XPropertyAccess > xUserDefined(
        mxDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
    const uno::Sequence< beans::PropertyValue > props =
        xUserDefined->getPropertyValues();
    for (sal_Int32 i = 0; i < props.getLength(); ++i) {
        ::rtl::OUStringBuffer sValueBuffer;
        ::rtl::OUStringBuffer sType;
        if (!SvXMLUnitConverter::convertAny(
                sValueBuffer, sType, props[i].Value)) {
            continue;
        }
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_NAME, props[i].Name );
        mrExport.AddAttribute( XML_NAMESPACE_META, XML_VALUE_TYPE,
                              sType.makeStringAndClear() );
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META,
                                  XML_USER_DEFINED, sal_True, sal_False );
        mrExport.Characters( sValueBuffer.makeStringAndClear() );
    }

    const uno::Sequence< beans::NamedValue > aDocStatistic =
            mxDocProps->getDocumentStatistics();
	// write document statistic if there is any provided
	if ( aDocStatistic.getLength() )
	{
		for ( sal_Int32 nInd = 0; nInd < aDocStatistic.getLength(); nInd++ )
		{
			sal_Int32 nValue = 0;
			if ( aDocStatistic[nInd].Value >>= nValue )
			{
				::rtl::OUString aValue = rtl::OUString::valueOf( nValue );
				if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "TableCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_TABLE_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "ObjectCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_OBJECT_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "ImageCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_IMAGE_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "PageCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_PAGE_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "ParagraphCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_PARAGRAPH_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "WordCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_WORD_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "CharacterCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_CHARACTER_COUNT, aValue );
				else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString(
                        RTL_CONSTASCII_USTRINGPARAM( "CellCount" ) ) ) )
					mrExport.AddAttribute(
                        XML_NAMESPACE_META, XML_CELL_COUNT, aValue );
				else
				{
					DBG_ASSERT( sal_False, "Unknown statistic value!\n" );
				}
			}
		}
		SvXMLElementExport aElem( mrExport,
            XML_NAMESPACE_META, XML_DOCUMENT_STATISTIC, sal_True, sal_True );
	}
}

//-------------------------------------------------------------------------

static const char *s_xmlns  = "xmlns";
static const char *s_xmlns2 = "xmlns:";
static const char *s_meta   = "meta:";
static const char *s_href   = "xlink:href";

SvXMLMetaExport::SvXMLMetaExport(
        SvXMLExport& i_rExp,
        const uno::Reference<document::XDocumentProperties>& i_rDocProps ) :
    mrExport( i_rExp ),
	mxDocProps( i_rDocProps ),
    m_level( 0 ),
    m_preservedNSs()
{
    DBG_ASSERT( mxDocProps.is(), "no document properties" );
}

SvXMLMetaExport::~SvXMLMetaExport()
{
}

void SvXMLMetaExport::Export()
{
//    exportDom(xDOM, mrExport); // this would not work (root node, namespaces)
    uno::Reference< xml::sax::XSAXSerializable> xSAXable(mxDocProps,
        uno::UNO_QUERY);
    if (xSAXable.is()) {
        ::comphelper::SequenceAsVector< beans::StringPair > namespaces;
        const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap());
        for (sal_uInt16 key = rNsMap.GetFirstKey();
             key != USHRT_MAX; key = rNsMap.GetNextKey(key)) {
            beans::StringPair ns;
            const ::rtl::OUString attrname = rNsMap.GetAttrNameByKey(key);
            if (attrname.matchAsciiL(s_xmlns2, strlen(s_xmlns2))) {
                ns.First  = attrname.copy(strlen(s_xmlns2));
            } else if (attrname.equalsAsciiL(s_xmlns, strlen(s_xmlns))) {
                // default initialized empty string
            } else {
            DBG_ERROR("namespace attribute not starting with xmlns unexpected");
            }
            ns.Second = rNsMap.GetNameByKey(key);
            namespaces.push_back(ns);
        }
        xSAXable->serialize(this, namespaces.getAsConstList());
    } else {
        // office:meta
		SvXMLElementExport aElem( mrExport, XML_NAMESPACE_OFFICE, XML_META,
								  sal_True, sal_True );
        // fall back to using public interface of XDocumentProperties
        _MExport();
    }
}

// ::com::sun::star::xml::sax::XDocumentHandler:
void SAL_CALL
SvXMLMetaExport::startDocument()
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    // ignore: has already been done by SvXMLExport::exportDoc
    DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" );
}

void SAL_CALL
SvXMLMetaExport::endDocument()
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    // ignore: will be done by SvXMLExport::exportDoc
    DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" );
}

// unfortunately, this method contains far too much ugly namespace mangling.
void SAL_CALL
SvXMLMetaExport::startElement(const ::rtl::OUString & i_rName,
    const uno::Reference< xml::sax::XAttributeList > & i_xAttribs)
    throw (uno::RuntimeException, xml::sax::SAXException)
{

    if (m_level == 0) {
        // namepace decls: default ones have been written at the root element
        // non-default ones must be preserved here
        const sal_Int16 nCount = i_xAttribs->getLength();
        for (sal_Int16 i = 0; i < nCount; ++i) {
            const ::rtl::OUString name(i_xAttribs->getNameByIndex(i));
            if (name.matchAsciiL(s_xmlns, strlen(s_xmlns))) {
                bool found(false);
                const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap());
                for (sal_uInt16 key = rNsMap.GetFirstKey();
                     key != USHRT_MAX; key = rNsMap.GetNextKey(key)) {
                    if (name.equals(rNsMap.GetAttrNameByKey(key))) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    m_preservedNSs.push_back(beans::StringPair(name,
                        i_xAttribs->getValueByIndex(i)));
                }
            }
        }
        // ignore the root: it has been written already
        ++m_level;
        return;
    }

    if (m_level == 1) {
        // attach preserved namespace decls from root node here
        for (std::vector<beans::StringPair>::const_iterator iter =
                m_preservedNSs.begin(); iter != m_preservedNSs.end(); ++iter) {
            const ::rtl::OUString ns(iter->First);
            bool found(false);
            // but only if it is not already there
            const sal_Int16 nCount = i_xAttribs->getLength();
            for (sal_Int16 i = 0; i < nCount; ++i) {
                const ::rtl::OUString name(i_xAttribs->getNameByIndex(i));
                if (ns.equals(name)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                mrExport.AddAttribute(ns, iter->Second);
            }
        }
    }

    // attach the attributes
    if (i_rName.matchAsciiL(s_meta, strlen(s_meta))) {
        // special handling for all elements that may have
        // xlink:href attributes; these must be made relative
        const sal_Int16 nLength = i_xAttribs->getLength();
        for (sal_Int16 i = 0; i < nLength; ++i) {
            const ::rtl::OUString name (i_xAttribs->getNameByIndex (i));
            ::rtl::OUString value(i_xAttribs->getValueByIndex(i));
            if (name.matchAsciiL(s_href, strlen(s_href))) {
                value = mrExport.GetRelativeReference(value);
            }
            mrExport.AddAttribute(name, value);
        }
    } else {
        const sal_Int16 nLength = i_xAttribs->getLength();
        for (sal_Int16 i = 0; i < nLength; ++i) {
            const ::rtl::OUString name  (i_xAttribs->getNameByIndex(i));
            const ::rtl::OUString value (i_xAttribs->getValueByIndex(i));
            mrExport.AddAttribute(name, value);
        }
    }

    // finally, start the element
    // #i107240# no whitespace here, because the DOM may already contain
    // whitespace, which is not cleared when loading and thus accumulates.
    mrExport.StartElement(i_rName, (m_level > 1) ? sal_False : sal_True);
    ++m_level;
}

void SAL_CALL
SvXMLMetaExport::endElement(const ::rtl::OUString & i_rName)
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    --m_level;
    if (m_level == 0) {
        // ignore the root; see startElement
        return;
    }
    DBG_ASSERT( m_level >= 0, "SvXMLMetaExport: level error" );
    mrExport.EndElement(i_rName, sal_False);
}

void SAL_CALL
SvXMLMetaExport::characters(const ::rtl::OUString & i_rChars)
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    mrExport.Characters(i_rChars);
}

void SAL_CALL
SvXMLMetaExport::ignorableWhitespace(const ::rtl::OUString & /*i_rWhitespaces*/)
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    mrExport.IgnorableWhitespace(/*i_rWhitespaces*/);
}

void SAL_CALL
SvXMLMetaExport::processingInstruction(const ::rtl::OUString & i_rTarget,
    const ::rtl::OUString & i_rData)
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    // ignore; the exporter cannot handle these
    (void) i_rTarget;
    (void) i_rData;
}

void SAL_CALL
SvXMLMetaExport::setDocumentLocator(const uno::Reference<xml::sax::XLocator>&)
    throw (uno::RuntimeException, xml::sax::SAXException)
{
    // nothing to do here, move along...
}


