/**
 * Copyright (C) 2006-2007  NTT DATA CORPORATION
 * 
 * Version: 1.0.0 2007/04/01
 *  
 */
package net.cellcomputing.himawari.display;

import static net.cellcomputing.himawari.display.GrobalDDManager.ConstructFloatsParameter;
import static net.cellcomputing.himawari.display.GrobalDDManager.ConstructIntsParameter;
import static net.cellcomputing.himawari.display.GrobalDDManager.ConstructStringsParameter;
import static net.cellcomputing.himawari.library.EqEnvVars.EnvVars_Last;
import static net.cellcomputing.himawari.library.EqVariableClass.class_uniform;
import static net.cellcomputing.himawari.library.EqVariableType.type_color;
import static net.cellcomputing.himawari.library.EqVariableType.type_float;
import static net.cellcomputing.himawari.library.EqVariableType.type_integer;
import static net.cellcomputing.himawari.library.EqVariableType.type_normal;
import static net.cellcomputing.himawari.library.EqVariableType.type_point;
import static net.cellcomputing.himawari.library.EqVariableType.type_string;
import static net.cellcomputing.himawari.library.EqVariableType.type_vector;
import static net.cellcomputing.himawari.library.RiGlobal.QGetRenderContext;
import static net.cellcomputing.himawari.shaderexecenv.GlobalShaderExecEnv.gVariableTokens;

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map.Entry;

import javax.imageio.ImageIO;

import net.cellcomputing.himawari.accessory.STLVector;
import net.cellcomputing.himawari.accessory.primitive.p_String;
import net.cellcomputing.himawari.accessory.primitive.p_float;
import net.cellcomputing.himawari.accessory.primitive.p_int;
import net.cellcomputing.himawari.exception.DisplayNotFoundException;
import net.cellcomputing.himawari.exception.XqException;
import net.cellcomputing.himawari.library.IqBucket;
import net.cellcomputing.himawari.library.IqDDManager;
import net.cellcomputing.himawari.library.SqParameterDeclaration;
import net.cellcomputing.himawari.library.UserParameter;
import net.cellcomputing.himawari.util.HimawariLogger;

/**
 * @author NTT DATA Corporation
 * Displayに関する命令を管理するクラス。
 * DislayRequestとImageDataを管理する。
 * DisplayRequestはRI命令にある情報を管理し、
 * ImageDataはImageの描画を行うものである。
 * 
 * 通常は
 * 1.AddDisplayメソッドを用いてRequestを登録する
 * 2.openDisplaysメソッドで準備を行う
 * 3.displayBucketメソッドでCqBucket単位のレンダリングを行う
 * 4.closeDisplaysメソッドで、ファイルへの書き出しなどの終了処理を行う。
 * という流れで用いられる。
 */
public strictfp class DisplayManager implements IqDDManager {
	
	/**
	 * TIFF形式を表す文字定数
	 */
	private static final String DISPLAY_TIFF = "tiff";

	/**
	 * PNG形式を表す文字定数
	 */
	private static final String DISPLAY_PNG = "png";

	/**
	 * BMP形式を表す文字定数
	 */
	private static final String DISPLAY_BMP = "bmp";

	/**
	 * JPEG形式を表す文字定数
	 */
	private static final String DISPLAY_JPG = "jpg";

	/**
	 * JPEG2000形式を表す文字定数
	 */
	private static final String DISPLAY_JPEG2000 = "jpeg2000";

	/**
	 * RGBA形式を表す文字定数
	 */
	private static final String DISPLAY_MODE_RGBA = "rgba";

	/**
	 *  RGB形式を表す文字定数
	 */
	private static final String DISPLAY_MODE_RGB = "rgb";

	/**
	 * Z形式を表す文字定数
	 */
	private static final String DISPLAY_MODE_Z = "z";
	
	/**
	 * HDR形式を表す文字定数
	 */
	private static final String DISPLAY_HDR = "hdr";
	
	
	/**
	 * File形式を表す文字定数
	 */
	private static final String DISPLAY_FILE = "file";
	/**
	 * Framebuffer形式を表す文字定数(Windowへの表示)
	 */
	private static final String DISPLAY_FRAMEBUFFER = "framebuffer";
	
	/**
	 * ZFrameBuffer形式を表す文字定数(Windowへの表示)
	 */
	private static final String DISPLAY_ZFRAMEBUFFER = "zframebuffer";
	/**
	 * ZFile形式を表す文字定数
	 */
	private static final String DISPLAY_ZFILE = "zfile";

	/**
	 * ZFile形式を表す文字定数
	 */
	private static final String DISPLAY_SHADOW_FILE = "shadow";

	private STLVector<DisplayRequest> m_displayRequests = new STLVector<DisplayRequest>(DisplayRequest.class); 
	private STLVector<ImageData> m_ImageData = new STLVector<ImageData>(ImageData.class);
	private LinkedList<String> m_displayList = new LinkedList<String>();
	
	//--CqDDManaerに定義されていた定数
	private static int rgb	= DISPLAY_MODE_RGB.hashCode();
	private static int rgba	= DISPLAY_MODE_RGBA.hashCode();
	private static int Ci		= "Cs".hashCode();
	private static int Oi		= "Os".hashCode();
	
	
	
	/**
	 * JVMが利用可能な画像形式を探索し、
	 * 利用できる画像形式をHashMapに登録する.
	 */
	public int Initialise() {
		String[] writerNames = ImageIO.getWriterFormatNames();

		for (int i = 0 ; i < writerNames.length ; i++) {
			m_displayList.add(writerNames[i]);
		}
		//特殊なディスプレイ形式をリストに追加する
		addExtractDisplay();
		
		return 0;
	}

	/**
	 * JVMでは認識されない特殊なディスプレイタイプを
	 * HashMapに登録する
	 */
	private void addExtractDisplay() {
		m_displayList.add(DISPLAY_FRAMEBUFFER);	//(表示用)
		m_displayList.add(DISPLAY_FILE);		//(デフォルト指定用)
		m_displayList.add(DISPLAY_HDR);			//(HDR形式)
		m_displayList.add(DISPLAY_ZFILE);		//(zFile形式)
		m_displayList.add(DISPLAY_SHADOW_FILE);		//(shadow形式)
		m_displayList.add(DISPLAY_ZFRAMEBUFFER);		//(shadow形式)
	}
	
	/**
	 * 何も行わない。
	 */
	public int Shutdown() {
		return 0;
	}
	
	/**
	 * DisplayのRequest情報を受け取り、
	 * modeに適合するImageData及び、typeに適合するRequestを作成し
	 * Vectorに登録をする
	 * 
	 * ディスプレイ形式あって、カラーモードが未対応の場合、
	 * 特になにも行わない
	 * @param name ディスプレイの名前
	 * @param type ディスプレイの形式
	 * @param mode ディスプレイのカラーモード
	 * @param modeID ディスプレイのカラーモードのハッシュ
	 * @param dataOffset
	 * @param dataSize
	 * @param mapArguments
	 * 
	 * @throws DisplayNotFoundException typeに対応するディスプレイ形式がない場合にthrowされる
	 * 
	 * @return 0
	 */
	public int AddDisplay(String name, String type, String mode, int modeID,
			int dataOffset, int dataSize, HashMap<String, Object> mapOfArguments) throws DisplayNotFoundException{
		ImageData imageData = null;
		DisplayRequest request = null;
		
		//利用可能な形式でない場合、例外をthrowする
		if(!m_displayList.contains(type)){
			throw new DisplayNotFoundException(type);
		}
		
		
		//--- AOV 2006.11.10 isawa for AOV ---------------

		//出力データのタイプを取得
		int outType = QGetRenderContext().OutputDataType(mode);

		//エントリされていたら（＝タイプが取得できたら）
		if( outType > 0 ) {
			//タイプにあわせたImageDataを作成
			switch( outType )
			{
//			case type_hpoint:
////				componentNames = "xyz";
////				break;
//
//				//shadowと同じにするパターン
//				type = DISPLAY_SHADOW_FILE; 
//				mode = DISPLAY_MODE_Z;
//				break;
				
			case type_float:
			case type_integer:
				//-- AOV float integer追加 2007/05/16 watanabei
				type = DISPLAY_HDR;
//				mode = DISPLAY_AOV_SINGLE;
				break;
				//--ここまで
			case type_vector:
			case type_point:
			case type_normal:
			case type_color:
//				componentNames = "rgb";
//				break;
				
				//HDRと同じにするパターン
				type = DISPLAY_HDR;
//				mode = DISPLAY_AOV_TRIPLE;
				break;
			}
		}
		
		//------------------------------------------------
		

		//--ImageData及びRequestを作成する
		//  Create用のFactoryを作成しても良いと思う
		//  fileが指定された場合、TIFFが利用できればtiff形式で、
		//  利用できない場合は〜形式で保存する
		
		if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_FRAMEBUFFER)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new FrameBufferRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_FRAMEBUFFER)){
			imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			request = new FrameBufferRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_TIFF)){
			imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			request = new TiffRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_TIFF)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new TiffRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_PNG)){
			imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			request = new PngRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_PNG)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new PngRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_BMP)){
			//RGBAはサポートしないためWARNINGを出力し、RGBモードに変換を行う
			HimawariLogger.getLogger().warning("[" + type + "]  \"" + mode + "\" is not supported. Convert \""+ mode +"\" into \"rgb\".\n");

			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new BMPRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_BMP)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new BMPRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_JPG)){
			//RGBAはサポートしないためWARNINGを出力し、RGBモードに変換を行う
			HimawariLogger.getLogger().warning("[" + type + "]  \"" + mode + "\" is not supported. Convert \""+ mode +"\" into \"rgb\".\n");

			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new JPEGRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_JPG)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new JPEGRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_HDR)){
			//RGBAはサポートしないためWARNINGを出力し、RGBモードに変換を行う
			HimawariLogger.getLogger().warning("[" + type + "]  \"" + mode + "\" is not supported. Convert \""+ mode +"\" into \"rgb\".\n");

			imageData = ImageData.getInstance(HDRImage.class,dataOffset);
			request = new HDRRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_HDR)){
			imageData = ImageData.getInstance(HDRImage.class,dataOffset);
			request = new HDRRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals(DISPLAY_JPEG2000)){
			imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			request = new JPEG2000Request(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGB) && type.equals(DISPLAY_JPEG2000)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			request = new JPEG2000Request(name,type,mode,modeID,dataOffset,dataSize,imageData);

		}else if(type.equals(DISPLAY_FILE) && mode.equals(DISPLAY_MODE_RGBA)){
			imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			
			if(m_displayList.contains(DISPLAY_TIFF))
				request = new  TiffRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
			else
				request = new PngRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
			
		}else if(type.equals(DISPLAY_FILE) && mode.equals(DISPLAY_MODE_RGB)){
			imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			
			if(m_displayList.contains(DISPLAY_TIFF))
				request = new  TiffRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
			else
				request = new PngRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
			
		}else if((type.equals(DISPLAY_SHADOW_FILE) || type.equals(DISPLAY_ZFILE)) && mode.equals(DISPLAY_MODE_Z)){
			//shadowMapを作成する
			imageData = ImageData.getInstance(ZFileImage.class,dataOffset);
			request = new ZFileRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if((type.equals(DISPLAY_ZFRAMEBUFFER) ) && mode.equals(DISPLAY_MODE_Z)){
			imageData = ImageData.getInstance(ZFileImage.class,dataOffset);
			request = new ZFrameBufferRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
			
//		}else if(mode.equals(DISPLAY_AOV_SINGLE) && type.equals(DISPLAY_HDR)){
		}else if((outType == type_float || outType ==type_integer) && type.equals(DISPLAY_HDR)){
			imageData = ImageData.getInstance(AOVSingleImageData.class,dataOffset);
			request = new HDRRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
//		}else if(mode.equals(DISPLAY_AOV_TRIPLE) && type.equals(DISPLAY_HDR)){
		}else if((outType == type_point || outType ==type_vector|| outType ==type_normal || outType ==type_color) && type.equals(DISPLAY_HDR)){
			imageData = ImageData.getInstance(AOVTripleImageData.class,dataOffset);
			request = new HDRRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}
		//Boinc pixieプロジェクトに対応するために追加　岩渕 2007/12/10
		else if(mode.equals(DISPLAY_MODE_RGB) && type.equals("boincbuffer")){
			//imageData = ImageData.getInstance(RGBImageData.class,dataOffset);
			//request = new FrameBufferRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else if(mode.equals(DISPLAY_MODE_RGBA) && type.equals("boincbuffer")){
			//imageData = ImageData.getInstance(RGBAImageData.class,dataOffset);
			//request = new FrameBufferRequest(name,type,mode,modeID,dataOffset,dataSize,imageData);
		}else {
			//エラーメッセージを表示する
			HimawariLogger.getLogger().error("[" + type + "]  \"" + mode + "\" is not supported.\n");
		}
		
		
		if(request != null)
			m_displayRequests.add(request);
		//--ImageDataを作成済みであれば登録を行わない
		if(imageData != null && !m_ImageData.contains(imageData)){
			m_ImageData.add(imageData);
		}
		
		//------- 2006.11.13 isawa add for AOV --------------------------------
		
		//特別な引数の設定
		
		// Create the array of UserParameter structures for all the unrecognised extra parameters,
		// while extracting information for the recognised ones.
		PrepareCustomParameters(mapOfArguments, imageData);
		
		//出力ファイル名の登録
		dispNames.add(name);
		//---------------------------------------------------------------------

		
		return 0;
	}


	/**
	 * 登録されているRequestを全て削除する
	 */
	public int ClearDisplays() {
		m_displayRequests.clear();
		m_ImageData.clear();
		return 0;
	}
	
	/**
	 * 登録されているRequestに対して
	 * openRequest()を呼び出す。
	 * また登録されているImageDataに対して、init()を呼び出す。
	 */
	public int OpenDisplays() {
		for(ImageData i : m_ImageData){
			i.init();
		}
		
		
		for(DisplayRequest i : m_displayRequests){
			i.openRequest();
		}
		return 0;
	}
	
	/**
	 * 登録されているRequest全てに対して、
	 * closeRequestメソッドを呼び出す
	 */
	public int CloseDisplays() {
		for(DisplayRequest i : m_displayRequests){
			i.closeRequest();
		}
		return 0;
	}
	
	/**
	 * 登録されているRequest全てに対して
	 * Displayメソッドを呼び出す
	 * また、登録されているImageData全てに対して
	 * setBucketを呼び出す
	 */
	public int DisplayBucket(IqBucket pBucket) {
		
		for(ImageData i : m_ImageData){
			i.setBucket(pBucket);
		}
		
		for(DisplayRequest i : m_displayRequests){
			i.display();
		}
		return 0;
	}
	
	/**
	 * CqDDManagerのメソッド
	 */
	public boolean fDisplayNeeds(String var) {
		long htoken = var.hashCode();
		
//		// Scan all registered displays to see if any of them need the variable specified.
		for( DisplayRequest i : m_displayRequests )
		{
			boolean usage = ( ( i.m_modeHash == rgba ) || ( i.m_modeHash == rgb ) );
			if ( ( htoken == Ci ) && usage )
				return ( true );
			else if ( ( htoken == Oi ) && usage )
				return ( true );
			else if ( ( i.m_modeHash == htoken ) )
				return ( true );
		}
		return ( true );
	}
	
	/**
	 * CqDDManagerのメソッド
	 */
	public int Uses() {
		int Uses = 0;
		// Scan all registered displays to combine the required variables.
		for( DisplayRequest i : m_displayRequests )
		{
			int ivar;
			for( ivar = 0; ivar < EnvVars_Last; ivar++ )
			{
				if( i.m_modeHash == gVariableTokens[ ivar ] )
					Uses |= 1 << ivar;
			}
		}
		return ( Uses );
	}
	
	
	//------- 2006.08.17 isawa add for BOINC ------------------------------
	/**
	 * 自己参照。
	 * BOINC上の、Himawariキッカーから静的にgetImage()メソッドを呼び出すためのもの。
	 */
	private static DisplayManager me;
	
	/**
	 * コンストラクタ。
	 * 自己参照をセットする。
	 */
	public DisplayManager() {
		me = this;
	}
	
	/**
	 * 現在レンダリング中のBufferedImageを取り出す。
	 * BOINC上のHimawariキッカーが利用する。
	 * 
	 * @return	現在レンダリング中のBufferedImageオブジェクト
	 */
	@SuppressWarnings("unchecked")
	public static BufferedImage getImage() {
		
		BufferedImage image;

		if( me == null ) return null;
		
		for( ImageData id : me.m_ImageData )
		{
			if (id.getImage() instanceof BufferedImage) {
				image = (BufferedImage) id.getImage();
				return image;
			}
			if (id instanceof ZFileImage) {
				ZFileImage zimage = (ZFileImage) id;
				return zimage.getZImage();
			}
		}
		
		return null;
	}
	//---------------------------------------------------------------------
	
	//------- 2006.11.13 isawa add for AOV --------------------------------
	
	/**
	 * ImageDataオブジェクト（画像を生成するオブジェクト）へ特殊な引数を設定する。
	 * 
	 * @param mapOfArguments	引数を格納したハッシュテーブル。
	 * @param req				引数を設定するImageData
	 */
	@SuppressWarnings("unchecked")
	private void PrepareCustomParameters(HashMap<String, Object> mapParams, ImageData req) {

		// Scan the map of extra parameters
//		std::map<std::string, void*>::iterator param;
//	    for ( param = mapParams.begin(); param != mapParams.end(); param++ )
		for( Entry<String, Object> param : mapParams.entrySet() )
	    {
			// First check if it is one of the recognised parameters that the renderer should handle.
			if(param.getKey().equals("quantize"))
			{
				// Extract the quantization information and store it with the display request.
				p_float[] floats = (p_float[])( param.getValue() );
				req.m_QuantizeZeroVal = floats[0].value;
				req.m_QuantizeOneVal = floats[1].value;
				req.m_QuantizeMinVal = floats[2].value;
				req.m_QuantizeMaxVal = floats[3].value;
			}
			else if(param.getKey().equals("dither"))
			{
				// Extract the quantization information and store it with the display request.
				p_float[] floats = (p_float[])( param.getValue() );
				req.m_QuantizeDitherVal = floats[0].value;
			}
			else
			{
				// Otherwise, construct a UserParameter structure and fill in the details.
				SqParameterDeclaration Decl;
				try
				{
					Decl = QGetRenderContext().FindParameterDecl( param.getKey() );
				}
				catch( XqException e )
				{
					HimawariLogger.getLogger().error( e.strReason() + "\n" );
					return;
				}

				// Check the parameter type is uniform, not valid for non-surface requests otherwise.
				if( Decl.m_Class.getValue() != class_uniform )
				{
					assert( false );
					continue;
				}

				UserParameter parameter = new UserParameter();
				parameter.name = null;
				parameter.value = 0;
				parameter.vtype = 0;
				parameter.vcount = 0;
				parameter.nbytes = 0;

				// Store the name
//				char* pname = reinterpret_cast<char*>(malloc(Decl.m_strName.size()+1));
//				strcpy(pname, Decl.m_strName.c_str());
//				parameter.name = pname;
				parameter.name = Decl.m_strName;

				switch ( Decl.m_Type.getValue() )
				{
					case type_string:
					{
						final p_String[] strings = (p_String[])( param.getValue() );
						ConstructStringsParameter(Decl.m_strName, strings, Decl.m_Count, parameter);
					}
					break;

					case type_float:
					{
						final p_float[] floats = (p_float[])( param.getValue() );
						ConstructFloatsParameter(Decl.m_strName, floats, Decl.m_Count, parameter);
					}
					break;

					case type_integer:
					{
						final p_int[] ints = (p_int[])( param.getValue() );
						ConstructIntsParameter(Decl.m_strName, ints, Decl.m_Count, parameter);
					}
					break;
				}
				req.m_customParams.add(parameter);
			}
	    }
	}
	
	
	/**
	 * Displayに指定された出力ファイル名を格納するコンテナ。
	 */
	private ArrayList<String>	dispNames = new ArrayList<String>();
	
	/**
	 * Displayに指定された出力ファイル名配列を取得する。
	 * BOINC上のHimawariキッカーが利用する。
	 * 
	 * @return	Displayに指定された出力ファイル名配列
	 */
	public static ArrayList<String> getDisplayNames() {
		
		if( me == null ) return null;
		
		//ri.picは除外
		me.dispNames.remove("ri.pic");
		
		//ファイル名として変換しておく
		File file;
		for( int i=0; i<me.dispNames.size(); i++ ) {
			file = new File( me.dispNames.get(i) );
			me.dispNames.set( i, file.getName() );
		}
		
		return me.dispNames;
	}
	
	
	//---------------------------------------------------------------------
	
	

	
}
