package framework.view3D;

import java.util.ArrayList;
import java.util.Enumeration;

import javax.media.j3d.Appearance;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.DistanceLOD;
import javax.media.j3d.Geometry;
import javax.media.j3d.GraphicsContext3D;
import javax.media.j3d.LOD;
import javax.media.j3d.Light;
import javax.media.j3d.Node;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;

import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;

import framework.model3D.BackgroundBox;
import framework.model3D.BaseObject3D;
import framework.model3D.BumpMapGenerator;
import framework.model3D.FresnelsReflectionMapGenerator;
import framework.model3D.IViewer3D;
import framework.model3D.Object3D;
import framework.model3D.ReflectionMapGenerator;

public class Viewer3D implements IViewer3D {
	GraphicsContext3D graphicsContext3D = null;
	ReflectionMapShader reflectionMappingShader = null;
	FresnelsReflectionMapShader fresnelsReflectionMappingShader = null;
	Dot3BumpMapShader dot3BumpMappingShader = null;
	FlatDot3BumpMapShader flatDot3BumpMappingShader = null;
	ArrayList<Light> lights = null;
	BackgroundBox skyBox = null;
	Camera3D camera = null;
	
	// 以下は省メモリ化のため導入
	static private Point3f center = new Point3f();
	static private Point3f origin = new Point3f();
	
	public Viewer3D(Camera3D camera) {
		this.camera = camera;
		reflectionMappingShader = new ReflectionMapShader();
		fresnelsReflectionMappingShader = new FresnelsReflectionMapShader();
		dot3BumpMappingShader = new Dot3BumpMapShader();
		flatDot3BumpMappingShader = new FlatDot3BumpMapShader();
	}

	@Override
	public void setGraphicsContext3D(GraphicsContext3D graphicsContext3D) {
		this.graphicsContext3D = graphicsContext3D;
	}
	

	@Override
	public void update(ArrayList<Light> lights, BackgroundBox skyBox) {
		// 光源の更新
		if (this.lights != lights) {
			this.lights = lights;
			for (int n = 0; n < lights.size(); n++) {
				if (lights.get(n) instanceof DirectionalLight) {
					// 平行光源の場合
					DirectionalLight dirlight = (DirectionalLight)lights.get(n);
					dot3BumpMappingShader.init(dirlight);
					flatDot3BumpMappingShader.init(dirlight);
					break;
				}
			}
		}
		
		// スカイボックスの更新
		if (this.skyBox != skyBox) {
			this.skyBox = skyBox;
			reflectionMappingShader.init(skyBox);
			fresnelsReflectionMappingShader.init(skyBox);
		}
	}
	
	public void draw(BaseObject3D obj) {
		
		// GraphicsContext3D 内の光源の更新
		if (lights != null && lights.size() != graphicsContext3D.numLights()) {
			for (int n = 0; n < lights.size(); n++) {
				graphicsContext3D.addLight(lights.get(n));
			}				
		}		
		
		Enumeration<Node> primitives = obj.getPrimitiveNodes();
		ReflectionMapGenerator reflectionMapGenerator = obj.getReflectionMappingInfo();
		BumpMapGenerator bumpMapGenerator = obj.getBumpMappingInfo();
		int n = 0;
		while (primitives.hasMoreElements()) {
			if (obj instanceof Object3D && ((Object3D)obj).isLODSet()) {
				// LOD の判定
				LOD lodNode = ((Object3D)obj).getLOD();
				if (lodNode instanceof DistanceLOD) {
					double nearSide = 0.0;
					double farSide = 0.0;
					DistanceLOD distanceLOD = ((DistanceLOD)lodNode);
					if (n > distanceLOD.numDistances()) break;
					if (n > 0) nearSide = distanceLOD.getDistance(n - 1);
					if (n < distanceLOD.numDistances()) farSide = distanceLOD.getDistance(n);
					
					distanceLOD.getPosition(center);
					Transform3D trans = new Transform3D();
					graphicsContext3D.getModelTransform(trans);
					trans.transform(center);	// オブジェクトの中心座標（世界座標上）
					trans = camera.getWorldToView();
					trans.transform(center);	// オブジェクトの中心座標（カメラ座標上）
					double distance = (double)center.distance(origin);
					if (distance < nearSide) {
						break;
					} else if (distance > farSide && n < distanceLOD.numDistances()) {
						n++;
						continue;
					}
				}
			}
			Node primitive = primitives.nextElement();
			if (primitive instanceof Shape3D) {
				// Shape3Dをレンダリング
				Appearance ap = ((Shape3D)primitive).getAppearance();
				if (bumpMapGenerator != null) {
					// obj にはバンプマッピングが設定されている
					if (!bumpMapGenerator.isHorizontal()) {
						dot3BumpMappingShader.updateAppearance(ap, bumpMapGenerator, camera);
					} else {
						// 水平のポリゴンに対しては、よりテクスチャユニットの消費量が少ない FlatDot3BumpMapShader を使う
						flatDot3BumpMappingShader.updateAppearance(ap, bumpMapGenerator, camera);
					}
				}
				if (reflectionMapGenerator != null) {
					// obj には反射マッピングが設定されている
					if (reflectionMapGenerator instanceof FresnelsReflectionMapGenerator) {
						// フレネル反射を使用
						fresnelsReflectionMappingShader.updateAppearance(ap, (FresnelsReflectionMapGenerator)reflectionMapGenerator, camera);
					} else {
						reflectionMappingShader.updateAppearance(ap, reflectionMapGenerator, camera);
					}
				}
				
				if (((Shape3D)primitive).getGeometry() != null) {
					graphicsContext3D.draw((Shape3D)primitive);
				}
			} else if (primitive instanceof Primitive) {
				Appearance ap = ((Primitive)primitive).getAppearance();
				if (bumpMapGenerator != null) {
					// obj にはバンプマッピングが設定されている
					if (!bumpMapGenerator.isHorizontal()) {
						dot3BumpMappingShader.updateAppearance(ap, bumpMapGenerator, camera);					
					} else {
						// 水平のポリゴンに対しては、よりテクスチャユニットの消費量が少ない FlatDot3BumpMapShader を使う
						flatDot3BumpMappingShader.updateAppearance(ap, bumpMapGenerator, camera);					
					}
				}
				if (reflectionMapGenerator != null) {
					// obj には反射マッピングが設定されている
					if (reflectionMapGenerator instanceof FresnelsReflectionMapGenerator) {
						// フレネル反射を使用
						fresnelsReflectionMappingShader.updateAppearance(ap, (FresnelsReflectionMapGenerator)reflectionMapGenerator, camera);
					} else {
						reflectionMappingShader.updateAppearance(ap, reflectionMapGenerator, camera);
					}
				}
				graphicsContext3D.setAppearance(ap);
				if (primitive instanceof Cone) {
					// Coneをレンダリング
					Geometry g = ((Cone)primitive).getShape(Cone.BODY).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Cone)primitive).getShape(Cone.CAP).getGeometry();
					graphicsContext3D.draw(g);
				} else if (primitive instanceof Cylinder) {
					// Cylinderをレンダリング
					Geometry g = ((Cylinder)primitive).getShape(Cylinder.BODY).getGeometry();			
					graphicsContext3D.draw(g);
					g = ((Cylinder)primitive).getShape(Cylinder.TOP).getGeometry();			
					graphicsContext3D.draw(g);
					g = ((Cylinder)primitive).getShape(Cylinder.BOTTOM).getGeometry();			
					graphicsContext3D.draw(g);
				} else if (primitive instanceof Box) {
					// Boxをレンダリング
					Geometry g = ((Box)primitive).getShape(Box.TOP).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Box)primitive).getShape(Box.BOTTOM).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Box)primitive).getShape(Box.FRONT).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Box)primitive).getShape(Box.BACK).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Box)primitive).getShape(Box.LEFT).getGeometry();
					graphicsContext3D.draw(g);
					g = ((Box)primitive).getShape(Box.RIGHT).getGeometry();
					graphicsContext3D.draw(g);
				} else if (primitive instanceof Sphere) {
					// Sphereをレンダリング
					Geometry g = ((Sphere)primitive).getShape().getGeometry();
					graphicsContext3D.draw(g);
				}
			}
			n++;
		}		
	}

	@Override
	public void setModelTransform(Transform3D t) {
		graphicsContext3D.setModelTransform(t);
	}
}
