/*
 * $Id: SendBuffer.java,v 1.7.4.1 2000/06/15 20:41:23 yamauchi Exp $
 *
 *     (M\pi)^2 : SendBuffer 
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * SendBuffer
 *    ある方向 (rank) に対する送信バッファ
 *    たとえば object (ray) を送信する際には以下のことを行う．
 *	1. object pointer のキューに object のポインタをためておく．
 *	2. ある程度たまったら object の実体を送信用のバッファにコピー
 *	   してそのバッファを通信ライブラリに渡して送信する．
 *	   ここで受信を考えて実体のバッファのサイズは固定とする．
 *	   (ここでの mRealBufSize まで) 送信時にはバッファを lock する． 
 *	3. 通信ライブラリが実体を送信終了したら実体のバッファを unlock 
 *	   する． 
 *
 * original source SendBuffer.h
 *
 * 1999年8月19日(木)
 *
 * EncodedData 用に変更 1999年9月22日(水)
 */

package jp.ac.tohoku.is.archi.mpi2.net;

import  mpi.*;
import  jp.ac.tohoku.is.archi.mpi2.util.*;
import  jp.ac.tohoku.is.archi.mpi2.common.*;
import  jp.ac.tohoku.is.archi.mpi2.sysparam.*;

/**
 * SendBuffer : ある一つの方向の送信用のバッファ
 */
public final class SendBuffer {
    private Queue       mQueue;		   // 送信するもののキュー
    private boolean     mIsLockRealBuffer; // Bufferがlock中ならtrue = 送信中
    private int	        mMaxSendNumParOnce;// 一度に送信する最大オブジェクト数
    private EncodedData	mEncodedBuf;	   // Serializeした送信実体のバッファ
    private Request     mReqHandle;	   // 送信リクエストハンドル
    private int         mObjectTag;	   // このSendBufferが送信するObjのTAG

    private CommunBufStatistics mCBStat;   // 統計情報
    
    // 実体配列に lock をかける
    private synchronized void lockRealbuffer()
    {  
	Assert.out(this.mIsLockRealBuffer == false, 
		   "Error!: already locked. lockRealbuffer");
	this.mIsLockRealBuffer = true;	
    }

    // 実体配列の内容の変更許可 unlock.
    //   この時，buffer は再利用するので delete する必要はない
    private synchronized void unLockRealbuffer()	
    {  
	Assert.out(this.mIsLockRealBuffer == true, 
		   "Error!: already unlocked. unLockRealbuffer");
	this.mIsLockRealBuffer = false;
    }    

    /**
     * 送信要求を発行する
     *   ポインタキューにつないであるものの実体を内部のバッファにコピー 
     *   して送信ライブラリに送る．
     *   既にバッファが lock されている場合にはエラー
     *     @param destRank 	送信先の rank
     *	   @return		送信した object 数
     */
    private synchronized int issueSendObjs(int destRank)
    {
	if(this.isLockRealBuffer() == true)
	{
	    String s = 
		"Error! : Now locking sendbuffer.\n" + 
		"         call unlock before call. destrank = " +
		destRank + ", SendBuffer.issueSendObjs.";
	    throw new RuntimeException(s);
	}
	
	// コピーする数は mMaxSendNumParOnce 以下 
	// 	= 最大 mMaxSendNumParOnce 送信する
	int copyObjNum = -1;
	if(this.mQueue.length() < this.mMaxSendNumParOnce) 
	{
	    copyObjNum = this.mQueue.length();
	}
	else
	{
	    copyObjNum = this.mMaxSendNumParOnce;
	}

	// コピー可能な分だけコピーする
	int i = 0;
	Encodable [] sendarray = new Encodable[copyObjNum];
	for(i = 0; i < copyObjNum; i++)
	{
	    Assert.out(this.mQueue.hasElements(), 
		       "Error!: No more objects. SendBuffer.issueSendObjs");
	    sendarray[i] = (Encodable)this.mQueue.deq();
	}
	
	// バッファを使用中にする
	this.lockRealbuffer();

	// Serialize したバッファを生成．これが lock される
	this.mEncodedBuf = Marshal.createEncodedData(sendarray);
	

	// 送信要求発行 
	Mpi2Communicator comm = Mpi2CommFactory.getInstance();
	this.mReqHandle = 
	    comm.IsendObj(destRank, this.mEncodedBuf, this.mObjectTag);

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

	// Profile Monitor
	ProfileMon.getInstance().send(destRank, copyObjNum, this.mObjectTag);

	return(copyObjNum);
    }

    /**
     * 送信要求は完了したか．完了した場合には lock を外す
     * 	@return 未完から完了に変化した場合に true を返す
     */
    private synchronized boolean isCompleteSend()
    {
	// ロックされていないので，送信要求の完了はない(状態の変化はない)
	if(this.isLockRealBuffer() == false)
	{
	    return(false);
	}
	else
	{
	    // ロック中 . 完了をテストする
	    Mpi2Communicator comm = Mpi2CommFactory.getInstance();
	    if(comm.isSendComplete(this.mReqHandle) == true)
	    {
		this.unLockRealbuffer();
		return(true);	
	    }
	    else
	    {
		return(false);	    
	    }
	}
    }

    /**
     * キューに存在する物体数が threshold を越えたら送信を試みる
     */
    private synchronized boolean 
    tryToIssueSendObjsIfExceedQueueLen(int destRank)
    {
	if((this.isExceedQueueLen() == true) && 
	   (this.isLockRealBuffer() == false))
	{
	    this.issueSendObjs(destRank);
	    return(true);
	}else
	{
	    return(false);
	}
    }

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

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

    /*------------------------------------------------------------
     * public functions
     *-----------------------------------------------------------*/
    public SendBuffer()
    {
	// PROF this.mQueue		= new VectorQueue();
	this.mQueue		= new RingBufferQueue();
	this.mIsLockRealBuffer	= false;
	this.mMaxSendNumParOnce	= -1;
	this.mEncodedBuf	= null;
	this.mReqHandle		= null;
	this.mObjectTag		= Mpi2Enum.TAG_ELSE;
	this.mCBStat		= new CommunBufStatistics();
    }

    /**
     * initialize
     * 何度も call 可能
     *	@param sendBufferSize : 送信キューの初期値
     */
    public synchronized void initBuff(int sendThreshold, int objectTAG) 
    {
	// 内部の queue に何か残っている場合には初期化できない．
	if((this.mQueue != null) && (this.mQueue.length() != 0))
	{
	    throw new RuntimeException("Error! : queue has some elements, " + 
				       "can not initialize.");
	}

	// arg check
	if(sendThreshold < 1)
	{
	    System.out.println("Warning! : sendThreshold must be > 0 at " +
			       "SendBuffer.initBuff, set sendThreshold = 1.");
	    sendThreshold = 1;
	}

	this.mIsLockRealBuffer	= false; // 送信中ではない
	this.mMaxSendNumParOnce	= sendThreshold;
	// this.mEncodedBuf	= null;
	// this.mReqHandle	= null;
	this.mObjectTag		= objectTAG;
	
	// init statistics 
	this.mCBStat.init();
    }

    /**
     * queue 長が送信すべき閾値を越えたか?
     * 	 @return 送信すべき閾値を越えた場合には Yes
     */
    public synchronized boolean isExceedQueueLen() 
    {
	return(this.mQueue.length() >= this.mMaxSendNumParOnce);
    }

    /**
     * 実体のバッファは lock 中か
     *   @return 現在実体配列は送信中につき lock 中か
     */
    public boolean isLockRealBuffer() 	
    { 
	return(this.mIsLockRealBuffer); 
    }

    /**
     * 実体のバッファのサイズ : lock 中なら正しいサイズを返す
     *   @return 実体配列のサイズ 
     */
    public int  getSendBufSize() 	
    {
	return(this.mMaxSendNumParOnce);	
    }

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

    /**
     * この sendbuffer には仕事がない (== (キュー長 == 0) && (unlocking))
     * 	@return これ以上送信のための仕事がない場合は true
     */
    public synchronized boolean isNoMoreWork()
    {
	return((this.getSizeOfQueue()   == 0) && 
	       (this.isLockRealBuffer() == false));
    }


    /*------------------------------------------------------------
     * convenient functions : よく利用される関数群をまとめたもの
     *-----------------------------------------------------------*/
    /**
     * 物体を内部キューに enqueue し，キュー長が閾値を越えたら送信
     *   @param enqueueAndSendObj  : 送信する一つの object 
     *   @param destrank	   : 送信先 の rank
     *	 @return		   : 送信発行時には true を返す
     */
    public synchronized boolean 
    enqNewedObjwithMaySend(Object enqueueAndSendObj, int destrank)
    {
	// enqueue
	this.enqueue(enqueueAndSendObj);

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

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

    /**
     * この送信バッファの flush
     * 	 @destrank 	: 送信先 の rank
     * 	 @return	: 今回送信したならば true 
     */
    public synchronized boolean flushdone(int destrank)
    {
	// 送信要求の完了をテスト
	this.isCompleteSend();
	
	// try to send 
	return(this.tryToIssueSendObjs(destrank));
    }

    /**
     * 統計情報の取得
     */
    public CommunBufStatistics peekCBStatistics()
    {
	return(this.mCBStat);
    }
}
