/**************************************************************
 * 
 * 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_ucb.hxx"

/**************************************************************************
								TODO
 **************************************************************************

 *************************************************************************/
#include <ucbhelper/contentidentifier.hxx>
#include "odma_provider.hxx"
#include "odma_content.hxx"
#include "odma_contentprops.hxx"
#include <com/sun/star/util/Date.hpp>
#include <com/sun/star/util/Time.hpp>
#include <rtl/uri.hxx>
#include <algorithm>
#include <osl/file.hxx>

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

//=========================================================================
//=========================================================================
//
// ContentProvider Implementation.
//
//=========================================================================
//=========================================================================
ODMHANDLE ContentProvider::m_aOdmHandle = NULL;

ContentProvider::ContentProvider(
                const uno::Reference< lang::XMultiServiceFactory >& rSMgr )
: ::ucbhelper::ContentProviderImplHelper( rSMgr )
{
	
}

//=========================================================================
// virtual
ContentProvider::~ContentProvider()
{
	ContentsMap::iterator aIter = m_aContents.begin();
	for (;aIter != m_aContents.end() ;++aIter )
	{
		if(aIter->second->m_bIsOpen)
			closeDocument(aIter->first);
	}
	if(m_aOdmHandle)
	{
		NODMUnRegisterApp(m_aOdmHandle);
		m_aOdmHandle = NULL;
	}
}
// -----------------------------------------------------------------------------
ODMHANDLE ContentProvider::getHandle() 
{ 
	if(!m_aOdmHandle)
	{
		ODMSTATUS odm = NODMRegisterApp(&m_aOdmHandle,ODM_API_VERSION,ODMA_ODMA_REGNAME,NULL,NULL);
		switch(odm)
		{
		case ODM_SUCCESS:
			break;
		case ODM_E_NODMS:
			break;
		case ODM_E_CANTINIT:
			break;
		case ODM_E_VERSION:
			break;
		default:
			break;
		}
	}
	return m_aOdmHandle; 
}
// -----------------------------------------------------------------------------

//=========================================================================
//
// XInterface methods.
//
//=========================================================================

// @@@ Add own interfaces.
XINTERFACE_IMPL_3( ContentProvider,
                   lang::XTypeProvider,
                   lang::XServiceInfo,
                   ucb::XContentProvider );

//=========================================================================
//
// XTypeProvider methods.
//
//=========================================================================

// @@@ Add own interfaces.
XTYPEPROVIDER_IMPL_3( ContentProvider,
                      lang::XTypeProvider,
                      lang::XServiceInfo,
                      ucb::XContentProvider );

//=========================================================================
//
// XServiceInfo methods.
//
//=========================================================================

// @@@ Adjust implementation name. Keep the prefix "com.sun.star.comp."!
// @@@ Adjust service name.
XSERVICEINFO_IMPL_1( ContentProvider,
                     rtl::OUString::createFromAscii(
                            "com.sun.star.comp.odma.ContentProvider" ),
                     rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(ODMA_CONTENT_PROVIDER_SERVICE_NAME) ) );

//=========================================================================
//
// Service factory implementation.
//
//=========================================================================

ONE_INSTANCE_SERVICE_FACTORY_IMPL( ContentProvider );

//=========================================================================
//
// XContentProvider methods.
//
//=========================================================================

// virtual
uno::Reference< ucb::XContent > SAL_CALL ContentProvider::queryContent(
        const uno::Reference< ucb::XContentIdentifier >& Identifier )
    throw( ucb::IllegalIdentifierException, uno::RuntimeException )
{
	// Check URL scheme...
	if(!getHandle())
		throw ucb::IllegalIdentifierException();

    rtl::OUString aScheme( rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(ODMA_URL_SCHEME) ) );
	sal_Int32 nIndex = 0;
	rtl::OUString sOdma = aScheme.getToken(3,'.',nIndex);
	rtl::OUString sCanonicURL = Identifier->getContentIdentifier();
	// check if url starts with odma
    if ( !(Identifier->getContentProviderScheme().equalsIgnoreAsciiCase( aScheme ) || 
		   Identifier->getContentProviderScheme().equalsIgnoreAsciiCase( sOdma )) )
        throw ucb::IllegalIdentifierException();

	if(!(	sCanonicURL.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM(ODMA_URL_SCHEME_SHORT ODMA_URL_SHORT)) ||
			sCanonicURL.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM(ODMA_URL_SCHEME ODMA_URL_SHORT))))
		throw ucb::IllegalIdentifierException();

	// @@@ Further id checks may go here...
#if 0
	if ( id-check-failes )
        throw ucb::IllegalIdentifierException();
#endif

	// @@@ Id normalization may go here...
#if 0
	// Normalize URL and create new Id.
    rtl::OUString aCanonicURL = ( Identifier->getContentIdentifier() );
    uno::Reference< ucb::XContentIdentifier > xCanonicId
        = new ::ucb::ContentIdentifier( m_xSMgr, aCanonicURL );
#else
    uno::Reference< ucb::XContentIdentifier > xCanonicId = Identifier;
#endif

	osl::MutexGuard aGuard( m_aMutex );

	// Check, if a content with given id already exists...
    uno::Reference< ucb::XContent > xContent
		= queryExistingContent( xCanonicId ).get();
	if ( xContent.is() )
		return xContent;

	// @@@ Decision, which content implementation to instanciate may be
	//     made here ( in case you have different content classes ).

	// Create a new content.

	sCanonicURL = convertURL(sCanonicURL);
	
	::rtl::Reference<ContentProperties> aProp;
	// first check if we got an ODMA ID from outside
	if( sCanonicURL.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM(ODMA_URL_ODMAID)))
	{// we get an orignal ODMA id so we have to look for the name
		::rtl::OString sDocId = ::rtl::OUStringToOString(sCanonicURL,RTL_TEXTENCODING_MS_1252);
		sal_Char* lpszDocName = new sal_Char[ODM_NAME_MAX];
		
		ODMSTATUS odm = NODMGetDocInfo(	getHandle(),
										const_cast<sal_Char*>(sDocId.getStr()),
										ODM_NAME,
										lpszDocName,
										ODM_NAME_MAX
									);
		if(odm == ODM_SUCCESS)
		{
			aProp = new ContentProperties();
			aProp->m_sDocumentName = ::rtl::OStringToOUString(rtl::OString(lpszDocName),RTL_TEXTENCODING_ASCII_US);
			aProp->m_sDocumentId   = sDocId;
			aProp->m_sContentType  = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(ODMA_CONTENT_TYPE));
			append(aProp);
		}
		delete [] lpszDocName;
	}
	else // we got an already fetched name here so look for it
	{
		// we have a valid document name 
		aProp = getContentPropertyWithTitle(sCanonicURL);
		if(!aProp.is())
			aProp = getContentPropertyWithSavedAsName(sCanonicURL);
		if(!aProp.is())
		{
			if(sCanonicURL.equalsIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM("/")))
			{ // found only the scheme
				aProp = new ContentProperties();
				aProp->m_sDocumentId = "/";
				aProp->m_sTitle = sCanonicURL;
				aProp->m_bIsFolder = sal_True;
				aProp->m_bIsDocument = !aProp->m_bIsFolder;
				m_aContents.insert(ContentsMap::value_type(aProp->m_sDocumentId,aProp));
			}
			else
				aProp = queryContentProperty(sCanonicURL);
		}
	}
	if(!aProp.is())
		throw ucb::IllegalIdentifierException();

	xContent = new Content( m_xSMgr, this, xCanonicId ,aProp);
    registerNewContent( xContent );

	if ( !xContent->getIdentifier().is() )
        throw ucb::IllegalIdentifierException();

	return xContent;
}
// -----------------------------------------------------------------------------
void ContentProvider::closeDocument(const ::rtl::OString& _sDocumentId)
{
	ContentsMap::iterator aIter = m_aContents.find(_sDocumentId);
	if(aIter != m_aContents.end())
	{
		DWORD dwFlags = ODM_SILENT;
		ODMSTATUS odm = NODMCloseDocEx(	ContentProvider::getHandle(), 
										const_cast<sal_Char*>(_sDocumentId.getStr()), 
										&dwFlags,
										0xFFFFFFFF,
										0xFFFFFFFF,
										NULL,
										0);
		OSL_ENSURE(odm == ODM_SUCCESS,"Error while closing a document!");
		if(odm == ODM_SUCCESS)
			aIter->second->m_bIsOpen = sal_False;
	}
}
// -----------------------------------------------------------------------------
void ContentProvider::saveDocument(const ::rtl::OString& _sDocumentId)
{
	ContentsMap::iterator aIter = m_aContents.find(_sDocumentId);
	if(aIter != m_aContents.end())
	{
		sal_Char* lpszDocId = new sal_Char[ODM_DOCID_MAX];
		DWORD dwFlags = ODM_SILENT;
		ODMSTATUS odm = NODMSaveDocEx(getHandle(),
									const_cast<sal_Char*>(_sDocumentId.getStr()),
									lpszDocId,
									&dwFlags);
		OSL_ENSURE(odm == ODM_SUCCESS,"Could not save document!");
		if(odm != ODM_SUCCESS)
		{
			delete [] lpszDocId;
			throw uno::Exception();
		}
		aIter->second->m_sDocumentId = rtl::OString(lpszDocId);
		delete [] lpszDocId;
	}
}
// -----------------------------------------------------------------------------
util::Date toDate(const ::rtl::OString& _sSQLString)
{
	sal_uInt16	nYear	= 0,
				nMonth	= 0,
				nDay	= 0;
	nYear	= (sal_uInt16)_sSQLString.copy(0,4).toInt32();
	nMonth	= (sal_uInt16)_sSQLString.copy(4,2).toInt32();
	nDay	= (sal_uInt16)_sSQLString.copy(6,2).toInt32();

	return util::Date(nDay,nMonth,nYear);
}
//-----------------------------------------------------------------------------
util::Time toTime(const ::rtl::OString& _sSQLString)
{
	sal_uInt16	nHour	= 0,
				nMinute	= 0,
				nSecond	= 0;
	nHour	= (sal_uInt16)_sSQLString.copy(8,2).toInt32();
	nMinute = (sal_uInt16)_sSQLString.copy(10,2).toInt32();
	nSecond = (sal_uInt16)_sSQLString.copy(12,2).toInt32();

	return util::Time(0,nHour,nMinute,nSecond);
}
//-----------------------------------------------------------------------------
util::DateTime toDateTime(const ::rtl::OString& _sSQLString)
{
	util::Date aDate = toDate(_sSQLString);
	util::Time aTime = toTime(_sSQLString);

	return util::DateTime(0,aTime.Seconds,aTime.Minutes,aTime.Hours,aDate.Day,aDate.Month,aDate.Year);
}
// -----------------------------------------------------------------------------
void ContentProvider::fillDocumentProperties(const ::rtl::Reference<ContentProperties>& _rProp)
{
	// read some properties from the DMS
	sal_Char* lpszDocInfo = new sal_Char[ODM_DOCID_MAX];
	sal_Char* pDocId = const_cast<sal_Char*>(_rProp->m_sDocumentId.getStr());

	// read the create date of the document
	ODMSTATUS odm = NODMGetDocInfo(	getHandle(),
									pDocId,
									ODM_CREATEDDATE,
									lpszDocInfo,
									ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_aDateCreated = toDateTime(::rtl::OString(lpszDocInfo));

	// read the modified date of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_MODIFYDATE,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_aDateModified = toDateTime(::rtl::OString(lpszDocInfo));

	// read the title of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_TITLETEXT,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_sTitle = ::rtl::OStringToOUString(rtl::OString(lpszDocInfo),RTL_TEXTENCODING_ASCII_US);

	// read the name of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_NAME,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_sDocumentName = ::rtl::OStringToOUString(rtl::OString(lpszDocInfo),RTL_TEXTENCODING_ASCII_US);

	// read the author of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_AUTHOR,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_sAuthor = ::rtl::OStringToOUString(rtl::OString(lpszDocInfo),RTL_TEXTENCODING_ASCII_US);

	// read the subject of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_SUBJECT,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_sSubject = ::rtl::OStringToOUString(rtl::OString(lpszDocInfo),RTL_TEXTENCODING_ASCII_US);

	// read the keywords of the document
	odm = NODMGetDocInfo(	getHandle(),
							pDocId,
							ODM_KEYWORDS,
							lpszDocInfo,
							ODM_DOCID_MAX);
	if(odm == ODM_SUCCESS)
		_rProp->m_sKeywords = ::rtl::OStringToOUString(rtl::OString(lpszDocInfo),RTL_TEXTENCODING_ASCII_US);

/*
	odm = NODMGetDocInfo(	getHandle(),
									const_cast<sal_Char*>(_rProp->m_sDocumentId.getStr()),
									ODM_URL,
									lpszDocInfo,
									ODM_DOCID_MAX);
*/
	delete [] lpszDocInfo;
}
// -----------------------------------------------------------------------------
void ContentProvider::append(const ::rtl::Reference<ContentProperties>& _rProp)
{
	// now fill some more properties
	fillDocumentProperties(_rProp);
	// and append them
	m_aContents.insert(ContentsMap::value_type(_rProp->m_sDocumentId,_rProp));
}
// -----------------------------------------------------------------------------
::rtl::Reference<ContentProperties> ContentProvider::queryContentProperty(const ::rtl::OUString& _sDocumentName)
{
	::rtl::Reference<ContentProperties> aReturn;
	sal_Char* lpszDMSList	= new sal_Char[ODM_DMSID_MAX];

	ODMSTATUS odm = NODMGetDMS(ODMA_ODMA_REGNAME, lpszDMSList);
	if(odm == ODM_SUCCESS)
	{
		sal_Char* pQueryId = new sal_Char[ODM_QUERYID_MAX];
		lpszDMSList[strlen(lpszDMSList)+1] = '\0';

		::rtl::OString sTitleText(::rtl::OUStringToOString(_sDocumentName,RTL_TEXTENCODING_ASCII_US));
		::rtl::OString sQuery("SELECT ODM_DOCID, ODM_NAME WHERE ODM_TITLETEXT = '");
		sQuery += sTitleText;
		sQuery += "'";
		
		DWORD dwFlags = ODM_SPECIFIC;
		odm = NODMQueryExecute(getHandle(), sQuery,dwFlags, lpszDMSList, pQueryId );
		if(odm == ODM_SUCCESS)
		{
			sal_uInt16 nCount		= 10;
			sal_uInt16 nMaxCount	= 10;
			sal_Char* lpszDocId		= new sal_Char[ODM_DOCID_MAX * nMaxCount];
			sal_Char* lpszDocName	= new sal_Char[ODM_NAME_MAX * nMaxCount];
			sal_Char* lpszDocInfo	= new sal_Char[ODM_DOCID_MAX];
			
			::rtl::OUString sContentType(RTL_CONSTASCII_USTRINGPARAM(ODMA_CONTENT_TYPE));
			do
			{
				if(nCount >= nMaxCount)
				{
					// get the result
					nCount = nMaxCount;
					odm = NODMQueryGetResults(getHandle(), pQueryId,lpszDocId, lpszDocName, ODM_NAME_MAX, (WORD*)&nCount);
				}
				if(odm == ODM_SUCCESS)
					for(sal_uInt16 i = 0; i < nCount; ++i)
					{
						odm = NODMGetDocInfo(	getHandle(),
												&lpszDocId[ODM_DOCID_MAX*i],
												ODM_TITLETEXT,
												lpszDocInfo,
												ODM_DOCID_MAX);
						if( odm == ODM_SUCCESS && sTitleText == ::rtl::OString(lpszDocInfo))
						{
							aReturn = new ContentProperties();
							aReturn->m_sDocumentName	= ::rtl::OStringToOUString(rtl::OString(&lpszDocName[ODM_NAME_MAX*i]),RTL_TEXTENCODING_ASCII_US);
							aReturn->m_sDocumentId	= ::rtl::OString(&lpszDocId[ODM_DOCID_MAX*i]);
							aReturn->m_sContentType	= sContentType;
							append(aReturn);
							nCount = 0; // break condition from outer loop
							break;
						}
					}
			}
			while(nCount > nMaxCount);

			delete [] lpszDocInfo;
			delete [] lpszDocId;
			delete [] lpszDocName;
		}

		// now close the query
		odm = NODMQueryClose(ContentProvider::getHandle(), pQueryId);
		delete [] pQueryId;
	}
	delete [] lpszDMSList;
	

	return aReturn;
}
// -----------------------------------------------------------------------------
::rtl::Reference<ContentProperties> ContentProvider::getContentProperty(const ::rtl::OUString& _sName,
																   const ContentPropertiesMemberFunctor& _aFunctor) const
{
	::rtl::Reference<ContentProperties> aReturn;
	ContentsMap::const_iterator aFind = ::std::find_if(	m_aContents.begin(),
														m_aContents.end(),
														::std::compose1(
															::std::bind2nd(_aFunctor,_sName),
															::std::select2nd<ContentsMap::value_type>()
														)
													);
	if(aFind != m_aContents.end())
		aReturn = aFind->second;
	return aReturn;
}
// -----------------------------------------------------------------------------
::rtl::Reference<ContentProperties> ContentProvider::getContentPropertyWithSavedAsName(const ::rtl::OUString& _sSaveAsName) const
{
	ContentPropertiesMemberFunctor aFunc(::std::mem_fun(&ContentProperties::getSavedAsName));
	return getContentProperty(_sSaveAsName,aFunc);
}
// -----------------------------------------------------------------------------
::rtl::Reference<ContentProperties> ContentProvider::getContentPropertyWithTitle(const ::rtl::OUString& _sTitle) const
{
	ContentPropertiesMemberFunctor aFunc(::std::mem_fun(&ContentProperties::getTitle));
	return getContentProperty(_sTitle,aFunc);
}
// -----------------------------------------------------------------------------
::rtl::OUString ContentProvider::openDoc(const ::rtl::Reference<ContentProperties>& _rProp)  throw (uno::Exception)
{
	OSL_ENSURE(_rProp.is(),"No valid content properties!");
	if(!_rProp->m_bIsOpen)
	{
		sal_Char *pFileName = new sal_Char[ODM_FILENAME_MAX];
		
		DWORD dwFlag = ODM_MODIFYMODE | ODM_SILENT;
		ODMSTATUS odm = NODMOpenDoc(getHandle(), dwFlag, const_cast<sal_Char*>(_rProp->m_sDocumentId.getStr()), pFileName);
		switch(odm)
		{
			case ODM_E_INUSE:
				dwFlag = ODM_VIEWMODE;
				if( NODMOpenDoc(getHandle(), dwFlag, const_cast<sal_Char*>(_rProp->m_sDocumentId.getStr()), pFileName) != ODM_SUCCESS)
					break;
				// else run through
			case ODM_SUCCESS:
				::osl::FileBase::getFileURLFromSystemPath(::rtl::OStringToOUString(rtl::OString(pFileName),RTL_TEXTENCODING_ASCII_US)
															,_rProp->m_sFileURL);
				_rProp->m_bIsOpen = sal_True;
				break;
			default:
				delete [] pFileName;
				throw uno::Exception();  // TODO give a more precise error message here
		}

		delete [] pFileName;
	}
	return _rProp->m_sFileURL;
}
// -----------------------------------------------------------------------------
::rtl::OUString ContentProvider::convertURL(const ::rtl::OUString& _sCanonicURL)
{
	sal_Int32 nPos = 0;
	// check if url starts with odma
	if(_sCanonicURL.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM(ODMA_URL_SCHEME_SHORT ODMA_URL_SHORT)))
	{ // URL starts with odma:// so we have to remove this
		nPos = ODMA_URL_SHORT_LGTH;
	}
	else if(_sCanonicURL.matchIgnoreAsciiCaseAsciiL(RTL_CONSTASCII_STRINGPARAM(ODMA_URL_SCHEME ODMA_URL_SHORT)))
	{ // URL starts with vnd.sun.star.odma:/// so we have to remove this
		nPos = ODMA_URL_LGTH;
	}

	::rtl::OUString sCanonicURL = _sCanonicURL;
	// now check what formats we allow
	if(nPos == _sCanonicURL.getLength()) // only ask for root entry
		sCanonicURL = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("/"));

	if(nPos < sCanonicURL.getLength())
	{
		sCanonicURL = sCanonicURL.copy(nPos);
		sCanonicURL = rtl::Uri::decode(sCanonicURL,rtl_UriDecodeWithCharset,RTL_TEXTENCODING_UTF8);
	}
	if(sCanonicURL.getLength() > 1 && sCanonicURL.getStr()[0] == sal_Unicode('/'))
	{
		sCanonicURL = sCanonicURL.copy(1);
		if(sCanonicURL.getLength() == 1 && sCanonicURL.getStr()[0] == sal_Unicode('.'))
			sCanonicURL = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("/"));
	}
	return sCanonicURL;
}
// -----------------------------------------------------------------------------
sal_Bool ContentProvider::deleteDocument(const ::rtl::Reference<ContentProperties>& _rProp)
{
	closeDocument(_rProp->m_sDocumentId);
	ODMSTATUS odm = NODMActivate(ContentProvider::getHandle(),
								 ODM_DELETE,
								 const_cast< sal_Char*>(_rProp->m_sDocumentId.getStr()));
	if(odm == ODM_SUCCESS)
		m_aContents.erase(_rProp->m_sDocumentId);
	
	return odm == ODM_SUCCESS;
}
// -----------------------------------------------------------------------------
