/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2008 OMC Denmark ApS.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma warning(disable : 4996)

#include "ITnefS_impl.h"
#include "obj_utils.h"
#include "conv_utils.h"
#include "istream_utils.h"

#include "templates.i"

static inline struct MemResource *new_resource(void *blob,
					       bool com_obj)
{
	struct MemResource *retv = new struct MemResource;
	if (!retv)
		throw std::bad_alloc();

	retv->mem = blob;
	retv->com_obj = com_obj;

	return retv;
}

static inline ULONG native_flags(const ::BRUTUS::BDEFINE Flags)
{
	::BRUTUS::BDEFINE flags = Flags;
	ULONG retval = 0;

	if (!Flags)
		return 0;

	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_ATTACHMENTS_ONLY) {
		retval |= TNEF_PROP_ATTACHMENTS_ONLY;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_ATTACHMENTS_ONLY);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_CONTAINED) {
		retval |= TNEF_PROP_CONTAINED;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_CONTAINED);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_CONTAINED_TNEF) {
		retval |= TNEF_PROP_CONTAINED_TNEF;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_CONTAINED_TNEF);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_EXCLUDE) {
		retval |= TNEF_PROP_EXCLUDE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_EXCLUDE);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_INCLUDE) {
		retval |= TNEF_PROP_INCLUDE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_INCLUDE);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_PROP_MESSAGE_ONLY) {
		retval |= TNEF_PROP_MESSAGE_ONLY;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_PROP_MESSAGE_ONLY);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_CREATE) {
		retval |= MAPI_CREATE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_CREATE);
	}
	if (flags & ::BRUTUS::BRUTUS_MAPI_MODIFY) {
		retval |= MAPI_MODIFY;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_MAPI_MODIFY);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_COMPONENT_ATTACHMENT) {
		retval |= TNEF_COMPONENT_ATTACHMENT;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_COMPONENT_ATTACHMENT);
	}
	if (flags & ::BRUTUS::BRUTUS_TNEF_COMPONENT_MESSAGE) {
		retval |= TNEF_COMPONENT_MESSAGE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_TNEF_COMPONENT_MESSAGE);
	}

	if (flags) {
		char msg[128] = {0};
		sprintf_s(msg, sizeof(msg), "Unknown flag(s) from BRUTUS : %X", flags);
		BRUTUS_LOG_BUG(msg);
	}

	return retval;
}


BRUTUS_ITnef_i::BRUTUS_ITnef_i(LPITNEF Tnef,
			       LPMAPISESSION MAPISession,
			       ::PortableServer::POA_ptr Poa)
	: i_tnef_(Tnef),
	  mapi_session_(MAPISession),
	  poa_(::PortableServer::POA::_duplicate(Poa))
{
	mapi_session_->AddRef();
	mems.clear();
}

// FIXME: lpvData can be IStream pointer
::BRUTUS::BRESULT BRUTUS_ITnef_i::AddProps(::BRUTUS::BDEFINE ulFlags,
					   ::CORBA::ULong ulElemID,
					   const ::BRUTUS::seq_octet & lpvData,
					   ::BRUTUS::IStream_ptr stream,
					   const ::BRUTUS::SPropTagArray & lpPropList)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::AddProps()");

	ULONG flags = native_flags(ulFlags);

	void *mapi_void;
	if (flags & TNEF_PROP_CONTAINED_TNEF) {
		try {
			mapi_void = (void*) new CStream(stream);
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	} else if (!seq_octet_brutus_to_mapi(lpvData, 0, mapi_void)) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}

	SPropTagArray *tags = NULL;
	if (!proptag_array_brutus_to_mapi(&lpPropList, 0, tags)) {
		BRUTUS_LOG_ERR("Could not convert Brutus SPropTagArray to MAPI SPropTagArray");
		MAPIFreeBuffer(mapi_void);
		MAPIFreeBuffer(tags);
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->AddProps(flags,
				       (ULONG)ulElemID,
				       mapi_void,
				       tags);

		try {
			mems.push_back(new_resource((void*)tags, false));
			if (flags & TNEF_PROP_CONTAINED_TNEF)
				mems.push_back(new_resource(mapi_void, true));
			else
				mems.push_back(new_resource(mapi_void, false));
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::AddProps()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::ExtractProps(::BRUTUS::BDEFINE ulFlags,
					       const ::BRUTUS::SPropTagArray & lpPropList,
					       ::BRUTUS::STnefProblemArray_out lpProblems)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::ExtractProps()");

	ULONG flags = native_flags(ulFlags);

	::BRUTUS::STnefProblemArray_var tprobs;
	try {
		tprobs = new ::BRUTUS::STnefProblemArray;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}
	LPSTnefProblemArray mapi_probs = NULL;

	SPropTagArray *tags = NULL;
	if (!proptag_array_brutus_to_mapi(&lpPropList, 0, tags)) {
		BRUTUS_LOG_ERR("Could not convert Brutus SPropTagArray to MAPI SPropTagArray");
		MAPIFreeBuffer(tags);
		lpProblems = tprobs._retn();
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->ExtractProps(flags,
					   tags,
					   &mapi_probs);

		try {
			mems.push_back(new_resource((void*)tags, false));
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	if ((hr == S_OK) && (!sTnefProblem_array_mapi_to_brutus(mapi_probs, tprobs.out(), true)))
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	lpProblems = tprobs._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::ExtractProps()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::Finish(::BRUTUS::BDEFINE ulFlags,
					 ::CORBA::UShort_out lpKey,
					 ::BRUTUS::STnefProblemArray_out lpProblems)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::Finish()");

	ULONG flags = native_flags(ulFlags);

	WORD key;

	::BRUTUS::STnefProblemArray_var tprobs;
	try {
		tprobs = new ::BRUTUS::STnefProblemArray;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}
	LPSTnefProblemArray mapi_probs = NULL;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->Finish(flags,
				     &key,
				     &mapi_probs);

		for (unsigned long n=0; n<mems.size(); n++) {
			if (!mems[n]->com_obj) {
				MAPIFreeBuffer(mems[n]->mem);
			} else {
				((LPUNKNOWN)(mems[n]->mem))->Release();
			}
			delete mems[n];
		}
		mems.clear();
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	lpKey = (::CORBA::UShort)key;

	if ((hr == S_OK) && (!sTnefProblem_array_mapi_to_brutus(mapi_probs, tprobs.out(), true)))
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	lpProblems = tprobs._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::Finish()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::OpenTaggedBody(::BRUTUS::IMessage_ptr lpMessage,
						 ::BRUTUS::BDEFINE ulFlags,
						 ::BRUTUS::IStream_out lppStream)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::OpenTaggedBody()");

	::BRUTUS::BRESULT br;

	lppStream = ::BRUTUS::IStream::_nil();

	::CORBA::ULongLong p = (::CORBA::ULongLong)NULL;
	if (!::CORBA::is_nil(lpMessage)) {
		try {
			br = lpMessage->GetLocalPointer(p);
		}
		catch (...) {
			BRUTUS_LOG_ERR("Could not retrive local pointer");
			return ::BRUTUS::BRUTUS_EXCEPTION_FROM_CLIENT;
		}
		if (::BRUTUS::BRUTUS_S_OK != br) {
			BRUTUS_LOG_ERR("Could not retrive local pointer");
			return ::BRUTUS::BRUTUS_UNKNOWN_ERROR;
		}
	}
	LPMESSAGE message = (LPMESSAGE)p;
	if (!valid_mapi_interface_pointer<IMessage>(message))
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;

	ULONG flags = native_flags(ulFlags);

	LPSTREAM mapi_stream = NULL;
	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->OpenTaggedBody(message,
					     flags,
					     &mapi_stream);
	}
	if (message)
		message->Release();

	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	if (hr == S_OK)
		lppStream = create_object<BRUTUS_IStream_i, ::BRUTUS::IStream, LPSTREAM>
			(mapi_stream, poa_.in(), mapi_session_);

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::OpenTaggedBody()");
	return br;
}


::BRUTUS::BRESULT BRUTUS_ITnef_i::SetProps(::BRUTUS::BDEFINE ulFlags,
					   ::CORBA::ULong ulElemID,
					   const ::BRUTUS::SPropValue & lpProps)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::SetProps()");

	ULONG flags = native_flags(ulFlags);

	LPSPropValue mapi_prop = NULL;
	if (!spropvalue_brutus_to_mapi(&lpProps, 0, mapi_prop)) {
		MAPIFreeBuffer(mapi_prop);
		BRUTUS_LOG_ERR("Could not convert Brutus SPropValue to MAPI SPropValue");
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->SetProps(flags, (ULONG)ulElemID, 1, mapi_prop);

		try {
			mems.push_back(new_resource((void*)mapi_prop, false));
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::SetProps()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::EncodeRecips(::BRUTUS::BDEFINE ulFlags,
					       ::BRUTUS::IMAPITable_ptr lpRecipientTable)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::EncodeRecips()");

	::BRUTUS::BRESULT br;
	::CORBA::ULongLong p = (::CORBA::ULongLong)NULL;
	if (!::CORBA::is_nil(lpRecipientTable)) {
		try {
			br = lpRecipientTable->GetLocalPointer(p);
		}
		catch (...) {
			BRUTUS_LOG_ERR("Could not retrive local pointer");
			return ::BRUTUS::BRUTUS_EXCEPTION_FROM_CLIENT;
		}
		if (::BRUTUS::BRUTUS_S_OK != br) {
			BRUTUS_LOG_ERR("Could not retrive local pointer");
			return ::BRUTUS::BRUTUS_UNKNOWN_ERROR;
		}
	}
	LPMAPITABLE table = (LPMAPITABLE)p;
	if (!valid_mapi_interface_pointer<IMAPITable>(table))
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;

	ULONG flags = native_flags(ulFlags);

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->EncodeRecips(flags,
					   table);
	}
	if (table)
		table->Release();

	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::EncodeRecips()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::FinishComponent(::BRUTUS::BDEFINE ulFlags,
						  ::CORBA::ULong ulComponentID,
						  const ::BRUTUS::SPropTagArray & lpCustomPropList,
						  const ::BRUTUS::SPropValue & lpCustomProps,
						  const ::BRUTUS::SPropTagArray & lpPropList,
						  ::BRUTUS::STnefProblemArray_out lpProblems)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::FinishComponent()");

	ULONG flags = native_flags(ulFlags);

	LPSPropTagArray custom_prop_list = NULL;
	if (!proptag_array_brutus_to_mapi(&lpCustomPropList, 0, custom_prop_list)) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}

	::BRUTUS::STnefProblemArray_var tprobs;
	try {
		tprobs = new ::BRUTUS::STnefProblemArray;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		MAPIFreeBuffer(custom_prop_list);
		throw ::CORBA::NO_MEMORY();
	}
	LPSTnefProblemArray mapi_probs = NULL;

	LPSPropValue mapi_prop = NULL;
	if (!spropvalue_brutus_to_mapi(&lpCustomProps, 0, mapi_prop)) {
		MAPIFreeBuffer(mapi_prop);
		MAPIFreeBuffer(custom_prop_list);
		BRUTUS_LOG_ERR("Could not convert Brutus SPropValue to MAPI SPropValue");
		lpProblems = tprobs._retn();
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	LPSPropTagArray prop_list = NULL;
	if (!proptag_array_brutus_to_mapi(&lpPropList, 0, prop_list)) {
		BRUTUS_LOG_ERR("No memory");
		MAPIFreeBuffer(mapi_prop);
		MAPIFreeBuffer(custom_prop_list);
		throw ::CORBA::NO_MEMORY();
	}

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->FinishComponent(flags,
					      (ULONG)ulComponentID,
					      custom_prop_list,
					      mapi_prop,
					      prop_list,
					      &mapi_probs);

		try {
			mems.push_back(new_resource((void*)prop_list, false));
			mems.push_back(new_resource((void*)mapi_prop, false));
			mems.push_back(new_resource((void*)custom_prop_list, false));
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	if ((hr == S_OK) && (!sTnefProblem_array_mapi_to_brutus(mapi_probs, tprobs.out(), true)))
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	lpProblems = tprobs._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::FinishComponent()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_ITnef_i::QueryInterface(const char *iid,
						 ::BRUTUS::IUnknown_out ppvObject)
{
	ppvObject = ::BRUTUS::IUnknown::_nil();

	GUID mapi_iid;
	if (!guid_brutus_to_mapi_no_alloc(iid, &mapi_iid)) {
		BRUTUS_LOG_BUG("Could not convert ::BRUTUS::GUID into MAPI GUID");
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
	}

	HRESULT hr;
	void *object = NULL;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = i_tnef_->QueryInterface(mapi_iid, &object);
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	if (hr == S_OK) {
		if (!create_brutus_object(iid, (LPUNKNOWN)object, poa_, ppvObject, mapi_session_)) {
			BRUTUS_LOG_BUG("Could not create brutus object.");
			((LPUNKNOWN)object)->Release();
			br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
	}

	return br;
}

void BRUTUS_ITnef_i::Destroy(::CORBA::ULong InstanceID)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ITnef_i::Destroy()");

	::PortableServer::ObjectId_var oid;
	oid = poa_->servant_to_id(this);

	poa_->deactivate_object(oid);

	BRUTUS_LOG_INF("Leaving BRUTUS_ITnef_i::Destroy()");
}
