import java.io.*;
import java.nio.*;
import java.awt.image.*;
import javax.media.j3d.*;
import javax.vecmath.*;

import dxg.*;
import vra.*;
import vfs.*;

/**
 * 3Dオブジェクトを表わすクラス
 */
public abstract class Obj3D {
    String mName;

    protected Obj3D(String name) {
	mName = name;
    }

    public abstract javax.media.j3d.Node getObject(Vfs vfs) throws ApplicationException;

    @Override
    public String toString() {
	return mName;
    }

    /**
     * 指定したvraファイルに対応する3Dオブジェクトを返す
     */
    static javax.media.j3d.Node loadByVraFile(Vfs vfs, String vraPath) throws ApplicationException {
	Vra vra = null;
	try {
	    vra = Vra.load(vfs, vraPath);
	} catch (VraException e) {
	    throw new ApplicationException("cannot load vra: " + vraPath, e);
	}
	BranchGroup vraBg = new BranchGroup();

	Dxg dxg = null;
	try {
	    dxg = new Dxg(vfs, vra.getDxgPath());
	} catch (DxgException e) {
	    throw new ApplicationException("cannot load dxg", e);
	}
	GroupData[] dxgGrpList = dxg.getGroups();

	for (int vraMeshId = 0; vraMeshId < vra.getNrMeshes(); vraMeshId++) {
	    String meshName = vra.getMeshName(vraMeshId);

	    int materialId = vra.getMaterialId(vraMeshId);
	    Appearance app;
	    try {
		app = createAppearance(vfs, vra.getDdsPath(materialId));
	    } catch (IOException e) {
		throw new ApplicationException("cannot create apperance", e);
	    }

	    int dxgGrpId = dxg.getGroupId(meshName);
	    if (dxgGrpId < 0) {
		continue;
	    }
	    GroupData dxgGrp = dxgGrpList[dxgGrpId];
	    BranchGroup grpBg = addDxgMeshes(dxgGrp, app);
	    vraBg.addChild(grpBg);
	}
	return vraBg;
    }

    private static BranchGroup addDxgMeshes(GroupData dxgGrp, Appearance app) {
	BranchGroup grpBg = new BranchGroup();
	for(int dxgMeshId = 0; dxgMeshId < dxgGrp.getNrMeshes(); dxgMeshId++) {
	    float[] verteces = dxgGrp.getVerteces(dxgMeshId);
	    int[] faces = dxgGrp.getFaces(dxgMeshId);
	    float[] uvs = dxgGrp.getUvs(dxgMeshId);

	    IndexedTriangleArray geometry = new IndexedTriangleArray(
		verteces.length,
		GeometryArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2 | GeometryArray.USE_COORD_INDEX_ONLY,
		faces.length);
	    geometry.setCoordinates(0, verteces);
	    geometry.setCoordinateIndices(0, faces);
	    geometry.setTextureCoordinates(0, 0, uvs);

	    Shape3D shape = new Shape3D(geometry);
	    shape.setAppearance(app);
	    grpBg.addChild(shape);
	}
	return grpBg;
    }

    private static Appearance createAppearance(Vfs vfs, String ddsPath) throws IOException {
	// テクスチャ
	ByteBuffer buf = vfs.getBuffer(ddsPath);
	BufferedImage image = DdsLoader.load(buf);
	ImageComponent2D imgc = new ImageComponent2D(ImageComponent.FORMAT_RGBA,
		image);
	Texture tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
		image.getWidth(), image.getHeight());
	tex.setImage(0, imgc);

	// ポリゴン表示属性
	PolygonAttributes attr = new PolygonAttributes();
	// ワイヤーフレームで表示
	//attr.setPolygonMode(PolygonAttributes.POLYGON_LINE);
	// 表面・裏面を表示
	attr.setCullFace(PolygonAttributes.CULL_NONE);

	// 透明度
	//TransparencyAttributes tattr = new TransparencyAttributes();
	//tattr.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE);
	//tattr.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
	//tattr.setTransparencyMode(TransparencyAttributes.BLENDED);

	// 作成したテクスチャ・ポリゴン表示属性・透明度を設定
	Appearance app = new Appearance();
	app.setTexture(tex);
	app.setPolygonAttributes(attr);
	//app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
	//app.setTransparencyAttributes(tattr);

	return app;
    }
}

//----------------------------------------------------------------------

class CharaObj3D extends Obj3D {
    private int mVariation;
    private File[] mFiles;

    CharaObj3D(String name, int var, File[] files) {
	super(name);
	mVariation = var;
	mFiles = files;
    }

    public String toString() {
	return mName + "-" + mVariation;
    }

    @Override
    public javax.media.j3d.Node getObject(Vfs vfs) throws ApplicationException {
	// グループ
	BranchGroup wig = new BranchGroup(); // 01
	BranchGroup head = new BranchGroup(); // 02
	BranchGroup body = new BranchGroup(); // 03-16
	wig.addChild(loadByVraFile(vfs, mFiles[1].getPath()));
	head.addChild(loadByVraFile(vfs, mFiles[2].getPath()));
	for(int i = 3; i <= 16; i++)
	    body.addChild(loadByVraFile(vfs, mFiles[i].getPath()));

	Transform3D wigTr = new Transform3D();
	wigTr.set(new Vector3d(0.0, 145.0, 0.0));
	TransformGroup wigTg = new TransformGroup(wigTr);
	wigTg.addChild(wig);

	Transform3D headTr = new Transform3D();
	headTr.set(new Vector3d(0.0, 133.0, 0.0));
	TransformGroup headTg = new TransformGroup(headTr);
	headTg.addChild(head);

	// ひとつのBranchGroupにまとめる
	BranchGroup charaBg = new BranchGroup();
	charaBg.addChild(wigTg);
	charaBg.addChild(headTg);
	charaBg.addChild(body);
	return charaBg;
    }
}

//----------------------------------------------------------------------

class SingleObj3D extends Obj3D {
    private File mFile;

    /**
     * @param name	表示用の名前
     * @param file	vraファイルを示すFileオブジェクト
     */
    SingleObj3D(String name, File file) {
	super(name);
	mFile = file;
    }

    public javax.media.j3d.Node getObject(Vfs vfs) throws ApplicationException {
	return loadByVraFile(vfs, mFile.getPath());
    }
}

class CharaPartObj3D extends SingleObj3D {
    CharaPartObj3D(String name, File file) {
	super(name, file);
    }
}

class ItemObj3D extends SingleObj3D {
    ItemObj3D(String name, File file) {
	super(name, file);
    }
}

class FieldObj3D extends SingleObj3D {
    FieldObj3D(String name, File file) {
	super(name, file);
    }
}
