/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



#include <iostream>

#include "osl/interlck.h"
#include "rtl/ustring.hxx"
#include "uno/environment.hxx"
#include "uno/mapping.hxx"
#include "uno/dispatcher.h"

//#include "cascade_mappping.hxx"
#include "cppu/EnvDcp.hxx"


//#define LOG_CALLING_named_purpose_getMapping

//#define LOG_LIFECYLE_MediatorMapping
#ifdef LOG_LIFECYLE_MediatorMapping
#  define LOG_LIFECYLE_MediatorMapping_emit(x) x

#else
#  define LOG_LIFECYLE_MediatorMapping_emit(x)

#endif


using namespace com::sun::star;

class MediatorMapping : public uno_Mapping
{
	oslInterlockedCount m_refCount;
	
	uno::Mapping        m_from2uno;
	uno::Mapping        m_uno2to;

	uno::Environment    m_from;
	uno::Environment    m_interm;
	uno::Environment    m_to;

public:	
	void acquire(void);
	void release(void);

	void mapInterface(void                            ** ppOut, 
					  void                             * pInterface,
					  typelib_InterfaceTypeDescription * pInterfaceTypeDescr);
	MediatorMapping(uno_Environment * pFrom, 
					uno_Environment * pInterm,
					uno_Environment * pTo);
	~MediatorMapping();
};

extern "C" {
static void SAL_CALL s_acquire(uno_Mapping * mapping)
{
	MediatorMapping * pMediatorMapping = static_cast<MediatorMapping *>(mapping);
	pMediatorMapping->acquire();
}
	
static void SAL_CALL s_release(uno_Mapping * mapping)
{
	MediatorMapping * pMediatorMapping = static_cast<MediatorMapping *>(mapping);
	pMediatorMapping->release();
}
	
static void SAL_CALL s_mapInterface(
	uno_Mapping                      * mapping,
	void                            ** ppOut, 
	void                             * pInterface,
	typelib_InterfaceTypeDescription * pInterfaceTypeDescr)
{
	MediatorMapping   * pMediatorMapping  = static_cast<MediatorMapping *>(mapping);
	pMediatorMapping->mapInterface(ppOut, pInterface, pInterfaceTypeDescr);
}
}

MediatorMapping::MediatorMapping(uno_Environment * pFrom, 
								 uno_Environment * pInterm,
								 uno_Environment * pTo)
    : m_refCount(0),
	  m_from2uno(pFrom, pInterm),
	  m_uno2to  (pInterm, pTo),
	  m_from    (pFrom),
	  m_interm  (pInterm),
	  m_to      (pTo)
{
	LOG_LIFECYLE_MediatorMapping_emit(std::cerr << __FUNCTION__ << std::endl);

	if (!m_from2uno.get() || !m_uno2to.get())
		abort();

	uno_Mapping::acquire      = s_acquire;
	uno_Mapping::release      = s_release;
	uno_Mapping::mapInterface = s_mapInterface;
}

MediatorMapping::~MediatorMapping() 
{
	LOG_LIFECYLE_MediatorMapping_emit(std::cerr << __FUNCTION__ << std::endl);
}

void MediatorMapping::acquire(void)
{
	LOG_LIFECYLE_MediatorMapping_emit(std::cerr << __FUNCTION__ << std::endl);

	osl_incrementInterlockedCount(&m_refCount);
}

void MediatorMapping::release(void)
{
	LOG_LIFECYLE_MediatorMapping_emit(std::cerr << __FUNCTION__ << std::endl);

	if (osl_decrementInterlockedCount(&m_refCount) == 0)
	{
        ::uno_revokeMapping(this);
	}
}

extern "C" { static void s_mapInterface_v(va_list * pParam)
{
	void                            ** ppOut               = va_arg(*pParam, void **);
	void                             * pInterface          = va_arg(*pParam, void *);
	typelib_InterfaceTypeDescription * pInterfaceTypeDescr = va_arg(*pParam, typelib_InterfaceTypeDescription *);
	uno_Mapping                      * pMapping            = va_arg(*pParam, uno_Mapping *);

	pMapping->mapInterface(pMapping, ppOut, pInterface, pInterfaceTypeDescr);
}}

void MediatorMapping::mapInterface(
	void                            ** ppOut, 
	void                             * pInterface,
	typelib_InterfaceTypeDescription * pInterfaceTypeDescr)
{
	if (*ppOut != 0)
	{
		uno_ExtEnvironment * env = m_to.get()->pExtEnv;
		OSL_ASSERT( env != 0 );
		env->releaseInterface( env, *ppOut );
		*ppOut = NULL;
	}

	void * ret = 0;
	uno_Interface * pUnoI = 0;

	m_from.invoke(s_mapInterface_v, &pUnoI, pInterface, pInterfaceTypeDescr, m_from2uno.get());
		
	m_uno2to.mapInterface(&ret, pUnoI, pInterfaceTypeDescr);
		
	if (pUnoI)
		m_interm.get()->pExtEnv->releaseInterface(m_interm.get()->pExtEnv, pUnoI);
	
	*ppOut = ret;
}

extern "C" { static void SAL_CALL s_MediatorMapping_free(uno_Mapping * pMapping)
    SAL_THROW_EXTERN_C()
{
    delete static_cast<MediatorMapping *>(pMapping);
}}



static rtl::OUString getPrefix(rtl::OUString const & str1, rtl::OUString const & str2)
{
	sal_Int32 nIndex1 = 0;
	sal_Int32 nIndex2 = 0;
	sal_Int32 sim = 0;
	
	rtl::OUString token1;
	rtl::OUString token2;
	
	do
	{
		token1 = str1.getToken(0, ':', nIndex1);
		token2 = str2.getToken(0, ':', nIndex2);

		if (token1.equals(token2))
			sim += token1.getLength() + 1;
	}
	while(nIndex1 == nIndex2 && nIndex1 >= 0 && token1.equals(token2));

	rtl::OUString result;

	if (sim)
		result = str1.copy(0, sim - 1);

	return result;
}

// 	rtl::OUString str1(RTL_CONSTASCII_USTRINGPARAM("abc:def:ghi"));
// 	rtl::OUString str2(RTL_CONSTASCII_USTRINGPARAM("abc:def"));
// 	rtl::OUString str3(RTL_CONSTASCII_USTRINGPARAM("abc"));
// 	rtl::OUString str4(RTL_CONSTASCII_USTRINGPARAM(""));

// 	rtl::OUString pref;

// 	pref = getPrefix(str1, str1);
// 	pref = getPrefix(str1, str2);
// 	pref = getPrefix(str1, str3);
// 	pref = getPrefix(str1, str4);

// 	pref = getPrefix(str2, str1);
// 	pref = getPrefix(str3, str1);
// 	pref = getPrefix(str4, str1);


void getCascadeMapping(uno_Mapping     ** ppMapping,
					   uno_Environment  * pFrom,
					   uno_Environment  * pTo,
					   rtl_uString      * pAddPurpose)
{
	if (pAddPurpose && pAddPurpose->length)
		return;

	rtl::OUString uno_envType(RTL_CONSTASCII_USTRINGPARAM(UNO_LB_UNO));

	rtl::OUString from_envType    = cppu::EnvDcp::getTypeName(pFrom->pTypeName);
	rtl::OUString to_envType      = cppu::EnvDcp::getTypeName(pTo->pTypeName);
	rtl::OUString from_envPurpose = cppu::EnvDcp::getPurpose(pFrom->pTypeName);
	rtl::OUString to_envPurpose   = cppu::EnvDcp::getPurpose(pTo->pTypeName);

#ifdef LOG_CALLING_named_purpose_getMapping
	rtl::OString s_from_name = rtl::OUStringToOString(pFrom->pTypeName, RTL_TEXTENCODING_ASCII_US);
	rtl::OString s_to_name   = rtl::OUStringToOString(pTo->pTypeName,   RTL_TEXTENCODING_ASCII_US);

	std::cerr << __FUNCTION__ << " - creating mediation ";
	std::cerr << "pFrom: " << s_from_name.getStr();
	std::cerr <<" pTo: "   << s_to_name.getStr() << std::endl;
#endif

	if (from_envPurpose == to_envPurpose) // gcc:bla => uno:bla
		return;

	// reaching this point means, we need a mediated mapping!!!
	// we generall mediate via uno[:free]
	uno_Environment * pInterm = NULL;

	// chained uno -> uno
	if (from_envType == uno_envType && to_envType == uno_envType)
	{
		rtl::OUString purpose = getPrefix(from_envPurpose, to_envPurpose);

		rtl::OUString uno_envDcp = uno_envType;
		uno_envDcp += purpose;

		// direct mapping possible?
		// uno:bla-->uno:bla:blubb
		if (from_envPurpose.equals(purpose)) 
		{
			rtl::OUString rest = to_envPurpose.copy(purpose.getLength());

			sal_Int32 index = rest.indexOf(':', 1);
			if (index == -1)
			{
				uno_getMapping(ppMapping, pFrom, pTo, rest.copy(1).pData);
				return;
			}

			uno_envDcp += rest.copy(0, index);
		}
		else if (to_envPurpose.equals(purpose))
		{
			rtl::OUString rest = from_envPurpose.copy(purpose.getLength());

			sal_Int32 index = rest.indexOf(':', 1);
			if (index == -1)
			{
				uno_getMapping(ppMapping, pFrom, pTo, rest.copy(1).pData);
				return;
			}

			uno_envDcp += rest.copy(0, index);
		}
		
		uno_getEnvironment(&pInterm, uno_envDcp.pData, NULL);
	}
	else if (from_envType != uno_envType && to_envType == uno_envType) // <ANY> -> UNO ?
		// mediate via uno:purpose(fromEnv)
	{
		rtl::OUString     envDcp = uno_envType;

		envDcp += from_envPurpose;
 		uno_getEnvironment(&pInterm, envDcp.pData, NULL);
	}
	else if (from_envType == uno_envType && to_envType != uno_envType) // UNO -> <ANY>?
		// mediate via uno(context)
	{
		rtl::OUString     envDcp = uno_envType;

 		envDcp += to_envPurpose;
		uno_getEnvironment(&pInterm, envDcp.pData, NULL);
	}
	else // everything else 
		// mediate via uno:purpose
	{
		rtl::OUString purpose = getPrefix(from_envPurpose, to_envPurpose);

		rtl::OUString uno_envDcp = uno_envType;
		uno_envDcp += purpose;

		uno_getEnvironment(&pInterm, uno_envDcp.pData, NULL);
	}

	uno_Mapping * pMapping = new MediatorMapping(pFrom, pInterm, pTo);
	pInterm->release(pInterm);


	pMapping->acquire(pMapping);

	::uno_registerMapping(&pMapping, s_MediatorMapping_free, pFrom, pTo, pAddPurpose);
		
	if (*ppMapping)
		(*ppMapping)->release(*ppMapping);

	*ppMapping = pMapping;
}
