/*
 *  Copyright 2008 tarotarorg
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.tarotaro.flash.pv3d  {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.display.Graphics;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	import org.papervision3d.core.geom.renderables.Triangle3D;
	import org.papervision3d.core.material.TriangleMaterial;
	import org.papervision3d.core.proto.LightObject3D;
	import org.papervision3d.core.render.command.RenderTriangle;
	import org.papervision3d.core.render.data.RenderSessionData;
	import org.papervision3d.events.FileLoadEvent;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.BitmapColorMaterial;
	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.shaders.PhongShader;
	import org.papervision3d.materials.shaders.ShadedMaterial;

	public class MetasequoiaMaterial extends TriangleMaterial{
////////////////////////////////////////////////////////////////////////////////////////
//定数定義
////////////////////////////////////////////////////////////////////////////////////////
		private static const TEX:String = "tex";
		private static const APLANE:String = "aplane";
		private static const BUMP:String = "bump";

////////////////////////////////////////////////////////////////////////////////////////
//メンバ変数定義
////////////////////////////////////////////////////////////////////////////////////////
		/**
		* 基本となるパス
		*/
		private var _basePath:String;
		/**
		* 模様マップ
		*/
		private var _tex:BitmapData;
		/**
		* 透明マップ
		*/
		private var _aplane:BitmapData;
		/**
		* バンプマップ
		*/
		private var _bump:BitmapData;
		
		/**
		* ロードすべき画像の残数
		*/
		private var _materialsToLoad:Number;

		/**
		* 実際のレンダリング作業を行うマテリアル
		*/
		private var _material:TriangleMaterial;
		
		/**
		* 光源
		*/
		private var _light:LightObject3D;
////////////////////////////////////////////////////////////////////////////////////////
//プロパティ定義
////////////////////////////////////////////////////////////////////////////////////////
		/**
		* 模様マップを変更する
		* @param	texBmp
		*/
		public function set tex(map:BitmapData):void {
			this._tex = map;
			if (this._tex != null && this._materialsToLoad == 0) {
				this.materialLoadComplete();
			}
		}
		
		/**
		* 模様マップを取得する。
		* ここで取得した模様マップを変更しても、マテリアルは変更されない。
		* マテリアルを変更する場合、texプロパティにマテリアルを設定する必要がある
		* @return
		*/
		public function get tex():BitmapData {
			return this._tex != null ? this._tex.clone() : null;
		}
		/**
		* 透明マップを変更する
		* @param	map
		*/
		public function set aplane(map:BitmapData):void {
			this._aplane = map;
			if (this._aplane != null && this._materialsToLoad == 0) {
				this.materialLoadComplete();
			}
		}
		
		/**
		* 透明マップを取得する。
		* ここで取得した透明マップを変更しても、マテリアルは変更されない。
		* マテリアルを変更する場合、aplaneプロパティにマテリアルを設定する必要がある
		* @return
		*/
		public function get aplane():BitmapData {
			return this._aplane != null ? this._aplane.clone() : null;
		}

		/**
		* バンプマップを変更する
		* @param	map
		*/
		public function set bump(map:BitmapData):void {
			this._bump = map;
			if (this._bump != null && this._materialsToLoad == 0) {
				this.materialLoadComplete();
			}
		}
		
		/**
		* バンプマップを取得する。
		* ここで取得したバンプマップを変更しても、マテリアルは変更されない。
		* マテリアルを変更する場合、bumpプロパティにマテリアルを設定する必要がある
		* @return
		*/
		public function get bump():BitmapData {
			return this._bump != null ? this._bump.clone() : null;
		}
		
		public function get light():LightObject3D {
			return this._light;
		}
		
		public function set light( l : LightObject3D) :void {
			this._light = l;
			if (this._light != null && this._materialsToLoad == 0) {
				this.materialLoadComplete();
			}
		}
////////////////////////////////////////////////////////////////////////////////////////
//コンストラクタ
////////////////////////////////////////////////////////////////////////////////////////
		public function MetasequoiaMaterial() :void {
			this.init();
		}
////////////////////////////////////////////////////////////////////////////////////////
//publicな関数
////////////////////////////////////////////////////////////////////////////////////////
	/**
	* mqoファイルのマテリアルチャンク行と基本パスを用い、画像をロードしてマテリアルを作成する
	* @param	path
	* @param	line
	* @return
	*/
	public function parseLine(path:String , line:String) : void {
		this.init();
		this._basePath = path;
		this.parseColorInfo(line);
		this.parseName(line);
		var isTex:Boolean = this.parseTex(line);
		var isAplane:Boolean = this.parseAplane(line);
		var isBump:Boolean = this.parseBump(line);
		if (!(isTex || isAplane || isBump)) {
			this.materialLoadComplete();
		}
	}
	
	override public function drawTriangle(face3D:RenderTriangle,
														graphics:Graphics,
														renderSessionData:RenderSessionData,
														altBitmap:BitmapData = null,
														altUV:Matrix = null):void {
		if (this._material) {
			try {
				this._material.drawTriangle(face3D , graphics , renderSessionData , altBitmap , altUV);
			} catch (e:Error) {
				
			}
		}
	}
	
	override public function toString():String {
		var s:String;
		s = "[MetasequoiaMaterial tex=" + this._tex + "| aplane=" + this._aplane + 
			"| bump=" + this._bump + "| light=" + this._light + "| interactive=" + this.interactive +  "]";
		return s;
	}
////////////////////////////////////////////////////////////////////////////////////////
//privateな関数
////////////////////////////////////////////////////////////////////////////////////////
		/**
		* 初期化関数
		* @return
		*/
		private function init() :void {
			//テクスチャの初期化
			this.bitmap = null;
			this._materialsToLoad = 0;
			this._tex = null;
			this._aplane = null;
			this._bump = null;
		}

		/**
		* 色を解析し、設定
		* @param col マテリアルチャンク行の色パラメータ。col(R,G,B,A)で表される。
		*/
		private function parseColorInfo(line:String) : void {
			var colorstr:String = getParam(line , "col");
			if (colorstr) {
				var ret:Object = new Object();
				var color:Array = colorstr.match(/\d+\.\d+/g);
				var r:int = parseFloat(color[0]) * 255;
				var g:int = parseFloat(color[1]) * 255;
				var b:int = parseFloat(color[2]) * 255;
				var a:Number = parseFloat(color[3]);
				this.fillColor = ((r << 16) | (g << 8) | b);
				this.fillAlpha = a;
			}
		}
		
		/**
		* マテリアル名を解析し、設定
		* @param	line
		*/
		private function parseName(line:String) : void {
			// マテリアルの名前を取得
			var nameBeginIndex:int = line.indexOf("\"");
			var nameEndIndex:int = line.indexOf("\"", nameBeginIndex + 1);
			this.name = line.substring(nameBeginIndex + 1, nameEndIndex);
		}
		
		/**
		* 模様マップを解析し、設定
		* @param	line
		* @return	マップ画像が必要な場合true、不要な場合false
		*/
		private function parseTex(line:String):Boolean {
			var texPath:String = getParam(line , "tex");
			if (texPath) {
				texPath = this._basePath + texPath;
				this._materialsToLoad++;
				var manager:ImageManager = new ImageManager(texPath);
				manager.addEventListener(ImageManagerEvent.LOAD_COMPLETE , texLoadComplete);
				manager.getImage(texPath);
				return true;
			} else {
				return false;
			}
		}
		
		private function texLoadComplete(evt:ImageManagerEvent):void {
			var manager:ImageManager = evt.manager;
			var bd:BitmapData = manager.bitmapData;
			if (bd) {
				this._materialsToLoad--;
				this.tex = bd;
			}
		}
		/**
		* 透明マップを解析し、設定
		* @param	line
		* @return	マップ画像が必要な場合true、不要な場合false
		*/
		private function parseAplane(line:String):Boolean {
			var aplanePath:String = getParam(line , "aplane");
			if (aplanePath) {
				aplanePath = this._basePath + aplanePath;
				this._materialsToLoad++;
				var manager:ImageManager = new ImageManager(aplanePath);
				manager.addEventListener(ImageManagerEvent.LOAD_COMPLETE , aplaneLoadComplete);
				manager.getImage(aplanePath);
				return true;
			} else {
				return false;
			}
		}
		
		private function aplaneLoadComplete(evt:ImageManagerEvent):void {
			var manager:ImageManager = evt.manager;
			var bd:BitmapData = manager.bitmapData;
			if (bd) {
				this._materialsToLoad--;
				this.aplane = bd;
			}
		}
		/**
		* バンプマップを解析し、設定
		* @param	line
		* @return	マップ画像が必要な場合true、不要な場合false
		*/
		private function parseBump(line:String):Boolean {
			var bumpPath:String = getParam(line , "bump");
			if (bumpPath) {
				bumpPath = this._basePath + bumpPath;
				this._materialsToLoad++;
				var manager:ImageManager = new ImageManager(bumpPath);
				manager.addEventListener(ImageManagerEvent.LOAD_COMPLETE , bumpLoadComplete);
				manager.getImage(bumpPath);
				return true;
			} else {
				return false;
			}
		}
		
		private function bumpLoadComplete(evt:ImageManagerEvent):void {
			var manager:ImageManager = evt.manager;
			var bd:BitmapData = manager.bitmapData;
			if (bd) {
				this._materialsToLoad--;
				this.bump = bd;
			}
		}
		
		/**
		* マテリアルのロードが完全終了した際に呼ばれ、マテリアルの準備を行う
		* @return
		*/
		private function materialLoadComplete() : void {
			if ( (this.tex || this.aplane || this.bump) == null ) {
				//画像なし→fillColor、fillAlphaでColorMaterial作成
				this._material = new ColorMaterial(this.fillColor , this.fillAlpha);
			} else {
				var bm:BitmapMaterial = this.createBitmapMaterial();
				if ( this.bump != null && this.light != null ) {
					//バンプマップ有
					var sm:ShadedMaterial = new ShadedMaterial(bm , new PhongShader(this.light , 0xFFFFFF , 0x000000 , 0 , null , this.bump) );
					this._material = sm;
				} else {
					//バンプマップ無
					this._material = bm;
				}
			}
			var fileEvent:FileLoadEvent = new FileLoadEvent( FileLoadEvent.LOAD_COMPLETE);
			this.dispatchEvent( fileEvent );
		}
		
		/**
		* 模様マップと透明マップからBitmapMaterialを作成する。
		* @return
		*/
		private function createBitmapMaterial() : BitmapMaterial {
			if ( (this.tex || this.aplane) == null ) {
				return new BitmapColorMaterial(this.fillColor);
			}
			var bmpData:BitmapData;
			if ( this.tex != null ) {
				bmpData = new BitmapData (this.tex.width , this.tex.height , true , this.fillColor) ;
				bmpData.draw(this.tex);
			} else {
				bmpData = new BitmapData (this.aplane.width , this.aplane.height , true , this.fillColor) ;
			}
			if ( this.aplane != null ) {
				bmpData.copyChannel(this.aplane , this.aplane.rect , new Point(0,0) , BitmapDataChannel.RED , BitmapDataChannel.ALPHA);
			}
			return new BitmapMaterial( bmpData );
		}
////////////////////////////////////////////////////////////////////////////////////////
//ユーティリティ的な関数
////////////////////////////////////////////////////////////////////////////////////////
		/**
		* line内からパラメータを抜き出し、返す。
		* 二重引用符(")で囲まれている場合、二重引用符は削除する
		* @param	line パラメータを探し出す文字列
		* @param	paramName 対象パラメータ名
		* @return 対象パラメータ値
		*/
		private static function getParam(line:String, paramName:String):String {
			var pattern:RegExp = new RegExp(".*" + paramName + "\\(\"?(.*?)\"?\\).*" , "g");
			if (pattern.test(line)) {
				var result:String = line.replace(pattern , "$1");
				return result;
			} else {
				return null;
			}
		}
	}
	
}
