/*
 * $Id: PixelPtrDistributedScan.java,v 1.1 1999/08/06 15:07:54 yamauchi Exp $
 *     (M\pi)^2 : PixelPointer 
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *
 *	RayTracing での初期光分散のための pixel pointer
 *	
 * original source screenPointer.h
 *
 *	pixel pointer class 
 *		distributedscan	distributed に PE へ割り当てる
 *	初期レイ発生時に screen 上の pixel を分散型でスキャンする
 *
 * 1999年8月 6日(金)
 */

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

import  jp.ac.tohoku.is.archi.mpi2.util.Assert;

/**
 * distributed pixel by pixel
 *	いくつかおきに pixel を指す
 */
public final class PixelPtrDistributedScan extends PixelPointer {
    private int mBlockSizeX;            // block サイズ
    private int mBlockSizeY;
    private int mTotalPEs;
    private int mPEID;
    private int mCurrentBlock;          // 現在何番目の block を見ているか
    private int mBlockStartX;           // ある block の左上の X 位置
    private int mBlockStartY;           // ある block の左上の Y 位置
    private int mInnerBlockX;           // block 内部の座標
    private int mInnerBlockY;           
    

    /**
     * constructor
     *	@param maxX, maxY             : screen の大きさ
     * 	@param blocksiezX, blocksiezY : 連続して担当する block の大きさ
     * 	@param numberOfPEs            : 総 PE 数 (PE は 1 次元的にとらえる)
     *  @param myPEID                 : 自分の PE の ID (rank)
     */
    public PixelPtrDistributedScan(int maxX, 	   int maxY, 
				   int blocksizeX,  int blocksizeY, 
				   int numberOfPEs, int myPEID)
    {
        // screen の大きさと total PE Num と myRank (PE id から 
        // distributedX,Y と startX,Y を計算する．)
        this.initMax(maxX, maxY, blocksizeX, blocksizeY, numberOfPEs, myPEID);
    }

    /**
     * scan 方式名を返す
     */
    public String getScreenPointerType() 
    {
        return("ScanType : Distributed (distributed first ray generation)");
    }

    /**
     * initialize 
     */
    public void initMax(int maxX, 	 int maxY,
			int blocksizeX,  int blocksizeY, 
			int numberOfPEs, int myPEID)
    {
        Assert.out(maxX > 0, "Illegal maxX PixelPtrScanlineScan.initMax");
        Assert.out(maxY > 0, "Illegal maxY PixelPtrScanlineScan.initMax");
        this.mMaxX = maxX;
        this.mMaxY = maxY; 

        Assert.out((0 < blocksizeX) && (blocksizeX < maxX),
		   "Illegal blocksizeX PixelPtrScanlineScan.initMax");
        Assert.out((0 < blocksizeY) && (blocksizeY < maxY),
		   "Illegal blocksizeY PixelPtrScanlineScan.initMax");
        this.mBlockSizeX = blocksizeX;
        this.mBlockSizeY = blocksizeY;

        Assert.out(numberOfPEs > 0, 
		   "Illegal numberofPES PixelPtrScanlineScan.initMax");
        Assert.out((0 <= myPEID) && (myPEID < numberOfPEs),
		   "Illegal myPEID PixelPtrScanlineScan.initMax");
        this.mTotalPEs = numberOfPEs;
        this.mPEID     = myPEID;

        this.mCurrentBlock = 0;

	this.mBlockStartX = this.getBlockStartX(this.mCurrentBlock);
	this.mBlockStartY = this.getBlockStartY(this.mCurrentBlock);

        this.mInnerBlockX = 0;
        this.mInnerBlockY = 0; 
    }


    /*
     * block_num 番目の block は画面がずっと横長として 0 からいくつめ
     * かを返す
     * 	@param block_num : 何番目の Block か
     */
    private int getDistFromZero(int block_num)
    {
        int macrowidth = block_num * (this.mBlockSizeX * this.mTotalPEs);

        // PE の rank による次の block の先頭までの幅
        int biasForThisPE = (this.mBlockSizeX * this.mPEID);
        
        // 画面がずっと横長として 0 からの位置
        int distFromZero = macrowidth + biasForThisPE;

	return(distFromZero);
    }

    /**
     * X 
     *	block size X * totalPEs = 全 PE が一つづつ block を担当して
     * 	カバーできる幅
     * 画面の X で mod をとればこの PE の block のはじまり
     */
    private int getBlockStartX(int block_num)
    {
        int startBlockX = this.getDistFromZero(block_num) % this.mMaxX;
	return(startBlockX);
    }

    /**
     * Y
     * 	何回折り返したか * Y 
     */
    public int getBlockStartY(int block_num)
    {
        int startBlockY = 
	    ((int)Math.floor(((double)(this.getDistFromZero(block_num))
			      / (double)this.mMaxX))) * this.mBlockSizeY;
	return(startBlockY);
    }

    /**
     * 次の screen 位置を取り出す
     *------------------------------
     * 以下のコードを 1 ステップづつ実行する
     * 
     * curblock = 0;
     * getBlockStart(curblock);
     * while(this.mIsAllOver == NO)
     * {
     *   for(int innerBlockY = 0; innerBlockY < blockSizeY; innerBlockY++)
     *   {
     *      for(int innerBlockX = 0; innerBlockX < blockSizeX; innerBlockX++)
     *      {
     *          this.mX = blockStartX + innerBlockX;
     *          this.mY = blockStartY + innerBlockY;
     *          if(this.mY > this.mMaxY)
     *          {
     *              this.mIsAllOver = YES;
     *          }
     *      }
     *   }
     *   curblock++;
     *   getBlockStart(curblock);
     * }
     */
    public boolean next()
    {
        // inner block loop
        this.mInnerBlockX++;
        if(this.mInnerBlockX >= this.mBlockSizeX)
        {
            this.mInnerBlockX = 0;         
            this.mInnerBlockY++;
            if(this.mInnerBlockY >= this.mBlockSizeY)
            {
                this.mInnerBlockY = 0;
                this.mCurrentBlock++;
                this.mBlockStartX = this.getBlockStartX(this.mCurrentBlock);
		this.mBlockStartY = this.getBlockStartY(this.mCurrentBlock);
            }
        }

        // set next position
        this.mX = this.mBlockStartX + this.mInnerBlockX;
        this.mY = this.mBlockStartY + this.mInnerBlockY;     
        if(this.mY >= this.mMaxY)         
        {
            return(false); // これ以上の ray の生成はできない。
        }
        return(true);
    }

    /**
     * test
     */
    public static void main(String [] args)
    {
	int screensizeX = 32;
	int screensizeY = 16;
	int blocksizeX  = 8;
	int blocksizeY  = 2;	
	int numberofPES = 6;	
	int myPEID      = 0;	

	PixelPtrDistributedScan ppDist = 
	    new PixelPtrDistributedScan(screensizeX, screensizeY, 
					blocksizeX,  blocksizeY,
					numberofPES, myPEID);
	System.out.println("Test : " + ppDist.getScreenPointerType());
	while(ppDist.next())
	{
	    System.out.println(ppDist.getPixelPosX() + ", " +
			       ppDist.getPixelPosY());
	}
    }
}
