/* -*- 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 "ISequentialStreamS_impl.h"
#include "obj_utils.h"
#include "conv_utils.h"

BRUTUS_ISequentialStream_i::BRUTUS_ISequentialStream_i(::ISequentialStream *SequentialStream,
						       LPMAPISESSION MAPISession,
						       ::PortableServer::POA_ptr Poa)
	: seq_stream_(SequentialStream),
	  mapi_session_(MAPISession),
	  poa_(::PortableServer::POA::_duplicate(Poa))
{
	seq_stream_->AddRef();
	mapi_session_->AddRef();
}

::BRUTUS::BRESULT 
BRUTUS_ISequentialStream_i::Read(::BRUTUS::seq_octet_out pv,
				 ::CORBA::ULong cb)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ISequentialStream_i::Read()");

	void *mapi_pv;
	HRESULT hr = MAPIAllocateBuffer((ULONG)cb, &mapi_pv);
	if (S_OK != hr) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}

	ULONG mapi_pcbRead = 0;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = seq_stream_->Read(mapi_pv,
				       (ULONG)cb,
				       &mapi_pcbRead);
	}
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	::BRUTUS::seq_octet_var brutus_pv;
	try {
		brutus_pv = new ::BRUTUS::seq_octet(mapi_pcbRead);
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		MAPIFreeBuffer(pv);
		throw ::CORBA::NO_MEMORY();
	}
	if (::BRUTUS::BRUTUS_S_OK == br) {
		for (ULONG n=0; n<mapi_pcbRead; n++) {
			brutus_pv[n] = ((::CORBA::Octet*)mapi_pv)[n];
		}
	}
	MAPIFreeBuffer(pv);
	pv = brutus_pv._retn();

	BRUTUS_LOG_INF("Leaving BRUTUS_ISequentialStream_i::Read()");
	return br;
}

::BRUTUS::BRESULT
BRUTUS_ISequentialStream_i::Write(const ::BRUTUS::seq_octet &pv,
				  ::CORBA::ULong_out pcbWritten)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ISequentialStream_i::Write()");

	void *mapi_pv;
	HRESULT hr = MAPIAllocateBuffer((ULONG)pv.length(), &mapi_pv);
	if (S_OK != hr) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}
	for (::CORBA::ULong n=0; n<pv.length(); n++) {
		((BYTE*)mapi_pv)[n] = (BYTE)pv[n];
	}

	ULONG mapi_written = 0;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = seq_stream_->Write(mapi_pv,
					(ULONG)pv.length(),
					&mapi_written);
	}
	MAPIFreeBuffer(mapi_pv);
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	pcbWritten = mapi_written;

	BRUTUS_LOG_INF("Leaving BRUTUS_ISequentialStream_i::Write()");
	return br;
}

::BRUTUS::BRESULT 
BRUTUS_ISequentialStream_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 = seq_stream_->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 (S_OK == hr) {
		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_ISequentialStream_i::Destroy(::CORBA::ULong InstanceID)
{
	BRUTUS_LOG_INF("Entering BRUTUS_ISequentialStream_i::Destroy()");

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

	poa_->deactivate_object(oid);

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