package framework.game2D;

import java.math.BigDecimal;

import javax.media.j3d.Appearance;
import javax.media.j3d.Material;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;

import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.image.TextureLoader;

import framework.model3D.BaseObject3D;

/**
 * 2次元の登場物のクラス
 * 
 * @author T.Kuno
 * 
 */
public class Sprite implements Movable {
	Box box = null;
	private TransformGroup transformGroup;
	private double collisionRadius;
	private float scale = 1.0f;

	// 登場物の配置位置
	private Position2D position = new Position2D();
	
	// 登場物の配置位置の奥行
	private double depth = 0.0;
	
	// 登場物の速度
	private Velocity2D velocity = new Velocity2D();


	// /////////////////////////////////////////////////
	//
	// コンストラクタ
	//
	// /////////////////////////////////////////////////

	public Sprite(String imageFile) {
		this(imageFile, 1.0f);
	}
	
	public Sprite(String imageFile, float scale) {
		this(imageFile, scale, 0.0);
	}

	public Sprite(String imageFile, float scale, double depth) {
		this.scale = scale;
		transformGroup = new TransformGroup();
		Appearance appearance = new Appearance();

		if (imageFile != null){
			TextureLoader loader = new TextureLoader(imageFile, TextureLoader.BY_REFERENCE, null);
			appearance.setTexture(loader.getTexture());
			appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
			appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
		} 

		Material material = new Material();
		material.setLightingEnable(false);
		material.setDiffuseColor(0.0f, 0.0f, 0.0f);
		material.setAmbientColor(0.0f, 0.0f, 0.0f);
		material.setSpecularColor(0.0f, 0.0f, 0.0f);
		material.setEmissiveColor(1.0f, 1.0f, 1.0f);
		material.setShininess(1.0f);
		appearance.setMaterial(material);

		TextureAttributes ta = new TextureAttributes();
		ta.setTextureMode(TextureAttributes.REPLACE);
		appearance.setTextureAttributes(ta);

		TransparencyAttributes transAttributes = new TransparencyAttributes();

		transAttributes.setCapability(TransparencyAttributes.ALLOW_BLEND_FUNCTION_READ);
		transAttributes.setCapability(TransparencyAttributes.ALLOW_BLEND_FUNCTION_WRITE);
		transAttributes.setCapability(TransparencyAttributes.ALLOW_MODE_READ);
		transAttributes.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE);
		transAttributes.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
		transAttributes.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);

		transAttributes.setTransparencyMode(TransparencyAttributes.BLENDED);
		transAttributes.setTransparency(0.0f);
		// transAttributes.setSrcBlendFunction(TransparencyAttributes.BLEND_SRC_ALPHA);
		transAttributes.setDstBlendFunction(TransparencyAttributes.BLEND_SRC_ALPHA);
		appearance.setTransparencyAttributes(transAttributes);

		box = new Box(1.0f * scale, 1.0f * scale, 0.0f, Box.GENERATE_TEXTURE_COORDS | Box.GENERATE_NORMALS, appearance);
		transformGroup.addChild(box);
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		position.set(0.0, 0.0);
		setPosition(0.0, 0.0, depth);
		velocity.set(0.0, 0.0);
		setCollisionRadius(1.0);
	}


	// /////////////////////////////////////////////////
	//
	// SpriteのPositionに関するメソッド
	//
	// /////////////////////////////////////////////////

	/**
	 * スプライトの位置を設定する。
	 * 
	 * @param x
	 * @param y
	 */
	public void setPosition(double x, double y) {
		position.set(x, y);
		Transform3D t3d = new Transform3D();
		t3d.setTranslation(new Vector3d(x, y, depth));
		transformGroup.setTransform(t3d);
	}
	
	/**
	 * スプライトの位置を設定する。
	 * 
	 * @param x
	 * @param y
	 */
	public void setPosition(double x, double y, double z) {
		depth = z;
		position.set(x, y);
		Transform3D t3d = new Transform3D();
		t3d.setTranslation(new Vector3d(x, y, z));
		transformGroup.setTransform(t3d);
	}

	// インターフェースの実装
	@Override
	public Position2D getPosition() {
		return this.position;
	}

	@Override
	public void setPosition(Position2D pos) {
		setPosition(pos.getX(), pos.getY());
	}


	// //////////////////////////////////////////////////////
	//
	// SpriteのVelocityに関するメソッド
	//
	// //////////////////////////////////////////////////////
	/**
	 * スプライトの速度を設定する。
	 * 
	 * @param x
	 * @param y
	 */
	public void setVelocity(double x, double y) {
		velocity.set(x, y);
	}

	// インターフェースの実装
	@Override
	public void setVelocity(Velocity2D vel) {
		setVelocity(vel.getX(), vel.getY());
	}

	@Override
	public Velocity2D getVelocity() {
		return this.velocity;
	}

	// /////////////////////////////////////////
	//
	// Spriteのmotionメソッド
	//
	// ////////////////////////////////////////

	// インターフェースの実装
	@Override
	public void motion(long interval) {
		double frame = (double)interval / 1000.0;
		setPosition(position.getX() + frame * velocity.getX(), position.getY() + frame * velocity.getY());
	}

	/**
	 * オブジェクトとの衝突判定をし、その結果に応じて物体を動かす
	 * 
	 * @param interval
	 * @param mazeGround
	 *            --- 迷路ゲームのステージ
	 */
	public void motion(long interval, Map2D mazeGround) {
		// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//
		// 注意事項及び連絡事項。
		// BigDecimalクラスを使って意図的に丸め誤差を行っているのは、座標点や速度ベクトルの成分値などの誤差が原因で
		// 衝突判定がうまく機能しない恐れがあったためです。
		//
		// 原因はわかりませんが、Boxクラスのインスタンスを生成する際にfloat型でBoxの大きさなどを指定し、
		// 座標点、速度ベクトルの値がdouble型であり、その値を用いてBoxを動かしているからと推測しています。（単純に、計算処理の結果が間違っている公算大ですが。）
		// あるいはカメラの見える範囲を設定（setViewRange(・・・)）が原因かと思われます。
		//
		// 現在も衝突判定が完全に出来ておらずに不具合が入っているため、そのままにしてあります。
		//
		// 衝突判定の完成度としては、ブロック一つに対しては仕様通りの動きになっています。
		// ですが、4個（ブロックがくっついてる状態）の場合だとうまくいかない状態です。
		//
		// キャッシュ処理もほとんど行っていないため、若干重たく感じると思います。
		//
		// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		// スプライトの位置を表す値を小数第2位以降を切り捨て、スプライトの位置を再計算する
		position.setX(new BigDecimal(position.getX()).setScale(2,BigDecimal.ROUND_DOWN).doubleValue());
		position.setY(new BigDecimal(position.getY()).setScale(2,BigDecimal.ROUND_DOWN).doubleValue());
		
		velocity.setX(new BigDecimal(velocity.getX()).setScale(3,BigDecimal.ROUND_UP).doubleValue());
		velocity.setY(new BigDecimal(velocity.getY()).setScale(3,BigDecimal.ROUND_UP).doubleValue());

		// 衝突判定する前に一度スプライトの位置を動かす
		motion(interval);

		// 衝突判定の結果
		MapCollisionResult mazeCollisionResult = new MapCollisionResult();

//		System.out.println(position.getX() + "," + position.getY());
//		System.out.println(velocity.getX() + "," + velocity.getX());

		mazeCollisionResult = mazeGround.checkCollision(this);

		if (mazeCollisionResult.isCheckColision()) {
			velocity.set(mazeCollisionResult.getColisionBackVelocity().getX(), mazeCollisionResult.getColisionBackVelocity().getY());
			motion(interval);
			mazeCollisionResult.setCheckColision(false);

			// ////////////////////////////////////////////////////
			//
			// 再び衝突判定
			//
			// ////////////////////////////////////////////////////

			// 衝突判定の結果
			mazeCollisionResult = mazeGround.checkCollision(this);
			if (mazeCollisionResult.isCheckColision()) {
				velocity.set(mazeCollisionResult.getColisionBackVelocity().getX(), mazeCollisionResult.getColisionBackVelocity().getY());
				motion(interval);
				mazeCollisionResult.setCheckColision(false);
			}
		}
	}

	
	// //////////////////////////////
	//
	// プレイヤーを左方向に動かすメソッド
	//
	// //////////////////////////////

	/**
	 * キャラクタの位置を左方向に動かす。
	 * 
	 * @param d
	 *            動かす量
	 */
	public void moveLeft(double d) {
		this.position.addX(-1.0 * d / 100);
		setPosition(position);
	}

	// //////////////////////////////
	//
	// プレイヤーを右方向に動かすメソッド
	//
	// //////////////////////////////

	/**
	 * キャラクタの位置を右方向に動かす。
	 * 
	 * @param d
	 *            動かす量
	 */
	public void moveRight(double d) {
		this.position.addX(1.0 * d / 100);
		setPosition(position);
	}

	// //////////////////////////////
	//
	// プレイヤーを上方向に動かすメソッド
	//
	// //////////////////////////////

	/**
	 * キャラクタの位置を上方向に動かす。
	 * 
	 * @param d
	 *            動かす量
	 */
	public void moveUp(double d) {
		this.position.addY(1.0 * d / 100);
		setPosition(position);
	}

	// //////////////////////////////
	//
	// プレイヤーを下方向に動かすメソッド
	//
	// //////////////////////////////

	/**
	 * キャラクタの位置を下方向に動かす。
	 * 
	 * @param d
	 *            動かす量
	 */
	public void moveDown(double d) {
		this.position.addY(-1.0 * d / 100);
		setPosition(position);
	}

	// //////////////////////////////
	//
	// 衝突判定関連のメソッド
	//
	// //////////////////////////////

	/**
	 * 衝突判定のBounding Sphere（境界球）をcollisionRadiusで設定する
	 * 
	 * @param collisionRadius
	 *            -- BoundingSphereの半径
	 */
	public void setCollisionRadius(double collisionRadius) {
		this.collisionRadius = collisionRadius;
	}

	/**
	 * 衝突判定のBounding Sphere（境界球）の半径を返す
	 * 
	 * @return　 BoundingSphereの半径
	 */
	public double getCollisionRadius() {
		return collisionRadius;
	}
	
	/**
	 * イメージを変更する
	 * @param imageFile イメージファイル名
	 */
	public void setImage(String imageFile) {
		Appearance appearance = box.getAppearance();
		if (imageFile != null && appearance != null){
			TextureLoader loader = new TextureLoader(imageFile, TextureLoader.BY_REFERENCE, null);
			appearance.setTexture(loader.getTexture());
		} 
	}

	@Override
	public BaseObject3D getBody() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public TransformGroup getTransformGroupToPlace() {
		// TODO Auto-generated method stub
		return transformGroup;
	}

	@Override
	public boolean checkCollision(Movable other) {
		if (other instanceof Sprite) {
			Sprite otherSprite = (Sprite)other;
			double thisMinX = getPosition().getX();
			double thisMaxX = getPosition().getX() + 2.0 * (double)scale;
			double thisMinY = getPosition().getY();
			double thisMaxY = getPosition().getY() + 2.0 * (double)scale;
			double otherMinX = otherSprite.getPosition().getX();
			double otherMaxX = otherSprite.getPosition().getX() + 2.0 * (double)scale;
			double otherMinY = otherSprite.getPosition().getY();
			double otherMaxY = otherSprite.getPosition().getY() + 2.0 * (double)scale;
			if((otherMinX <= thisMinX && otherMaxX >= thisMaxX)
					|| (otherMinX >= thisMinX && otherMaxX <= thisMaxX)){
				if(otherMaxY > thisMinY && otherMaxY < thisMaxY){
					return true;
				} else if(otherMinY < thisMaxY && otherMinY > thisMinY){
					return true;
				}
			} else if((otherMinY <= thisMinY && otherMaxY >= thisMaxY)
				|| (otherMinY >= thisMinY && otherMaxY <= thisMaxY)){
				if(otherMaxX > thisMinX && otherMaxX < thisMaxX){
					return true;				
				} else if(otherMinX < thisMaxX && otherMinX > thisMaxX){
					return true;				
				}
			} else if (otherMinX > thisMinX && otherMinX < thisMaxX && otherMaxY > thisMinY && otherMaxY < thisMaxY) {
				return true;			
			} else if (otherMaxX > thisMinX && otherMaxX < thisMaxX && otherMaxY > thisMinY && otherMaxY < thisMaxY) {
				return true;			
			} else if (otherMaxX > thisMinX && otherMaxX < thisMaxX && otherMinY > thisMinY && otherMinY < thisMaxY) {
				return true;			
			} else if (otherMinX > thisMinX && otherMinX < thisMaxX && otherMinY > thisMinY && otherMinY < thisMaxY) {
				return true;			
			}
			return false;
		} else if (other instanceof Actor2D) {
			Vector2d v = new Vector2d();
			v.sub(this.getPosition().getVector2d(), other.getPosition()
					.getVector2d());
			if (v.length() <= (getCollisionRadius() + ((Actor2D)other).getCollisionRadius())) {
				return true;
			} else {
				return false;
			}			
		}
		return false;
	}
	
	public boolean checkCollisionWithRadius(Movable other) {
		if (other instanceof Sprite) {
			Sprite otherSprite = (Sprite)other;
			double thisMinX = getPosition().getX();
			double thisMaxX = getPosition().getX() + 2.0 * collisionRadius;
			double thisMinY = getPosition().getY();
			double thisMaxY = getPosition().getY() + 2.0 * collisionRadius;
			double otherMinX = otherSprite.getPosition().getX();
			double otherMaxX = otherSprite.getPosition().getX() + 2.0 * otherSprite.collisionRadius;
			double otherMinY = otherSprite.getPosition().getY();
			double otherMaxY = otherSprite.getPosition().getY() + 2.0 * otherSprite.collisionRadius;
			if((otherMinX <= thisMinX && otherMaxX >= thisMaxX)
					|| (otherMinX >= thisMinX && otherMaxX <= thisMaxX)){
				if(otherMaxY > thisMinY && otherMaxY < thisMaxY){
					return true;
				} else if(otherMinY < thisMaxY && otherMinY > thisMinY){
					return true;
				}
			} else if((otherMinY <= thisMinY && otherMaxY >= thisMaxY)
				|| (otherMinY >= thisMinY && otherMaxY <= thisMaxY)){
				if(otherMaxX > thisMinX && otherMaxX < thisMaxX){
					return true;				
				} else if(otherMinX < thisMaxX && otherMinX > thisMaxX){
					return true;				
				}
			} else if (otherMinX > thisMinX && otherMinX < thisMaxX && otherMaxY > thisMinY && otherMaxY < thisMaxY) {
				return true;			
			} else if (otherMaxX > thisMinX && otherMaxX < thisMaxX && otherMaxY > thisMinY && otherMaxY < thisMaxY) {
				return true;			
			} else if (otherMaxX > thisMinX && otherMaxX < thisMaxX && otherMinY > thisMinY && otherMinY < thisMaxY) {
				return true;			
			} else if (otherMinX > thisMinX && otherMinX < thisMaxX && otherMinY > thisMinY && otherMinY < thisMaxY) {
				return true;			
			}
			return false;
		} else if (other instanceof Actor2D) {
			Vector2d v = new Vector2d();
			v.sub(this.getPosition().getVector2d(), other.getPosition()
					.getVector2d());
			if (v.length() <= (getCollisionRadius() + ((Actor2D)other).getCollisionRadius())) {
				return true;
			} else {
				return false;
			}			
		}
		return false;
	}
}
