/*
 * $Id: RayTracer.java,v 1.11.4.2 2000/06/19 17:46:20 yamauchi Exp $
 *     (M\pi)^2 : Ray-Tracer
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source 
 *
 *	レイトレーシング法の実装
 * 
 * 1999年8月27日(金)
 */

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

import	jp.ac.tohoku.is.archi.mpi2.common.*;
import	jp.ac.tohoku.is.archi.mpi2.dda.*;
import	jp.ac.tohoku.is.archi.mpi2.net.*;
import	jp.ac.tohoku.is.archi.mpi2.obj.*;
import	jp.ac.tohoku.is.archi.mpi2.host.*;
import	jp.ac.tohoku.is.archi.mpi2.sysparam.*;
import	jp.ac.tohoku.is.archi.mpi2.util.*;

import  jp.ac.tohoku.is.archi.mpi2.jdk12src.Map;
import  jp.ac.tohoku.is.archi.mpi2.jdk12src.Iterator;
import  jp.ac.tohoku.is.archi.mpi2.jdk12src.HashMap;
import  jp.ac.tohoku.is.archi.mpi2.jdk12src.Vector;

/**
 * sub functions for ray-tracing stage
 *
 * ray-tracing の処理 複数本の ray を処理する
 *    交差判定、次空間計算等を行い、送信する ray を NetworkInterface に送る
 */
public final class RayTracer 
implements Mpi2Enum, Mpi2Constants, HostConstants, Config
{
    // PE への参照
    private PE mPE;

    // レイトレーシングステージの終了判定用
    private boolean mIsRayTracingStageOver;

    // 現在 Ray の生成は停止中か
    private boolean mIsStopGenNewRay;

    // hostへのrayの発生停止要求を提出しているか
    private boolean mSendingRayStopComToHost;
    
    // ray generator for ray-tracing
    private RayTrFRGUnit mRayTrFRGUnit;

    // reference of member of PEs
    private HashMap 		mRaySpaceMapRef;
    private SearchNextSpcDDA 	mRayDDAUnitRef;    
    private NetIntUnit		mRayNIUnitRef;
    
    public RayTracer(PE pe)
    {
	this.mPE 			= pe;
	this.mIsRayTracingStageOver 	= false;
	this.mIsStopGenNewRay		= false;
	this.mSendingRayStopComToHost	= false;
	this.mRayTrFRGUnit		= new RayTrFRGUnit();
	this.mRaySpaceMapRef		= this.mPE.peekSpaceMap();
     	this.mRayDDAUnitRef		= this.mPE.peekDDAUnit();
	this.mRayNIUnitRef		= this.mPE.peekNIUnit();
    }
    
    /**
     * ray tracing のための初期化
     */
    public void initForRayTracing()
    {
	/*
	 * 初期状態では ray のキューには何も入っていてはいけないので調べる
	 * (たとえば，radiosity の計算時の残りなどはあってはいけない．)
	 */
	Assert.out(this.mPE.peekRayQueue().isEmpty(), 
		   "Error!: there must be no ray : initForRayTracing()");
	
	if(this.mPE.peekSysInfo().isRayTracingCalc() == false)
	{
	    /*--
	     *-- ray-tracing の計算をしないという場合の処理は正しくはど
	     *-- うすれば良いのか決めていない．ここでは適当なパラメータ
	     *-- を再設定しているが，不正な状態にならないようにするとい
	     *-- う位の意味である．ここを通る場合は単に exit しても良い
	     *-- かもしれない．YAMAUCHI */
	    if(Mpi2Option.getInstance().getVerboseMode())
	    {
		String s = Mpi2Option.toHead(VERBOSEHEAD, 
					     this.mPE.getMyRank()) + 
		    "ray-tracing calculation is off. skip raytracing " +
		    "stage. no image is generated.";
		System.out.println(s);
	    }

	    // 既に終了したとして return
	    this.mIsRayTracingStageOver = true;	
	    return;
	}
    
	// ray tracing ステージの計算を行う
	this.mIsRayTracingStageOver = false;

	// initialize Network Interface unit
	this.mPE.initNetIntRayBuff();
    
	// initialize First Ray Generation Unit
	this.mRayTrFRGUnit.init(this.mPE.peekSysInfo().
				getFirstRaysGenerationMethod(),
				this.mPE.peekViewData(), 
				this.mPE.peekSpaceParam(), 
				this.mPE.peekID(),
				this.mPE.peekSysInfo().getTotalPENum(),
				this.mPE.getMyRank());

	if(this.mRayTrFRGUnit.canGenerateNextRay() == true)
	{
	    /*
	     * 初期レイのセット: 視点が定義空間内の場合などは放射しな
	     * いことが明らかな場合があり、その場合にはレイの作成はで
	     * きない．
	     */
	    this.shotSomeFRGRays();
	}
    }

    /**
     *   ray の処理 複数本のレイを処理する
     */
    public void processingRayTraces()
    {
	/*
	 * この関数が呼ばれる時には以下の条件を満たしている
	 *     ・キューに一本はレイが存在している
	 */
	Assert.out(!(this.mPE.peekRayQueue().isEmpty()), 
		   "Error!: there must be rays. processingRayTraces");
    
	/*
	 * 以下の状況の時にレイを一本取り出してその処理を行なう。
	 * 以下の条件はこの関数にエントリした時には満たされているので 
	 * do{}while;型のループとなる。 
	 * 	   ・レイがキューに存在する
	 * 	   ・レイを処理した数が getProcRayTrAtOnce 以下
	 */
	int i = 0; // レイを処理した数
	do {
	    Assert.out(this.mPE.peekRayQueue().length() > 0, 
		       "Error!: no ray : processingRayTraces");
	    Ray ray = (Ray)(this.mPE.peekRayQueue().deq());
	    this.processingOneRayTrace(ray);  
	    i++;
	}while((!(this.mPE.peekRayQueue().isEmpty())) && 
	       (i < this.mPE.peekSysInfo().getProcRayTrAtOnce()));
	// Statistics.addRayTraceProcsAtOnce(i); FIXME この統計はとるか?
    }


    /**
     * RP Block の処理を行う。輝度計算の処理が必要なら行い、結果を 
     * Host へ送信する。必要ならば他の PE への送信も行う．これは 
     * processingOneRadiosity とかなり共通部分があるが Hit 時の ray の
     * 処理が異る   
     */
    public void processingOneRayTrace(Ray ray)
    {
	/*
	 * 初期光分散の場合には ray の存在する空間を担当していない PE 
	 * が DDA  の計算を行う可能性がある．しかし．これは FRG で ray 
	 * を生成した直後にその PE で DDA の計算を行って，物体の存在す
	 * る空間に ray を送信することで回避すべき．したがって，この 
	 * ray のいる空間はこの PE の担当空間でなくてはならない  
	 */
	Assert.out(this.mPE.isDealSpace(ray.peekSpcPos()) == true, 
		   "Error!: ray is in invalid subspace. " + 
		   "processingOneRayTrace");
	
	// 交差判定
	ObjectPrimitive hitobj = this.calcInterSectionWithRayAndObj(ray);
    
	// 輝度計算、二次レイ発生、次空間探索、送信
	if(hitobj != null)
	{
	    // 物体に ray が交差した
	    final Material mat = 
		(this.mPE.peekMatArray())[hitobj.getMaterialID()];

	    // 物体の輝度を計算し、ray にセット
	    CalcRayTrIC.calcNsetIntensityRayTr(ray, hitobj, 
					       this.mPE.peekSysInfo().
					       getShadingScheme());

	    /*
	     * 更に反射屈折を行うか : 現在は ray は IC.SRG の流れになっ
	     * ている．
	     */
	    if(CalcRayTrSRG.
	       newSecondaryRayTracingRays(ray, hitobj, this.mPE.peekViewData(),
					  this.mPE) == false)
	    {
		/*
		 * 反射も屈折の ray も[どちらも]生成されなかった。これ
		 * まで計算した輝度を Frame Buffer へ送信する。片方で
		 * も生成された場合にはこれまでの輝度はどちらかの ray 
		 * に託されている。それが最終的に消える時に Frame
		 * Buffer へ送信する。 
		 */
		this.mPE.insertIntensityIntoSendBuffer(ray);
		// DELEMETE System.out.println("DEBUG insertintnsity");
	    }
	    // delete ray;		// deleteRay! for C++
	    // ray = null;
	    // 交差したので次空間へは行かない(2次レイが後を受け継ぐ)  
	}else
	{
	    // 次空間探索 : 空間を外れれば ray 消滅
	    this.calcRayTrNextSpace(ray);
	}	
    }

    /**
     * この PE の担当する部分空間でのレイと物体の交差判定
     */
    private ObjectPrimitive calcInterSectionWithRayAndObj(Ray ray)
    {
	Assert.out(this.mPE.isDealSpace(ray.peekSpcPos()), 
		   "Error!: this ray is in invalid subspace " +
		   "calcInterSectionWithRayAndObj"); 

	/*
	 * 物体との交差判定 
	 */
	Vector objArray;
	final int objNumInThisSpc;
	
	/* DELETEME
	for(Iterator i = this.mRaySpaceMapRef.values().iterator(); 
	    i.hasNext(); ) 
	{
	    Vector objary = (Vector)(i.next());
	    System.out.println("DEBUG : values is " + objary);
	}
	
	for(Iterator i = this.mRaySpaceMapRef.keySet().iterator(); 
	    i.hasNext(); ) 
	{
	    IntVec3 pos = (IntVec3)(i.next());
	    System.out.println("DEBUG : key is " + pos);
	}
	*/

	if(this.mRaySpaceMapRef.containsKey(ray.peekSpcPos()) == true)
	{
	    objArray = (Vector)(this.mRaySpaceMapRef.get(ray.peekSpcPos()));
	    objNumInThisSpc = objArray.size();
	}else
	{
	    // この空間には物体が存在しない．
	    objArray = null;
	    objNumInThisSpc = 0;
	}

	double distanceBetweenRayNObject = Double.MAX_VALUE;

	Vector3 hitPoint	= new Vector3();	// 交差点
	ObjectPrimitive hitobj 	= null; 		// 交差物体
	for(int i = 0; i < objNumInThisSpc; i++)
	{
	    ObjectPrimitive obj = (ObjectPrimitive)objArray.get(i);
	    if(obj != ray.peekRadObjRef()) //rayの発生した面とは交差判定しない
	    {
		double tmpDist = obj.getDistance(ray, hitPoint);
		Assert.out(tmpDist >= 0.0, "Error!: distance must be >= 0");

		/* DELETEME 
		if(tmpDist < Double.MAX_VALUE)
		{
		    System.out.println("DEBUG: hit");
		}
		*/

		// 球のような物体の場合にはこの部分空間に存在しない部分で 
		// hit する可能性がある．その場合を除く 
		switch(obj.who())
		{
		case ObjectPrimitive.OBJ_TRIANGLE:	
		    break;
		case ObjectPrimitive.OBJ_SPHERE:
		    if(this.mPE.checkHitPosIsInThisSpcPos(hitPoint, 
							  ray.peekSpcPos())
		       == false)
		    {
			// この空間ではヒットしていない
			tmpDist = Double.MAX_VALUE;
		    }
		    break;
		case ObjectPrimitive.OBJ_NULL:		
		default:
		    throw new RuntimeException("Internal Error! : this obj " + 
					       "cannot intersect calc" + obj);
		}

		if(tmpDist < distanceBetweenRayNObject)
		{
		    // tmpDist の方が近いので交差したobjを更新し、交点
		    // をセットする  
		    distanceBetweenRayNObject = tmpDist;
		    hitobj = obj;
		    ray.setHitPos(new Vector3(hitPoint));
		}
	    }
	}
	/*
	 * Volume との相互作用 
	 * NIN
	 */
	/*
	SLList < VolumeClass * > *pVolList = 
	    this.mVolSet.getList(ray.getSpcPos());
	if(!(pVolList.empty()))
	{
	    // volume が存在した場合には相互作用を計算する
	    processingOneRayTraceWithVolume(ray, pVolList, 
					    distanceBetweenRayNObject);
	}
	*/
	// もしかしたら volume の作用でここを null にすべき場合があるかも
	return(hitobj);
    }

    /**
     * DDA 計算．ray が定義空間を越えた場合には ray 消滅
     */
    private void calcRayTrNextSpace(Ray ray)
    {
	// 交差する物体は存在しない 次空間探索 (PEGetNExtSpace.cc)
	boolean isSendRay = this.mPE.moveRayToNextSubspace(ray);
 
	if(isSendRay == true)	// ray 送信
	{
	    this.mPE.setRayToNI(ray); // NIのバッファへためこむ  送信
	}else
	{
	    // 輝度をフレームバッファシステムへ送信
	    this.mPE.insertIntensityIntoSendBuffer(ray);	    
	    // delete ray;	// deleteRay! for C++
	    // ray = null;
 	}
    }

    /**
     * レイを一定数発生する for ray-tracing
     */
    private void shotSomeFRGRays()
    {
	// 呼び出したということは最小限 1 本は発生可能であるはず
	Assert.out(this.mRayTrFRGUnit.canGenerateNextRay() == true, 
		   "Error!: can not generate any rays. shotSomeFRGRays");

	int maxGenRaysAtOnce = 
	    this.mPE.peekSysInfo().getProcRayTrAtOnce();
	//  * GEN_FRAYS_FACTOR_BY_COMMUN; ???

	int i;
	for(i = 0; i < maxGenRaysAtOnce; i++)
	{
	    // 一本 ray を発生させる
	    Ray ray = this.mRayTrFRGUnit.newNextRay();

	    /*
	     * 複数の PE で共同して ray を発生させる場合には ray を発
	     * 生させてそれが担当空間と交差するかを試験しなくてはいけ
	     * ないので、たとえスクリーン上であっても実際にこの PE が 
	     * ray を発生させられない場合がある。そのような場合には 
	     * null が帰ってくる。 
	     */
	    if(ray != null)	
	    {
		if(this.mPE.isDealSpace(ray.peekSpcPos()) == true)
		{
		    // ray 発生時にこの PE の担当空間にいる
		    // ここで計算する
		    this.mPE.setRayToThisPE(ray);
		}else 
		{
		    // ray 発生時にこの PE の担当空間にいない
		    if(this.mPE.isDealSpace(ray.peekSpcPos()) == true)
		    {
			/* 
			 * 全体のマップを見て，オブジェクトがこの ray 
			 * の現在存在する空間に存在する場合にはその担
			 * 当 PE へ送信する : NI キューへ 
			 * (Multistage Network なので直接送信可能)
			 */
			this.mPE.setRayToNI(ray); 
		    }
		    else
		    {
			/*
			 * 全体のオブジェクト存在マップを見て，オブジェ 
			 * クトがこの ray の現在存在する空間には存在し
			 * ない場合には，次空間探索を行って物体の存在
			 * する空間へと移動する．
			 */
			this.calcRayTrNextSpace(ray);
		    }
		}
	    }else
	    {
		// ray は発生しなかった．することはない．
	    }
	    if(this.mRayTrFRGUnit.canGenerateNextRay() == false)
	    {
		// ray の発生終了
		if(Mpi2Option.getInstance().getVerboseMode())
		{
		    String s = Mpi2Option.toHead(VERBOSEHEAD, 
						 this.mPE.peekID())
			+ "generate rays( " +
			this.mRayTrFRGUnit.getTotalGenRays() + 
			" ) done.";
		    System.out.println(s);
		}
		break;
	    }
	}
	// System.out.println("DEBUG : shot some rays " + i);
    }

    /*------------------------------------------------------------
     * ray tracing
     *-----------------------------------------------------------*/
    public void doRayTracing()
    {
	if(Mpi2Option.getInstance().getVerboseMode())
	{
	    System.out.println(Mpi2Option.toHead(VERBOSEHEAD,	    
						 this.mPE.getMyRank())
			       + "PE in do raytracing OK");
	}

	ProfileMon pfm = ProfileMon.getInstance();
	pfm.startStage(ProfileMon.ProfSt_raytracing);

	// ray-tracing のための初期化
	this.initForRayTracing();
	if(Mpi2Option.getInstance().getVerboseMode())
	{
	    System.out.println(Mpi2Option.toHead(VERBOSEHEAD,
						 this.mPE.getMyRank())
			       + "PE in inited OK");
	}

	// init status 
	this.mPE.initBusyIdleStat();

	// host からの ray の発生停止要求はない
	this.mIsStopGenNewRay = false; 

	// host への ray の発生停止要求未提出
	this.mSendingRayStopComToHost = false; 
	
	while(this.isRayTracingStageOver() == false)
	{
	    if(Use_Profile_Monitor)
	    {
		/* PM_Array
		   static int beforerays = -PM_RAY_CHANGE_TICK;
		   int pm_rnum = this.mPE.peekRayQueue().length();
		   if(abs(pm_rnum - beforerays) > (PM_RAY_CHANGE_TICK / 2))
		   {
		   pm_raynum(pm_rnum);
		   beforerays = pm_rnum;
		   }
		   */
	    }

	    // まずは他から受信する
	    this.mPE.recvRays(); 

	    /*
	     * 次に他へ送信する
	     * 今回は ray が enqueue された時点で送信すべきならば送信
	     * するため，ここでの send はない this.sendRays(); 
	     */
	    // this.mNIUnit.flushIntensitySendBuf();// 輝度をホストへ送信する
	
	    // processing
	    if(this.mPE.peekRayQueue().hasElements())
	    {
		/*
		 * 受信レイなし && 処理するべき自己 ray がある
		 * (RP Block) ray を処理する。輝度計算、送信含む。
		 */
		Assert.out(this.mPE.isIdle() == false, 
			   "Error!: processing rayQueue().hasElements()");
		pfm.occurEv(ProfileMon.ProfEv_ProcRayRays);

		this.processingRayTraces(); 

		// ray の量によって ray の発生制限を行う
		this.mPE.globalLoadBalance(); 
	    }
	    else if(this.mRayTrFRGUnit.canGenerateNextRay() == true)
	    {
		// キューにレイなし、未発射レイあり
		this.mPE.toBusy();

		// レイを発生する前に host から ray の発生の停止要求がある
		// かを調べる 
		this.getAndPerformHostCommand();
		if(this.mIsStopGenNewRay == false) // host から停止要請はない
		{
		    pfm.occurEv(ProfileMon.ProfEv_genRays); 
		    this.shotSomeFRGRays();
		}
	    }
	    else if(this.mRayNIUnitRef.isSendRayBufAllEmpty() == false)
	    {
		// 受信レイなし && buff にレイが残っている
		Assert.out(this.mPE.isIdle() == false,
			   "Error!: here must be busy. RayTracer:481");

		// 送信可能な場合には送信発行を試みる
		this.mRayNIUnitRef.flushSendRaysBuffer();
		pfm.occurEv(ProfileMon.ProfEv_SendingWait);
		// System.out.println("DEBUG sending only");
	    }else if((this.mRayNIUnitRef.isNoSendRay() == true)&&//送信中でない
		     (this.mRayNIUnitRef.isNoRecvRay() == true)) //受信中でない
	    {
		// 輝度と PEStat の flush
		this.mRayNIUnitRef.flushIntensitySendBuf(); // 輝度 の flush
		this.mRayNIUnitRef.flushPEStatSendBuff();   // PEStat の flush
		
		if((this.mRayNIUnitRef.isSendingIntensity() == false) && 
		   (this.mRayNIUnitRef.isSendingPEStat()    == false))
		{
		    // 受信レイなし && 処理するべき自己 ray 無し && 送信終了
		    // && 受信終了 (ray && 輝度 && PEStat)
		    // == 全ての仕事が終了
		    if(this.mPE.isIdle() == false) // busy . idle
		    {
			this.mPE.toIdle();		
			// System.out.println("DEBUG toIdle");
			pfm.occurEv(ProfileMon.ProfEv_Idle);		    
		    }else
		    {
			/* 
			 * 全て処理なく idle のままであった
			 * もし終了のコマンドを受けたら 
			 * endOfRayTracing を true に 
			 * 変更させる 
			 */
			this.getAndPerformHostCommand(); 
			if(this.isRayTracingStageOver() == true)
			{
			    // 終了コマンドを受けた場合にはキューは空
			    Assert.out(this.mPE.peekRayQueue().isEmpty(),
				       "Error!: Host say all over, then "
				       + "queue must be empty.");
			}
		    }
		}
		/*
		System.out.println("DEBUG nosend, norecv ray, intensity " +
				   this.mRayNIUnitRef.isSendingIntensity() +
				   ", PEStat " + 
				   this.mRayNIUnitRef.isSendingPEStat());
		System.out.println("DEBUG nosend");
		DELETEME
		*/
		Mpi2System.pollingSleep();
	    }else
	    {
		this.mPE.toBusy();	    
	    }
	}
	/*
	  #ifdef FOR_STANDALONE
	  this.toIdle();
	  #endif
	  */
	this.mRayNIUnitRef.flushIntensitySendBuf();  // 輝度をホストへ送信する
	if(Mpi2Option.getInstance().getVerboseMode())
	{
	    String s = Mpi2Option.toHead(VERBOSEHEAD, this.mPE.getMyRank()) + 
		"out of PE loop at RayTracer.doRayTracing.";
	    System.out.println(s);
	}
	pfm.endStage(ProfileMon.ProfSt_raytracing);
    }
    
    /**
     * 新たなレイの発生は可能か
     */
    private boolean isStopGenNewRay()
    {
	return(this.mIsStopGenNewRay);
    }

    /**
     * RayTracing stage は終了か 
     */
    private boolean isRayTracingStageOver()
    {
	return(this.mIsRayTracingStageOver);
    }

    /**
     * Host からのコマンドを受信し解釈する．
     */
    private void getAndPerformHostCommand()
    {
	if(this.mPE.probeHostCommand() == false)
	{
	    return;		// コマンドの受信はなかった
	}
	
	// Host のコマンドを受けた
	Assert.out(this.mPE.peekHostCommRecvQueue().length() > 0, 
		   "Error!: queue len must be > 0 " + 
		   "RayTracer.getAndPerformHostCommand");
	HostCommand hcomm = 
	    (HostCommand)this.mPE.peekHostCommRecvQueue().deq();

	switch(hcomm.getCommand())
	{
	case End_Raytrace: 	
	    // raytracing stage 終了
	    this.mIsRayTracingStageOver = true;
	    break;

	case Suspend_GenNewRays: 
	    // 大域負荷分散: stop generate new rays
	    this.mIsStopGenNewRay = true;
	    break;
	    
	case Resume_GenNewRays:
	    // 大域負荷分散: resume generate new rays
	    this.mIsStopGenNewRay = false;
	    break;
	    
	case Send_Patch_Descriptions:
	    // ホストからのパッチ情報送信要求
	    {
		// 計算の途中でパッチ情報を host に送信する
		// this.sendOutRadioResultToHost();
		Mpi2Option.getInstance().
		    verboseOut("NIN sendOutRadioResultToHost at " + 
			       "RayTracer.getAndPerformHostCommand");
	    }
	    break;

	    // 以下は ray tracing ステージでは送られてこないはず
	case End_Radiosity: 		// radiosity stage 終了
	case End_PrepareShading: 	// prepare shading stage 終了
	case Issue_BarrierSync: 	// synchronization
	default:
	    String s = "Internal Error! : PE. r " + this.mPE.getMyRank() + 
		"PE.getAndPerformHostCommand received illegal host command";
	    throw new RuntimeException(s);
	}
	    
	if(Mpi2Option.getInstance().getDebugMode())
	{
	    System.out.println(Mpi2Option.toHead(DEBUGHEAD, 
						 this.mPE.getMyRank()) 
			       + "get " + hcomm);
	}
    }
}
