/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : CylinderCreatorTG.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */
package ciss.phase_viewer.acviewer.scenegraphelements;

import java.util.Enumeration;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Geometry;
import javax.media.j3d.Group;
import javax.media.j3d.LineArray;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.RestrictedAccessException;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;

import org.apache.log4j.Logger;

import ciss.phase_viewer.settings.GlobalProperties;
import ciss.phase_viewer.settings.PropertiesManager;

import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;

public class CylinderCreatorTG extends TransformGroup {
    private static Logger logger = Logger.getLogger(CylinderCreatorTG.class
            .getName());
    private int edges;
    private double height;
    private double radius;
    private Appearance cylApp;
    private Point3d b;
    private Point3d a;
    private GlobalProperties gp = PropertiesManager
            .getGlobalProperties(PropertiesManager.PROPERTIES_ACV);

    /**
     * edge(解像度)のデフォルト値(7)を設定.
     */
    public CylinderCreatorTG(Point3d b, Point3d a, double radius,
            Appearance cylApp) {
        super();
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);
        edges = 7;
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.cylApp = cylApp;
        create();
    }

    public CylinderCreatorTG(Point3d b, Point3d a, double radius,
            Appearance cylApp, int edges) {
        super();

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);

        this.edges = edges;
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.cylApp = cylApp;
        create();
    }

    public static int CYLINDER = 0;
    public static int WIRE = 1;
    private int mode = CYLINDER;
    private int linePattern = LineAttributes.PATTERN_SOLID;

    public CylinderCreatorTG(Point3d b, Point3d a, double radius,
            Color3f color3f, int edges) {
        super();

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);

        this.edges = edges;
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.color3f = color3f;
        createCylApp();
        create();
    }

    public CylinderCreatorTG(Point3d b, Point3d a, double radius,
            Color3f color3f, int edges, int mode) {
        super();

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);
        this.edges = edges;
        this.mode = mode;
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.color3f = color3f;
        createCylApp();

        create();
    }

    public CylinderCreatorTG(Point3d b, Point3d a, double radius,
            Color3f color3f, int edges, int mode, int linePattern) {
        super();

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);
        this.edges = edges;
        this.mode = mode;
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.color3f = color3f;
        this.linePattern = linePattern;
        createCylApp();
        create();
    }

    public CylinderCreatorTG(Point3d b, Point3d a) {
        super();
        this.b = b;
        this.a = a;

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
        this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        this.setCapability(Group.ALLOW_CHILDREN_READ);
        this.setCapability(ALLOW_CHILDREN_WRITE);
    }

    private void createCylApp() {
        this.cylApp = new Appearance();
        Material material = new Material();
        material.setDiffuseColor(new Color3f(color3f));
        material.setShininess(120.0f);
        cylApp.setMaterial(material);
    }

    /**
     * 解像度を設定する.
     * 
     * @param e
     *            edgeの数.
     */
    public void setResolution(int e) {
        edges = e;
    }

    /**
     * 強引に色を変更する場合に利用する.
     * 
     * @param color3f
     *            色
     */
    private Color3f color3f;

    public void setColor(Color3f color3f) {
        this.color3f = color3f;
        createCylApp();
    }

    private BranchGroup branchGroup;
    private BranchGroup branchBranchGroup;
    private Node cyl;

    /**
     * 指定の向きの円柱を作成するメソッド.
     * 
     * @param b
     *            円柱の下(ないし上)の座標
     * @param a
     *            円柱の上(ないし下)の座標
     * @param radius
     *            円柱の半径
     * @param cylApp
     *            円柱のAppearance.
     * @return 指定の向きの円柱
     */
    public void create() {
        // this.b = b;
        // this.a = a;
        // this.radius = radius;
        // this.cylApp = cylApp;
        Transform3D tgm = getTransMatrix();
        if (tgm == null)
            return;
        this.setTransform(tgm);
        if (mode == CYLINDER) {
            cyl = new Cylinder((float) radius, (float) height,
                    Cylinder.GENERATE_NORMALS, edges, 1, cylApp);
            setAllowIntersect((Cylinder) cyl);
        } else {
            cyl = createShape3DCyl();
        }
        branchGroup = new BranchGroup();
        branchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        branchGroup.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        branchBranchGroup = new BranchGroup();
        branchBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
        branchBranchGroup.addChild(cyl);
        branchGroup.addChild(branchBranchGroup);
        this.addChild(branchGroup);
    }

    private Color3f cylcolor = null;

    private Node createShape3DCyl() {
        Node nd = new Shape3D();
        cylcolor = color3f;
        // if ( cylcolor == null ) {
        // if ( color3f != null ) {
        // cylcolor = color3f;
        // } else {
        // try{
        // cylcolor = new Color3f(
        // Float.parseFloat(gp.getProperty("bondcolor_r")),
        // Float.parseFloat(gp.getProperty("bondcolor_g")),
        // Float.parseFloat(gp.getProperty("bondcolor_b")));
        // } catch(Exception exc){
        // cylcolor = new Color3f(0.6f,0.6f,0.6f);
        // }
        // }
        // }

        LineArray lineArray = new LineArray(2, LineArray.COORDINATES
                | LineArray.COLOR_3);
        lineArray.setCoordinate(0, new Point3f(0, -(float) (height * 0.5), 0));
        lineArray.setColor(0, cylcolor);
        lineArray.setCoordinate(1, new Point3f(0, (float) (height * 0.5), 0));
        lineArray.setColor(1, cylcolor);
        LineAttributes lattr = new LineAttributes();
        lattr.setLineWidth((float) radius * 300);
        lattr.setLinePattern(linePattern);
        // lattr.setPatternMask(0x00ff);
        // lattr.setPatternScaleFactor(3);
        Appearance app = new Appearance();
        app.setLineAttributes(lattr);
        try {
            lineArray.setCapability(LineArray.ALLOW_INTERSECT);
        } catch (RestrictedAccessException rae) {
        }
        ((Shape3D) nd).setGeometry(lineArray);
        ((Shape3D) nd).setAppearance(app);
        return nd;
    }

    private float height_buff;

    public void recreate(Point3d b, Point3d a, double radius, Appearance cylApp) {
        this.b = b;
        this.a = a;
        this.radius = radius;
        this.cylApp = cylApp;
        Transform3D tgm = getTransMatrix();
        if (tgm == null)
            return;
        this.setTransform(tgm);
        cyl = null;
        branchBranchGroup.detach();

        if (mode == CYLINDER) {
            cyl = new Cylinder((float) radius, (float) height,
                    Cylinder.GENERATE_NORMALS, edges, 1, cylApp);
            setAllowIntersect((Cylinder) cyl);
        } else {
            cyl = createShape3DCyl();
        }
        height_buff = (float) height;
        branchBranchGroup = new BranchGroup();
        branchBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
        branchBranchGroup.addChild(cyl);
        branchGroup.addChild(branchBranchGroup);
    }

    public void setMode(int mode) {
        this.mode = mode;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public void setLinePattern(int linePattern) {
        this.linePattern = linePattern;
    }

    public void recreate(Point3d b, Point3d a, boolean force) {
        if (this.b.epsilonEquals(b, 0.00005)
                && this.a.epsilonEquals(a, 0.00005) && !force) {
            logger.debug("no need to update this bond.");
            return;
        }
        this.b = b;
        this.a = a;
        Transform3D tgm = getTransMatrix();
        if (tgm == null)
            return;
        this.setTransform(tgm);

        if (Math.abs(height_buff - (float) height) > 0.005f || force) {
            cyl = null;
            // branchBranchGroup.detach();
            // cyl = new Cylinder( (float) radius, (float) height,
            // Cylinder.GENERATE_NORMALS, edges, 1, cylApp);
            if (mode == CYLINDER) {
                if (force)
                    createCylApp();
                cyl = new Cylinder((float) radius, (float) height,
                        Cylinder.GENERATE_NORMALS, edges, 1, cylApp);
                setAllowIntersect((Cylinder) cyl);
            } else {
                cyl = createShape3DCyl();
            }
            height_buff = (float) height;
            branchBranchGroup = new BranchGroup();
            branchBranchGroup.setCapability(BranchGroup.ALLOW_DETACH);
            branchBranchGroup.addChild(cyl);
            branchGroup.setChild(branchBranchGroup, 0);
        }

    }

    private Transform3D getTransMatrix() {
        // calculate center of object
        Vector3d center = new Vector3d();
        center.x = (a.x - b.x) / 2.0 + b.x;
        center.y = (a.y - b.y) / 2.0 + b.y;
        center.z = (a.z - b.z) / 2.0 + b.z;

        // calculate height of object and unit vector along cylinder axis
        Vector3d unit = new Vector3d();
        unit.sub(a, b); // unit = apex - base;
        height = unit.length();
        unit.normalize();

        /*
         * A Java3D cylinder is created lying on the Y axis by default. The idea
         * here is to take the desired cylinder's orientation and perform a
         * tranformation on it to get it ONTO the Y axis. Then this
         * transformation matrix is inverted and used on a newly-instantiated
         * Java 3D cylinder.
         */

        // calculate vectors for rotation matrix
        // rotate object in any orientation, onto Y axis (exception handled
        // below)
        // (see page 418 of _Computer Graphics_ by Hearn and Baker)
        Vector3d uX = new Vector3d();
        Vector3d uY = new Vector3d();
        Vector3d uZ = new Vector3d();
        double magX;
        Transform3D rotateFix = new Transform3D();

        uY = new Vector3d(unit);
        uX.cross(unit, new Vector3d(0, 0, 1));
        magX = uX.length();
        // magX == 0 if object's axis is parallel to Z axis
        if (magX != 0) {
            uX.z = uX.z / magX;
            uX.x = uX.x / magX;
            uX.y = uX.y / magX;
            uZ.cross(uX, uY);
        } else {
            // formula doesn't work if object's axis is parallel to Z axis
            // so rotate object onto X axis first, then back to Y at end
            double magZ;
            // (switched z -> y, y -> x, x -> z from code above)
            uX = new Vector3d(unit);
            uZ.cross(unit, new Vector3d(0, 1, 0));
            magZ = uZ.length();
            uZ.x = uZ.x / magZ;
            uZ.y = uZ.y / magZ;
            uZ.z = uZ.z / magZ;
            uY.cross(uZ, uX);
            // rotate object 90 degrees CCW around Z axis--from X onto Y
            rotateFix.rotZ(Math.PI / 2.0);
        }

        // create the rotation matrix
        Transform3D transMatrix = new Transform3D();
        Transform3D rotateMatrix = new Transform3D(new Matrix4d(uX.x, uX.y,
                uX.z, 0, uY.x, uY.y, uY.z, 0, uZ.x, uZ.y, uZ.z, 0, 0, 0, 0, 1));
        // invert the matrix; need to rotate it off of the Z axis
        try {
            rotateMatrix.invert();
        } catch (Exception exc) {
            // exc.printStackTrace();
            return null;
        }
        // rotate the cylinder into correct orientation
        transMatrix.mul(rotateMatrix);
        transMatrix.mul(rotateFix);
        // translate the cylinder away
        transMatrix.setTranslation(center);

        return transMatrix;
    }

    public String toString() {
        return "edge coordinates: " + b.toString() + ", " + a.toString()
                + " radius: " + radius;
    }

    public static void setAllowIntersect(Cylinder cyl) {
        try {
            Enumeration enumer = cyl.getShape(Cylinder.BODY).getAllGeometries();
            while (enumer.hasMoreElements()) {
                ((Geometry) enumer.nextElement())
                        .setCapability(Geometry.ALLOW_INTERSECT);
            }
            enumer = cyl.getShape(Cylinder.TOP).getAllGeometries();
            while (enumer.hasMoreElements()) {
                ((Geometry) enumer.nextElement())
                        .setCapability(Geometry.ALLOW_INTERSECT);
            }
            enumer = cyl.getShape(Cylinder.BOTTOM).getAllGeometries();
            while (enumer.hasMoreElements()) {
                ((Geometry) enumer.nextElement())
                        .setCapability(Geometry.ALLOW_INTERSECT);
            }
        } catch (RestrictedAccessException rae) {
        }
    }

    public static void setAllowIntersect(Cone cone) {
        try {
            Enumeration enumer = cone.getShape(Cone.BODY).getAllGeometries();
            while (enumer.hasMoreElements()) {
                ((Geometry) enumer.nextElement())
                        .setCapability(Geometry.ALLOW_INTERSECT);
            }
            enumer = cone.getShape(Cone.CAP).getAllGeometries();
            while (enumer.hasMoreElements()) {
                ((Geometry) enumer.nextElement())
                        .setCapability(Geometry.ALLOW_INTERSECT);
            }
        } catch (RestrictedAccessException rae) {
        }
    }
}
