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

static inline DWORD native_commit_flags(const ::BRUTUS::STGC Flags)
{
	::BRUTUS::STGC flags = Flags;
	DWORD retval = 0;

	if (!Flags)
		return 0;

	if (::BRUTUS::BRUTUS_STGC_DEFAULT == flags) // BRUTUS_STGC_DEFAULT == 0 (zero)
		return 0;

	if (flags & ::BRUTUS::BRUTUS_STGC_OVERWRITE) {
		retval |= STGC_OVERWRITE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_STGC_OVERWRITE);
	}
	if (flags & ::BRUTUS::BRUTUS_STGC_ONLYIFCURRENT) {
		retval |= STGC_ONLYIFCURRENT;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_STGC_ONLYIFCURRENT);
	}
	if (flags & ::BRUTUS::BRUTUS_STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE) {
		retval |= STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE);
	}
	if (flags & ::BRUTUS::BRUTUS_STGC_CONSOLIDATE) {
		retval |= STGC_CONSOLIDATE;
		FLAGS_OFF(::BRUTUS::BDEFINE, flags, ::BRUTUS::BRUTUS_STGC_CONSOLIDATE);
	}

	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_IStream_i::BRUTUS_IStream_i(LPSTREAM Stream,
				   LPMAPISESSION MAPISession,
				   ::PortableServer::POA_ptr Poa)
	:  istream_(Stream),
	   mapi_session_(MAPISession),
	   poa_(::PortableServer::POA::_duplicate(Poa))
{
	mapi_session_->AddRef();
	istream_->AddRef();
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Clone(::BRUTUS::IStream_out ppstm)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Clone()");

	ppstm = ::BRUTUS::IStream::_nil();
	::IStream *mapi_stream;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Clone(&mapi_stream);
	}
	::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)
		ppstm = create_object<BRUTUS_IStream_i, ::BRUTUS::IStream, LPSTREAM>
			(mapi_stream, poa_.in(), mapi_session_);

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::Clone()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Commit(::BRUTUS::STGC grfCommitFlags)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Commit()");

	DWORD flags = native_commit_flags(grfCommitFlags);

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Commit(flags);
	}
	::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_IStream_i::Commit()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::CopyTo(::BRUTUS::IStream_ptr pstm,
					   ::CORBA::ULongLong cb,
					   ::CORBA::ULongLong_out pcbRead,
					   ::CORBA::ULongLong_out pcbWritten)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::CopyTo()");

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

	ULONG read;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Read(buf,
				    (ULONG)cb,
				    &read);
	}
	::BRUTUS::BRESULT br;
	if (S_OK != hr) {
		MAPIFreeBuffer(buf);
		if (!hresult_to_bresult(hr, br)) {
			BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
			return ::BRUTUS::BRUTUS_INTERNAL_ERROR;
		}
		return br;
	}

	::BRUTUS::seq_octet_var brutus_buf;
	try {
		brutus_buf = new ::BRUTUS::seq_octet(read);
	}
	catch (...) {
		BRUTUS_LOG_ERR("No memory");
		MAPIFreeBuffer(buf);
		throw ::CORBA::NO_MEMORY();
	}
	brutus_buf->length(read);

	for (ULONG n=0; n<read; n++) // FIXME, too slow
		brutus_buf[n] = ((BYTE*)buf)[n];
	MAPIFreeBuffer(buf);

	::CORBA::ULong written;
	try {
		br = pstm->Write(brutus_buf.in(), written);
	}
	catch (...) {
		BRUTUS_LOG_ERR("Exception caught from client");
		br = ::BRUTUS::BRUTUS_UNKNOWN_ERROR;
	}

	pcbRead = read;
	pcbWritten = written;

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::CopyTo()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::LockRegion(::CORBA::ULongLong libOffset,
					       ::CORBA::ULongLong cb,
					       ::BRUTUS::LOCKTYPE dwLockType)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::LockRegion()");

	::BRUTUS::LOCKTYPE lock_type = dwLockType;
	DWORD lock = 0;
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_WRITE) {
		lock |= LOCK_WRITE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_WRITE);
	}
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_EXCLUSIVE) {
		lock |= LOCK_EXCLUSIVE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_EXCLUSIVE);
	}
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_ONLYONCE) {
		lock |= LOCK_ONLYONCE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_ONLYONCE);
	}
	if (lock_type) {
		char msg[128] = {0};
		sprintf_s(msg, sizeof(msg), "Unknown dwLockType in BRUTUS_IStream_i::LockRegion() : %X", lock_type);
		BRUTUS_LOG_BUG(msg);
		return ::BRUTUS::BRUTUS_STG_E_INVALIDFUNCTION;
	}
	ULARGE_INTEGER mapi_offset;
	ULARGE_INTEGER mapi_cb;
	mapi_offset.QuadPart = libOffset;
	mapi_cb.QuadPart = cb;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->LockRegion(mapi_offset,
					  mapi_cb,
					  lock);
	}
	::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_IStream_i::LockRegion()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Revert(void)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Revert()");

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Revert();
	}
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::Revert()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Seek(::CORBA::LongLong dlibMove,
					 ::BRUTUS::STREAM_SEEK dwOrigin,
					 ::CORBA::ULongLong_out plibNewPosition)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Seek()");

	DWORD origin;
	switch (dwOrigin) {
	case ::BRUTUS::BRUTUS_STREAM_SEEK_SET :
		origin = STREAM_SEEK_SET;
		break;
	case ::BRUTUS::BRUTUS_STREAM_SEEK_CUR :
		origin = STREAM_SEEK_CUR;
		break;
	case ::BRUTUS::BRUTUS_STREAM_SEEK_END :
		origin = STREAM_SEEK_END;
		break;
	default :
		BRUTUS_LOG_BUG("Unknown dwOrigin in BRUTUS_IStream_i::Seek()");
		return ::BRUTUS::BRUTUS_STG_E_INVALIDFUNCTION;
	}

	ULARGE_INTEGER mapi_newpos;
	LARGE_INTEGER mapi_libmove;
	mapi_libmove.QuadPart = dlibMove;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Seek(mapi_libmove,
				    origin,
				    &mapi_newpos);
	}
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	plibNewPosition = mapi_newpos.QuadPart;

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::Seek()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::SetSize(::CORBA::ULongLong libNewSize)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::SetSize()");

	ULARGE_INTEGER mapi_newsize;
	mapi_newsize.QuadPart = libNewSize;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->SetSize(mapi_newsize);
	}
	::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_IStream_i::SetSize()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Stat(::BRUTUS::STATSTG_out pstatstg,
					 ::BRUTUS::STATFLAG grfStatFlag)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Stat()");

	::BRUTUS::STATSTG_var brutus_stat;
	try {
		brutus_stat = new ::BRUTUS::STATSTG;
	}
	catch (std::bad_alloc &) {
		BRUTUS_LOG_ERR("No memory");
		throw ::CORBA::NO_MEMORY();
	}

	STATFLAG flag;
	switch (grfStatFlag) {
	case ::BRUTUS::BRUTUS_STATFLAG_DEFAULT :
		flag = STATFLAG_DEFAULT;
		break;
	case ::BRUTUS::BRUTUS_STATFLAG_NONAME :
		flag = STATFLAG_NONAME;
		break;
	default :
		BRUTUS_LOG_BUG("Unknown grfStatFlag in IStream::Stat()");
		pstatstg = brutus_stat._retn();
		return ::BRUTUS::BRUTUS_STG_E_INVALIDFLAG;
	}

	STATSTG mapi_stats = { 0 };
	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Stat(&mapi_stats, flag);
	}
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}
	if (::BRUTUS::BRUTUS_S_OK != br)
		return br;

	if (!statstg_mapi_to_brutus(&mapi_stats, flag, brutus_stat.out()))
		return ::BRUTUS::BRUTUS_STG_E_INSUFFICIENTMEMORY;

	if (STATFLAG_DEFAULT == flag)
		CoTaskMemFree((void*)mapi_stats.pwcsName);

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::Stat()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::UnlockRegion(::CORBA::ULongLong libOffset,
						 ::CORBA::ULongLong cb,
						 ::BRUTUS::LOCKTYPE dwLockType)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::UnlockRegion()");

	::BRUTUS::LOCKTYPE lock_type = dwLockType;
	DWORD lock = 0;
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_WRITE) {
		lock |= LOCK_WRITE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_WRITE);
	}
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_EXCLUSIVE) {
		lock |= LOCK_EXCLUSIVE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_EXCLUSIVE);
	}
	if (lock_type & ::BRUTUS::BRUTUS_LOCK_ONLYONCE) {
		lock |= LOCK_ONLYONCE;
		FLAGS_OFF(::BRUTUS::BDEFINE, lock_type, ::BRUTUS::BRUTUS_LOCK_ONLYONCE);
	}
	if (lock_type) {
		BRUTUS_LOG_BUG("Unknown dwLockType in BRUTUS_IStream_i::UnlockRegion()");
		return ::BRUTUS::BRUTUS_STG_E_INVALIDFUNCTION;
	}
	ULARGE_INTEGER mapi_offset;
	ULARGE_INTEGER mapi_cb;
	mapi_offset.QuadPart = libOffset;
	mapi_cb.QuadPart = cb;

	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->UnlockRegion(mapi_offset,
					    mapi_cb,
					    lock);
	}
	::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_IStream_i::UnlockRegion()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::Read(::BRUTUS::seq_octet_out pv,
					 ::CORBA::ULong cb)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_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 = istream_->Read(mapi_pv,
				    (ULONG)cb,
				    &mapi_pcbRead);
	}
	BRUTUS_LOG_HR;

	::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(mapi_pv);
		throw ::CORBA::NO_MEMORY();
	}
	brutus_pv->length(mapi_pcbRead);

	if (::BRUTUS::BRUTUS_S_OK == br) {
		for (ULONG n=0; n<mapi_pcbRead; n++) {
			brutus_pv[n] = ((::CORBA::Octet*)mapi_pv)[n];
		}
	}

	MAPIFreeBuffer(mapi_pv);
	pv = brutus_pv._retn();

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

::BRUTUS::BRESULT BRUTUS_IStream_i::Write(const ::BRUTUS::seq_octet &pv,
					  ::CORBA::ULong_out pcbWritten)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_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 = istream_->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_IStream_i::Write()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_i::GetSize(::CORBA::ULongLong_out Size)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::GetSize()");

	STATSTG mapi_stats = { 0 };
	HRESULT hr;
	{
		ACE_Write_Guard<ACE_RW_Mutex> guard(mutex_);
		hr = istream_->Stat(&mapi_stats, STATFLAG_NONAME);
	}
	::BRUTUS::BRESULT br;
	if (!hresult_to_bresult(hr, br)) {
		BRUTUS_LOG_BUG("Could not convert HRESULT into BRESULT");
		br = ::BRUTUS::BRUTUS_INTERNAL_ERROR;
	}

	Size = mapi_stats.cbSize.QuadPart;

	BRUTUS_LOG_INF("Leaving BRUTUS_IStream_i::GetSize()");
	return br;
}

::BRUTUS::BRESULT BRUTUS_IStream_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 = istream_->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_IStream_i::Destroy(::CORBA::ULong InstanceID)
{
	BRUTUS_LOG_INF("Entering BRUTUS_IStream_i::Destroy()");

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

	poa_->deactivate_object(oid);

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