/* -*- 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 "istream_utils.h"
#include "conv_utils.h"
#include "utils/macros.h"

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

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

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

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

	return retval;
}

STDMETHODIMP CStream::Seek(LARGE_INTEGER dlibMove,
			   DWORD dwOrigin,
			   ULARGE_INTEGER *plibNewPosition)
{
	BRUTUS::STREAM_SEEK origin;
	switch (dwOrigin) {
	case STREAM_SEEK_SET :
		origin = BRUTUS::BRUTUS_STREAM_SEEK_SET;
		break;
	case STREAM_SEEK_CUR :
		origin = BRUTUS::BRUTUS_STREAM_SEEK_CUR;
		break;
	case STREAM_SEEK_END :
		origin = BRUTUS::BRUTUS_STREAM_SEEK_END;
		break;
	default :
		BRUTUS_LOG_BUG("Unknown dwOrigin in CStream::Seek()");
		return STG_E_INVALIDFUNCTION;
	}

	CORBA::LongLong lib_move = dlibMove.QuadPart;
	CORBA::ULongLong new_pos;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Seek(lib_move,
					  origin,
					  new_pos);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Seek()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	if (plibNewPosition)
		plibNewPosition->QuadPart = new_pos;

	return hr;
}

STDMETHODIMP CStream::SetSize(ULARGE_INTEGER libNewSize)
{
	CORBA::ULongLong brutus_libNewSize = libNewSize.QuadPart;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->SetSize(brutus_libNewSize);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::SetSize()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	return hr;
}

STDMETHODIMP CStream::CopyTo(IStream *pstm,
			     ULARGE_INTEGER cb,
			     ULARGE_INTEGER *pcbRead,
			     ULARGE_INTEGER *pcbWritten)
{
	BRUTUS::seq_octet_var brutus_pv;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Read(brutus_pv.out(),
					  (CORBA::ULong)cb.QuadPart);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Read()");
		return E_FAIL;
	}
	HRESULT hr;
	if (BRUTUS::BRUTUS_S_OK != br) {
		if (!bresult_to_hresult(br, hr)) {
			BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
			return E_FAIL;
		}
		return hr;
	}

	void *pv;
	hr = MAPIAllocateBuffer(brutus_pv->length(), &pv);
	if (S_OK != hr)
		return hr;
	for (CORBA::ULong n=0; n<brutus_pv->length(); n++) {
		((BYTE*)pv)[n] = brutus_pv[n];
	}

	ULONG written;
	try {
		hr = pstm->Write(pv, brutus_pv->length(), &written);
	}
	catch (...) {
		BRUTUS_LOG_ERR("Exception caught");
		MAPIFreeBuffer(pv);
		return E_FAIL;
	}
	MAPIFreeBuffer(pv);

	if (pcbRead)
		pcbRead->QuadPart = brutus_pv->length();
	if (pcbWritten)
		pcbWritten->QuadPart = written;

	return hr;
}

STDMETHODIMP CStream::Commit(DWORD grfCommitFlags)
{
	BRUTUS::STGC flags = brutus_commit_flags(grfCommitFlags);

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Commit(flags);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Commit()");
		return E_FAIL;
	}

	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	return hr;
}

STDMETHODIMP CStream::Revert(void)
{
	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Revert();
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Revert()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	return hr;
}

STDMETHODIMP CStream::LockRegion(ULARGE_INTEGER bOffset,
				 ULARGE_INTEGER cb,
				 DWORD dwLockType)
{
	CORBA::ULongLong brutus_bOffset = bOffset.QuadPart;
	CORBA::ULongLong brutus_cb = cb.QuadPart;

	DWORD lock_type = dwLockType;
	BRUTUS::BDEFINE lock = 0;
	if (lock_type & LOCK_WRITE) {
		lock |= BRUTUS::BRUTUS_LOCK_WRITE;
		FLAGS_OFF(DWORD, lock_type, LOCK_WRITE);
	}
	if (lock_type & LOCK_EXCLUSIVE) {
		lock |= BRUTUS::BRUTUS_LOCK_EXCLUSIVE;
		FLAGS_OFF(DWORD, lock_type, LOCK_EXCLUSIVE);
	}
	if (lock_type & LOCK_ONLYONCE) {
		lock |= BRUTUS::BRUTUS_LOCK_ONLYONCE;
		FLAGS_OFF(DWORD, lock_type, LOCK_ONLYONCE);
	}
	if (lock_type) {
		BRUTUS_LOG_BUG("Unknown dwLockType in CStream::LockRegion()");
		return STG_E_INVALIDFUNCTION;
	}

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->LockRegion(brutus_bOffset,
						brutus_cb,
						lock);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::LockRegion()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	return hr;
}

STDMETHODIMP CStream::UnlockRegion(ULARGE_INTEGER bOffset,
				   ULARGE_INTEGER cb,
				   DWORD dwLockType)
{
	CORBA::ULongLong brutus_bOffset = bOffset.QuadPart;
	CORBA::ULongLong brutus_cb = cb.QuadPart;

	DWORD lock_type = dwLockType;
	BRUTUS::BDEFINE lock = 0;
	if (lock_type & LOCK_WRITE) {
		lock |= BRUTUS::BRUTUS_LOCK_WRITE;
		FLAGS_OFF(DWORD, lock_type, LOCK_WRITE);
	}
	if (lock_type & LOCK_EXCLUSIVE) {
		lock |= BRUTUS::BRUTUS_LOCK_EXCLUSIVE;
		FLAGS_OFF(DWORD, lock_type, LOCK_EXCLUSIVE);
	}
	if (lock_type & LOCK_ONLYONCE) {
		lock |= BRUTUS::BRUTUS_LOCK_ONLYONCE;
		FLAGS_OFF(DWORD, lock_type, LOCK_ONLYONCE);
	}
	if (lock_type) {
		BRUTUS_LOG_BUG("Unknown dwLockType in CStream::UnLockRegion()");
		return STG_E_INVALIDFUNCTION;
	}

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->UnlockRegion(brutus_bOffset,
						  brutus_cb,
						  lock);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::UnLockRegion()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	return hr;
}

STDMETHODIMP CStream::Stat(STATSTG *pstatstg,
			   DWORD grfStatFlag)
{
	BRUTUS::STATFLAG flag;
	switch (grfStatFlag) {
	case STATFLAG_DEFAULT :
		flag = BRUTUS::BRUTUS_STATFLAG_DEFAULT;
		break;
	case STATFLAG_NONAME :
		flag = BRUTUS::BRUTUS_STATFLAG_NONAME;
		break;
	default :
		BRUTUS_LOG_BUG("Unknown grfStatFlag in CStream::Stat()");
		return STG_E_INVALIDFLAG;
	}

	BRUTUS::STATSTG_var stats;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Stat(stats.out(),
					  flag);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Stat()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}
	if (S_OK != hr)
		return hr;

	if (!statstg_brutus_to_mapi(stats.in(), flag, pstatstg))
		return STG_E_INSUFFICIENTMEMORY;

	return S_OK;
}

STDMETHODIMP CStream::Clone(IStream **ppstm)
{
	if (!ppstm)
		return STG_E_INVALIDPOINTER;
	*ppstm = 0;

	BRUTUS::IStream_var b_stream;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Clone(b_stream.out());
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Clone()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		return E_FAIL;
	}

	CStream *stream = 0;
	try {
		stream = new CStream(b_stream.in());
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught");
		return STG_E_INSUFFICIENTMEMORY;
	}
	*ppstm = stream;

	return hr;
}


STDMETHODIMP CStream::Read(void *pv,
			   ULONG cb,
			   ULONG *pcbRead)
{
	BRUTUS::seq_octet_var brutus_pv;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Read(brutus_pv.out(),
					  (CORBA::ULong)cb);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Read()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	if (S_OK == hr) {
		for (CORBA::ULong n=0; n<brutus_pv->length(); n++) {
			((BYTE*)pv)[n] = brutus_pv[n];
		}
		if (pcbRead)
			*pcbRead = brutus_pv->length();
	}

	return hr;
}

STDMETHODIMP CStream::Write(const void *pv,
			    ULONG cb,
			    ULONG *pcbWritten)
{
	BRUTUS::seq_octet_var brutus_pv;
	try {
		brutus_pv = new BRUTUS::seq_octet;
	}
	catch (...) {
		BRUTUS_LOG_ERR("Exception caught");
		return E_FAIL;
	}
	brutus_pv->length(cb);
	for (CORBA::ULong n=0; n<cb; n++) {
		brutus_pv[n] = ((BYTE*)pv)[n];
	}

	CORBA::ULong brutus_pcbWritten;

	BRUTUS::BRESULT br;
	try {
		br = brutus_stream_->Write(brutus_pv.in(),
					   brutus_pcbWritten);
	}
	catch (...) {
		BRUTUS_LOG_BUG("Exception caught from client in CStream::Write()");
		return E_FAIL;
	}
	HRESULT hr;
	if (!bresult_to_hresult(br, hr)) {
		BRUTUS_LOG_BUG("Could not convert BRESULT into HRESULT");
		hr = E_FAIL;
	}

	if (pcbWritten)
		*pcbWritten = brutus_pcbWritten;

	return hr;
}
