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

#include "IExchangeExportChangesS_impl.h"
#include "IExchangeImportHierarchyChangesC.h"
#include "IExchangeImportContentsChangesC.h"

#include "obj_utils.h"
#include "conv_utils.h"
#include "istream_utils.h"
#include "ieicc_utils.h"
#include "ieihc_utils.h"

#include <ace/String_Base.h>

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

	if (!Flags)
		return 0;

	if (flags & ::BRUTUS::BRUTUS_SYNC_UNICODE) {
		retval |= SYNC_UNICODE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_SYNC_UNICODE);
	}
	if (flags & ::BRUTUS::BRUTUS_SYNC_NO_DELETIONS) {
		retval |= SYNC_NO_DELETIONS;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_SYNC_NO_DELETIONS);
	}
	if (flags & ::BRUTUS::BRUTUS_SYNC_READ_STATE) {
		retval |= SYNC_READ_STATE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_SYNC_READ_STATE);
	}
	if (flags & ::BRUTUS::BRUTUS_SYNC_ASSOCIATED) {
		retval |= SYNC_ASSOCIATED;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_SYNC_ASSOCIATED);
	}
	if (flags & ::BRUTUS::BRUTUS_SYNC_NORMAL) {
		retval |= SYNC_NORMAL;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_SYNC_NORMAL);
	}

	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_IExchangeExportChanges_i::BRUTUS_IExchangeExportChanges_i(LPEXCHANGEEXPORTCHANGES ExchangeExportChanges,
								 LPMAPISESSION MAPISession,
								 ::PortableServer::POA_ptr Poa)
	: exchange_export_changes_(ExchangeExportChanges),
	  mapi_session_(MAPISession),
	  poa_(::PortableServer::POA::_duplicate(Poa))
{
	mapi_session_->AddRef();
}

::BRUTUS::BRESULT BRUTUS_IExchangeExportChanges_i::GetLastError(::BRUTUS::BRESULT ReturnCode,
								::BRUTUS::BDEFINE ulFlags,
								::BRUTUS::MAPIERROR_out lppMAPIError)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IExchangeExportChanges_i::GetLastError()");

	::BRUTUS::MAPIERROR_var error;
	try {
		error = new ::BRUTUS::MAPIERROR;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}
	error->ulVersion = (::CORBA::ULong)0;
	error->ulLowLevelError = (::CORBA::ULong)0;
	error->ulContext = (::CORBA::ULong)0;

	HRESULT hr;
	if (!bresult_to_hresult(ReturnCode, hr)) {
		lppMAPIError = error._retn();
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
	}

	LPMAPIERROR mapi_error = NULL;
	unsigned long flags = native_flags(ulFlags);
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = exchange_export_changes_->GetLastError(hr, flags, &mapi_error);
	}

	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		MAPIFreeBuffer(mapi_error);
		lppMAPIError = error._retn();
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	if (!mapi_error)
		BRUTUS_LOG_INF("No applicable error information from MAPI");

	mapierror_mapi_to_brutus(mapi_error, error, true);

	lppMAPIError = error._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_IExchangeExportChanges_i::GetLastError");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IExchangeExportChanges_i::Config(::BRUTUS::IStream_ptr lpStream,
							  ::BRUTUS::BDEFINE ulFlags,
							  ::BRUTUS::IUnknown_ptr lpUnk,
							  ::BRUTUS::SRestrictionContainer_ptr lpRestriction,
							  const ::BRUTUS::SPropTagArray & lpIncludeProps,
							  const ::BRUTUS::SPropTagArray & lpExcludeProps,
							  ::CORBA::ULong ulBufferSize)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IExchangeExportChanges_i::Config()");

	LPSTREAM mapi_stream = NULL;
	try {
		mapi_stream = new CStream(lpStream);
	}
	catch (...) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}

	unsigned long flags = native_flags(ulFlags);

	// figure out which interface to support
	::BRUTUS::BRESULT br;
	LPUNKNOWN mapi_unk = NULL;
	try {
		::BRUTUS::IExchangeImportHierarchyChanges_var ieihc = ::BRUTUS::IExchangeImportHierarchyChanges::_nil();
		ieihc = ::BRUTUS::IExchangeImportHierarchyChanges::_narrow(lpUnk);
		if (!::CORBA::is_nil(ieihc)) {
			CExchangeImportHierarchyChanges *mapi_ieihc = NULL;
			try {
				mapi_ieihc = new CExchangeImportHierarchyChanges(ieihc,
										 mapi_session_,
										 poa_.in());
			}
			catch (...) {
				BRUTUS_LOG_ERR("No memory");
				throw ::CORBA::NO_MEMORY();
			}
			mapi_unk = mapi_ieihc;
		} else {
			::BRUTUS::IExchangeImportContentsChanges_var ieicc  = ::BRUTUS::IExchangeImportContentsChanges::_nil();
			ieicc = ::BRUTUS::IExchangeImportContentsChanges::_narrow(lpUnk);
			if (!::CORBA::is_nil(ieicc)) {
				CExchangeImportContentsChanges *mapi_ieicc = NULL;
				try {
					mapi_ieicc = new CExchangeImportContentsChanges(ieicc,
											mapi_session_,
											poa_.in());
				}
				catch (...) {
					BRUTUS_LOG_ERR("No memory");
					throw ::CORBA::NO_MEMORY();
				}
				mapi_unk = mapi_ieicc;
			} else {
				BRUTUS_LOG_ERR("Unsupported interface from client");
				return ::BRUTUS::BRUTUS_MAPI_E_INVALID_PARAMETER;
			}
		}
	} catch (...) {
		BRUTUS_LOG_ERR("Exception caught from client");
		return ::BRUTUS::BRUTUS_UNKNOWN_ERROR;
	}
	if (!mapi_unk)
		return ::BRUTUS::BRUTUS_UNKNOWN_ERROR;

	SRestriction *mapi_res = NULL;
	if (!::CORBA::is_nil(lpRestriction)) {
		::BRUTUS::SRestriction_var brutus_res;
		try {
			brutus_res = lpRestriction->content();
		}
		catch (const ::CORBA::Exception &e) {
			mapi_unk->Release();
			BRUTUS_LOG_CRITICAL(e._info().c_str());
			return ::BRUTUS::BRUTUS_EXCEPTION_FROM_CLIENT;
		}
		if (!srestriction_brutus_to_mapi(brutus_res.in(), 0, mapi_res)) {
			mapi_unk->Release();
			MAPIFreeBuffer(mapi_res);
			BRUTUS_LOG_BUG("Conversion error");
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
	}

	SPropTagArray *include_tags = NULL;
	proptag_array_brutus_to_mapi(&lpIncludeProps, 0, include_tags);
	SPropTagArray *exclude_tags = NULL;
	proptag_array_brutus_to_mapi(&lpExcludeProps, 0, exclude_tags);

	ULONG buf_size = (ULONG)ulBufferSize;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = exchange_export_changes_->Config(mapi_stream,
						      flags,
						      mapi_unk,
						      mapi_res,
						      include_tags,
						      exclude_tags,
						      buf_size);
	}
	MAPIFreeBuffer(include_tags);
	MAPIFreeBuffer(exclude_tags);

	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_IExchangeExportChanges_i::Config()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IExchangeExportChanges_i::Synchronize(::CORBA::ULong_out lpulSteps,
							       ::CORBA::ULong_out lpulProgress)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IExchangeExportChanges_i::Synchronize()");

	ULONG steps = 0;
	ULONG progress = 0;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = exchange_export_changes_->Synchronize(&steps, &progress);
	}
	lpulSteps = steps;
	lpulProgress = progress;

	::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_IExchangeExportChanges_i::Synchronize()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IExchangeExportChanges_i::UpdateState(::BRUTUS::IStream_ptr lpStream)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IExchangeExportChanges_i::Synchronize()");

	CStream *stream = NULL;
	if (!::CORBA::is_nil(lpStream)) {
		try {
			stream = new CStream(lpStream);
		}
		catch (...) {
			BRUTUS_LOG_ERR("No memory");
			throw ::CORBA::NO_MEMORY();
		}
	}

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = exchange_export_changes_->UpdateState(stream);
	}
	if (stream)
		stream->Release();

	::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_IExchangeExportChanges_i::Synchronize()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IExchangeExportChanges_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 = exchange_export_changes_->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();
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
	}

	return br;
}

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

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

	poa_->deactivate_object(oid);

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