//------------------------------------------------------------
// $Id: SendBuffer.h,v 1.1.1.1 1999/11/25 06:26:09 yamauchi Exp $
//------------------------------------------------------------
// SendBuffer
//    ある方向 (rank) に対する送信バッファ
//    たとえば object (ray) を送信する際には以下のことを行う．
//	1. object pointer のキューに object のポインタをためておく．
//	2. ある程度たまったら object の実体を送信用のバッファにコピー
//	   してそのバッファを通信ライブラリに渡して送信する．
//	   ここで受信を考えて実体のバッファのサイズは固定とする．
//	   (ここでの mRealBufSize まで) 送信時にはバッファを lock する． 
//	3. 通信ライブラリが実体を送信終了したら実体のバッファを unlock 
//	   する． 
//    
//   1999年4月13日(火)
//	Copyright (C) 1999 YAMAUCHI Hitoshi 山内 斉
//------------------------------------------------------------
#ifndef _SendBuffer_h
#define _SendBuffer_h 1

#include <assert.h>
#include "mpi2.h"
#include "req_handle_t.h"
#include "mySLQueue.h"
#include "Statistics.h"
#include "ProfileMon.h"

//------------------------------------------------------------
// SendBuffer : ある一つの方向の送信用のバッファ
//------------------------------------------------------------
template < class T >
class SendBuffer {
private:
    SendBuffer(const SendBuffer < T > & rhs);
    const SendBuffer < T > & operator=(const SendBuffer < T > & rhs);

protected:
    mySLQueue < T * > mQueue;	// 送信するもののポインタキュー
    
    YorN mIsLockRealBuffer;	// RealBuffer を lock 中なら YES = 送信中
    int  mRealBufSize;		// 送信用バッファのサイズ (実体)
    T   *mpRealBuff;		// 送信するものの実体を入れるバッファ
    reqHandle_st mReqHandle;	// 送信リクエストハンドル

public:
    CommunBufStatistics mCBStat;
    
protected:
    // 実体配列に lock をかける
    void lockRealbuffer(void)
    {  
	assert(this->mIsLockRealBuffer == NO);
	this->mIsLockRealBuffer = YES;	
    }

    // 実体配列の内容の変更許可 unlock.
    //   この時，buffer は再利用するので delete する必要はない
    void unLockRealbuffer(void)	
    {  
	assert(this->mIsLockRealBuffer == YES);
	this->mIsLockRealBuffer = NO;
    }    

    //------------------------------
    // 送信要求を発行する
    //   ポインタキューにつないであるものの実体を内部のバッファにコピー 
    //   して送信ライブラリに送る．
    //   既にバッファが lock されている場合にはエラー
    //     @param destRank 	送信先の rank
    //	   @return		送信した object 数
    //------------------------------
    int issueSendObjs(int destRank)
    {
	if(this->isLockRealBuffer() == YES)
	{
	    cout << "Error! : Now locking sendbuffer, abort." << endl;
	    cout << "         call unlock before call. desc = "
	    	 << destRank << ", SendBuffer::getRealBuffer." << endl;
	    exit(1);
	}

	// コピーする数は RealBufSize 以下
	int copynum = -1;
	if(this->mQueue.length() < this->mRealBufSize) 
	{
	    copynum = mQueue.length();
	}
	else
	{
	    copynum = this->mRealBufSize;
	}

	// コピー可能な分コピーする
	int i = 0;
	for(i = 0; i < copynum; i++)
	{
	    assert(!(this->mQueue.empty()));
	    T *pObj = this->mQueue.deq();
	    this->mpRealBuff[i] = *pObj;
	    delete pObj;	// ヒープ上の実体の解放
	}
	
	// 使用中にする
	this->lockRealbuffer();

	// 送信要求発行 
	this->mReqHandle = 
		commMpi2_IsendObj(destRank, this->mpRealBuff, copynum);

	// object の送信個数を記録
	this->mCBStat.reportNSendRecvObjs(copynum);

	// Profile Monitor
	ProfileMon::getInstance()->send(destRank, copynum, T::getTAG());

	return(copynum);
    }

    //------------------------------
    // 送信要求は完了したか．完了した場合には lock を外す
    // 	@return 未完から完了に変化した場合に YES を返す
    //------------------------------
    YorN isCompleteSend(void)
    {
	// ロックされていないので，送信要求の完了はない(状態の変化はない)
	if(this->isLockRealBuffer() == NO)
	{
	    return(NO);
	}
	else
	{
	    // ロック中 -> 完了をテストする
	    if(commMpi2_isSendComplete(this->mReqHandle) == YES)
	    {
		this->unLockRealbuffer();
		return(YES);	
	    }
	    else
	    {
		return(NO);	    
	    }
	}
    }

    //------------------------------
    // キューに存在する物体数が threshold を越えたら送信を試みる
    //------------------------------
    YorN tryToIssueSendObjsIfExceedQueueLen(int destRank)
    {
	if((this->isExceedQueueLen() == YES) && 
	   (this->isLockRealBuffer() == NO))
	{
	    this->issueSendObjs(destRank);
	    return(YES);
	}else
	{
	    return(NO);
	}
    }

    //------------------------------
    // 何かキューに存在して送信が可能な場合には送信を試みる
    //   @return 送信した
    //------------------------------
    YorN tryToIssueSendObjs(int destRank)
    {
	if((this->mQueue.length() > 0) && 
	   (this->isLockRealBuffer() == NO))
	{
	    this->issueSendObjs(destRank);
	    return(YES);
	}else
	{
	    return(NO);
	}
    }

    //------------------------------    
    // enqueue 
    //  ここで real buffer サイズを見て送信要求を発行しても良いが，
    //  今回は，送信要求の発行を明示的に行うことにした．1999年5月29日(土)
    //	  @param  pObj : キューの最後に追加
    //    @return 現在のキューのサイズ
    //------------------------------
    int enqueue(T * pObj)
    {
	this->mQueue.enq(pObj);
	this->mCBStat.reportQueLen(this->mQueue.length()); // キュー長の記録
	return(this->getSizeOfQueue()); // 現在のキューのサイズを返す
    }

public:
    SendBuffer(void)
    {
	this->mIsLockRealBuffer	= NO;
	this->mRealBufSize	= -1;
	this->mpRealBuff	= NULL;
	// this->mReqHandle;

	this->mCBStat.init();
    }
    ~SendBuffer(void)
    {
	if(this->mIsLockRealBuffer == YES)
	{
	    cout << "Error! : SendBuffer::~SendBuffer() buffer is locked. "
	    	<< "Queue length = " << this->mQueue.length() << ", Tag " 
		<< T::getTAG() << endl;
	}
	if(this->mRealBufSize > 0)
	{
	    delete [] this->mpRealBuff;
	    this->mpRealBuff = NULL;
	}
    }

    //------------------------------
    // initialize
    // 何度も call 可能
    //	@param sendBufferSize : 送信キューの初期値
    //------------------------------    
    void initBuff(int sendBufferSize) 
    {
	// 内部の queue に何か残っている場合には初期化できない．
	if(this->mQueue.length() != 0)
	{
	    cout << "Error! : queue has some elements, can not initialize." 
	    << endl;
	    exit(1);
	}

	if(this->mRealBufSize > 0)
	{
	    // re-initialize
	    if(this->mpRealBuff != NULL)
	    {
		delete [] this->mpRealBuff;
		this->mpRealBuff = NULL;
	    }
	}

	// arg check
	if(sendBufferSize < 1)
	{
	    cout << "Warning! : sendBufferSize must be > 0 at SendBuffer, "
	            "set sendBufferSize = 1." << endl;
	    sendBufferSize = 1;
	}

	this->mRealBufSize	= sendBufferSize;
	this->mIsLockRealBuffer	= NO; // 送信中ではない
	this->mpRealBuff	= new T[this->mRealBufSize];

	// init statistics 
	this->mCBStat.init();
    }

    //------------------------------
    // queue 長が送信すべき閾値を越えたか?
    // 	 @return 送信すべき閾値を越えた場合には Yes
    //------------------------------
    YorN isExceedQueueLen(void) 
    {
	return((this->mQueue.length() >= this->mRealBufSize) ? YES : NO);
    }

    //------------------------------
    // 実体のバッファは lock 中か
    //   @return 現在実体配列は送信中につき lock 中か
    //------------------------------
    YorN isLockRealBuffer(void) const 	{ return(this->mIsLockRealBuffer); }

    //------------------------------
    // 実体のバッファのサイズ : lock 中なら正しいサイズを返す
    //   @return 実体配列のサイズ 
    //------------------------------
    int  getSendBufSize(void) const 	{ return(this->mRealBufSize);	}

    //------------------------------
    // 現在 queue に入っているものの数
    // 	 @return キューのサイズ
    //------------------------------
    int getSizeOfQueue(void) 		{ return(this->mQueue.length());}

    //------------------------------
    // queue の最大長 : これまでの最大キュー長を返す
    // 	 @return 最大のキューのサイズ
    // DELETEME
    //------------------------------
    // int getMaxSizeOfQueue(void)		{ return(this->mMaxQueueLen);}
    
    //------------------------------
    // この sendbuffer には仕事がない (== (キュー長 == 0) && (unlocking))
    // 	@return これ以上送信のための仕事がない場合は YES
    //------------------------------
    YorN isNoMoreWork(void)
    {
	return(((this->getSizeOfQueue()== 0) && 
		(this->isLockRealBuffer() == NO)) ? YES : NO);
    }

public:
    //------------------------------------------------------------
    // convenient functions : よく利用される関数群をまとめたもの
    //------------------------------    
    // 物体を内部キューに enqueue し，キュー長が閾値を越えたら送信
    //   @param pEnqueueAndSendObj : 送信する一つの object へのポインタ
    //				     この先は new されていなくてはならない
    //   @param destrank	   : 送信先 の rank
    //	 @return		   : 送信発行時には YES を返す
    //------------------------------
    YorN enqNewedObjwithMaySend(T *pEnqueueAndSendObj, int destrank)
    {
	// enqueue
	this->enqueue(pEnqueueAndSendObj);

	// 送信要求の完了をテスト
	this->isCompleteSend();

	// try to send
	return(this->tryToIssueSendObjsIfExceedQueueLen(destrank));
    }

    //------------------------------
    // この送信バッファの flush
    // 	 @destrank 	: 送信先 の rank
    // 	 @return	: 今回送信したならば YES 
    //------------------------------
    YorN flushdone(int destrank)
    {
	// 送信要求の完了をテスト
	this->isCompleteSend();

	// try to send 
	return(this->tryToIssueSendObjs(destrank));
    }
};

#endif				// _SendBuffer_h
