/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/07/04, 15:56
!  AUTHOR(S): KOGA, Junichiro
!  File : Contour.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.text.DecimalFormat;
import java.util.Vector;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.RestrictedAccessException;
import javax.media.j3d.Shape3D;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.TriangleFanArray;
import javax.vecmath.Color3f;
import javax.vecmath.Point3f;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.ChaseTransformGroup;
import ciss.phase_viewer.acviewer.ConfigData;
import ciss.phase_viewer.acviewer.ConfigDataUpdateEvent;
import ciss.phase_viewer.acviewer.CoordsViewerInterface;
import ciss.phase_viewer.acviewer.J3DPanel;
import ciss.phase_viewer.acviewer.MainPanel;
import ciss.phase_viewer.acviewer.colormap.BaseColorMap;
import ciss.phase_viewer.acviewer.colormap.ColorBar;
import ciss.phase_viewer.acviewer.colormap.ColorMap;
import ciss.phase_viewer.acviewer.geom.Plane;
import ciss.phase_viewer.atomcoord.VolumetricData;
import ciss.phase_viewer.settings.GlobalProperties;
import ciss.phase_viewer.settings.PropertiesManager;

/**
 * 電荷密度のcontour.
 * 
 * @author
 */
public class Contour extends Plane implements ConfigData {
    private static Logger logger = Logger.getLogger(Contour.class.getName());
    private int ID;
    private boolean invert = false;

    private ColorBar colorBar;

    private J3DPanel parentPanel;

    private float[][] bounds;

    public Contour(J3DPanel parentPanel, VolumetricData vdata, double lenmax,
            double[] jusin) {
        this.parentPanel = parentPanel;
        this.vdata = vdata;
        this.lenmax = lenmax;
        this.jusin = jusin;
        init();
    }

    /**
     * このContourに関連付けられたColorBarを設定する
     * 
     * @param colorBar
     *            Contourに関連付けられたColorBar
     */
    public void setColorBar(ColorBar colorBar) {
        this.colorBar = colorBar;
    }

    /**
     * このContourに関連付けられたColorBarを取得する
     * 
     * @return このContourに関連付けられたColorBar
     */
    public ColorBar getColorBar() {
        return this.colorBar;
    }

    private int cID;

    /**
     * 使っているデータのIDを取得
     * 
     * @return 使っているデータのID
     */
    public int getChargeID() {
        return this.cID;
    }

    /**
     * 使っているデータのIDを設定
     * 
     * @param cID
     *            使っているデータのID
     */
    public void setChargeID(int cID) {
        this.cID = cID;
    }

    private boolean force = false;

    /**
     * ボリュームデータを設定.
     * 
     * @param 新しいボリュームデータ
     */
    public void setVolumetricData(VolumetricData vdata) {
        this.vdata = vdata;
        this.ndiv = vdata.getNumDiv();
        force = true;
    }

    public void forceCreation(boolean force) {
        this.force = force;
    }

    /**
     * 長さのスケールをセット
     * 
     * @param 長さのスケール
     */
    public void setLenMax(double lenmax) {
        this.lenmax = lenmax;
    }

    /**
     * 長さのスケールを取得
     * 
     * @return 長さのスケール
     */
    public double getLenMax() {
        return this.lenmax;
    }

    /**
     * 重心を取得
     * 
     * @return 重心
     */
    public double[] getCOM() {
        return this.jusin;
    }

    /**
     * 重心をセット
     * 
     * @param 重心
     */
    public void setCOM(double[] jusin) {
        this.jusin = jusin;
    }

    /**
     * IDをセットする.
     * 
     * @param このcontourのID
     *            .
     */
    public void setID(int ID) {
        this.ID = ID;
    }

    /**
     * 色を反転するかどうかを決める真偽値を設定
     * 
     * @param 色を反転させるならtrue
     */
    public void setInvert(boolean invert) {
        this.invert = invert;
    }

    /**
     * 色を反転するかどうかを決める真偽値を取得
     * 
     * @return 色を反転するならtrue
     */
    public boolean getInvert() {
        return this.invert;
    }

    /**
     * IDを取得する.
     * 
     * @return このcontourのID.
     */
    public int getID() {
        return this.ID;
    }

    /**
     * このcontour描画に利用される最大値を取得.
     * 
     * @return contour描画に利用される最大値
     */
    public Float getMaxVal() {
        return this.maxVal;
    }

    /**
     * このcontour描画に利用される最小値を取得.
     * 
     * @return contour描画に利用される最小値
     */
    public Float getMinVal() {
        return this.minVal;
    }

    private int[] ndiv;

    private void init() {
        origin.x += 0.00000013244f; /* セルの面と平面が同じだといまいちうまくいかないので原点をほんのちょっとずらす */
        origin.y += 0.00000028553f;
        origin.z += 0.00000034302f;
        origin_buff.x += 0.00000013244f;
        origin_buff.y += 0.00000028553f;
        origin_buff.z += 0.00000034302f;
        prop = PropertiesManager
                .getGlobalProperties(PropertiesManager.PROPERTIES_ACV);

        setCapability(BranchGroup.ALLOW_DETACH);
        setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);

        if (cmap == null) {
            cmap = BaseColorMap.getSupportedColorMaps()[0];
        }

        if (vdata == null) {
            logger.error("charge density not allocated");
            return;
        }

        ndiv = vdata.getNumDiv();
        if (ndiv == null || ndiv.length < 3) {
            logger.error("invalid ndiv");
            return;
        }

        bounds = ((ChaseTransformGroup) parentPanel.getRootTransform())
                .getEffectiveBounds();
        if (parentPanel instanceof MainPanel) {
            ((CoordsViewerInterface) parentPanel).getCD().register(this);
        }
    }

    private ColorMap cmap;

    /**
     * contour描画に利用されるカラーマップを設定.
     * 
     * @param cmap
     *            カラーマップ
     */
    public void setColorMap(ColorMap cmap) {
        this.cmap = cmap;
    }

    private float transparency = 0.f;

    /**
     * contourの透明度を設定
     * 
     * @param transparency
     *            contourの透明度
     */
    public void setTransparency(float transparency) {
        this.transparency = transparency;
    }

    /**
     * contourの透明度を取得
     * 
     * @return contourの透明度
     */
    public float getTransparency() {
        return this.transparency;
    }

    private int transparencyMode = TransparencyAttributes.SCREEN_DOOR;

    /**
     * 半透明表示のアルゴリズムを設定
     * 
     * @param transparencyMode
     *            半透明表示のアルゴリズム; TransparencyAttributesクラスのint値を利用する.
     */
    public void setTransparencyMode(int transparencyMode) {
        this.transparencyMode = transparencyMode;
    }

    /**
     * 半透明表示のアルゴリズムを取得
     * 
     * @return 半透明表示のアルゴリズム
     */
    public int getTransparencyMode() {
        return this.transparencyMode;
    }

    /**
     * 半透明表示のアルゴリズムを文字列で取得
     * 
     * @return 半透明表示のアルゴリズム; ただし文字列
     */
    public String getTransparencyModeInString() {
        if (transparencyMode == TransparencyAttributes.SCREEN_DOOR) {
            return "SCREEN_DOOR";
        } else if (transparencyMode == TransparencyAttributes.BLENDED) {
            return "BLENDED";
        } else {
            return "NONE";
        }
    }

    private Float minVal;
    private Float maxVal;
    private double lenmax = 1;
    private double[] jusin = new double[] { 0, 0, 0 };

    /**
     * contour描画の際に使われる最小値と最大値をセット.
     * 
     * @param minVal
     *            最小値
     * @param maxVal
     *            最大値
     */
    public void setMinMax(Float minVal, Float maxVal) {
        this.minVal = minVal;
        this.maxVal = maxVal;
    }

    private Plane[] planes;

    /**
     * クリップするための面を登録する. たとえばFBZの場合, その境界面
     * 
     * @param planes
     *            データをクリップする面
     */
    public void setClippingPlane(Plane[] planes) {
        this.planes = planes;
    }

    private GeometryArray createGeometry(Vector valVec) {
        float[] fjusin = new float[3];
        for (int i = 0; i < 3; i++)
            fjusin[i] = (float) jusin[i];
        float[][][][] coords = vdata.getCoordinates_explicitIndeces(
                (float) lenmax, fjusin);
        float[][][] vals = vdata.getValue_explicitIndeces();
        PartitionedCell cell = new PartitionedCell(coords, vals);
        if (planes != null) {
            float[] foo = vdata.getOrigin();
            cell.setClippingPlane(planes, foo);
        }

        Vector posVec = new Vector();
        Vector stripCountVec = new Vector();

        try {
            for (int i = 0; i < ndiv[2] - 1; i++) {
                for (int j = 0; j < ndiv[1] - 1; j++) {
                    for (int k = 0; k < ndiv[0] - 1; k++) {
                        if (!cell.setData(i, j, k))
                            continue;
                        int strCount = cell.doPlaneCross(this, valVec, posVec);
                        if (strCount >= 1) {
                            stripCountVec.addElement(new Integer(strCount));
                        }
                    }
                }
            }
        } catch (Exception exc) {
            exc.printStackTrace();
        }
        if (stripCountVec.size() == 0) {
            logger.info("no cell crosses the specified plane.");
            return null;
        }

        int[] strcount = new int[stripCountVec.size()];
        for (int i = 0; i < strcount.length; i++) {
            strcount[i] = ((Integer) stripCountVec.elementAt(i)).intValue();
        }
        TriangleFanArray farray = new TriangleFanArray(posVec.size(),
                TriangleFanArray.COORDINATES | TriangleFanArray.COLOR_3,
                strcount);
        // LineStripArray farray = new LineStripArray(posVec.size(),
        // TriangleFanArray.COORDINATES| TriangleFanArray.COLOR_3,strcount);
        // TriangleStripArray farray = new TriangleStripArray(posVec.size(),
        // TriangleFanArray.COORDINATES| TriangleFanArray.COLOR_3,strcount);
        farray.setCapability(TriangleFanArray.ALLOW_COLOR_WRITE);
        for (int i = 0; i < posVec.size(); i++) {
            Point3f p3 = (Point3f) posVec.get(i);
            farray.setCoordinate(i, p3);
        }
        try {
            farray.setCapability(TriangleFanArray.ALLOW_INTERSECT);
        } catch (RestrictedAccessException rae) {
        }
        // Point3f [] vertices = new Point3f[posVec.size()];
        // for ( int i=0 ; i<posVec.size() ; i++ ) {
        // vertices[i] = (Point3f) posVec.get(i);
        // }
        //
        // GeometryInfo ginfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
        // ginfo.setCoordinates(vertices);
        // ginfo.setStripCounts(strcount);
        // NormalGenerator gen = new NormalGenerator();
        // gen.generateNormals(ginfo);
        // GeometryArray farray = ginfo.getGeometryArray();
        //
        // farray.setCapability(GeometryArray.ALLOW_COLOR_WRITE);
        return farray;
    }

    private GlobalProperties prop;

    private void doColor() {
        float min = vdata.getMinVal();
        float max = vdata.getMaxVal();
        if (minVal == null) {
            minVal = new Float(min);
        }
        if (maxVal == null) {
            maxVal = new Float(max);
        }
        double minimum = (double) minVal.floatValue();
        double maximum = (double) maxVal.floatValue();
        Color3f[] colors = new Color3f[valVec.size()];
        for (int i = 0; i < valVec.size(); i++) {
            float val = ((Float) valVec.get(i)).floatValue();
            if (vdata.getInterpolationScheme() == VolumetricData.LOG) {
                colors[i] = cmap.getColor3f((float) BaseColorMap
                        .getLogScaledValue(minimum, maximum, (double) val),
                        invert);
            } else if (vdata.getInterpolationScheme() == VolumetricData.LINEAR) {
                colors[i] = cmap.getColor3f((float) BaseColorMap
                        .getLinearScaledValue(minimum, maximum, (double) val),
                        invert);
            }
        }
        if (fanarray != null)
            fanarray.setColors(0, colors);
        // for ( int i=0 ; i<valVec.size() ; i++ ) {
        // float val = ((Float) valVec.get(i)).floatValue();
        // if ( vdata.getInterpolationScheme() == VolumetricData.LOG ) {
        // Color3f c3f =
        // cmap.getColor3f((float)BaseColorMap.getLogScaledValue(minimum,maximum,(double)val),invert);
        // fanarray.setColor(i,c3f);
        // } else if ( vdata.getInterpolationScheme() == VolumetricData.LINEAR )
        // {
        // Color3f c3f =
        // cmap.getColor3f((float)BaseColorMap.getLinearScaledValue(minimum,maximum,(double)val),invert);
        // fanarray.setColor(i,c3f);
        // }
        // }
    }

    private void doAppearance() {
        if (app == null) {
            app = new Appearance();
            app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
            app.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE);
            app.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE);
        }

        if (tattr == null) {
            tattr = new TransparencyAttributes();
            tattr.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
            tattr.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE);
            app.setTransparencyAttributes(tattr);
        }

        if (pattr == null) {
            pattr = new PolygonAttributes();
            pattr.setCullFace(PolygonAttributes.CULL_NONE);
            pattr.setPolygonMode(PolygonAttributes.POLYGON_FILL);
            pattr.setBackFaceNormalFlip(true);
            app.setPolygonAttributes(pattr);
        }

        if (cattr == null) {
            cattr = new ColoringAttributes();
            cattr.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
            app.setColoringAttributes(cattr);
        }

        if (transparency == 0) {
            tattr.setTransparencyMode(TransparencyAttributes.NONE);
        } else {
            tattr.setTransparencyMode(transparencyMode);
        }
        tattr.setTransparency(transparency);
    }

    private VolumetricData vdata;
    private TransparencyAttributes tattr;
    private ColoringAttributes cattr;
    private PolygonAttributes pattr;
    private Appearance app;
    private Shape3D s3d;
    private GeometryArray fanarray;
    private Vector valVec = new Vector();
    private BranchGroup shape3dBranch;

    public void createContour() {
        valVec = new Vector();
        try {
            fanarray = createGeometry(valVec);
            if (fanarray == null) {
                return;
            }

            doColor();
            doAppearance();

            s3d = new Shape3D();
            s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
            s3d.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
            s3d.setAppearance(app);
            shape3dBranch = new BranchGroup();
            shape3dBranch.addChild(s3d);
            addChild(shape3dBranch);

            s3d.setGeometry(fanarray);
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    public void recreateContour() {
        if (!(origin.epsilonEquals(origin_buff, 0.001f) && normal
                .epsilonEquals(normal_buff, 0.000001f)) || force) {
            force = false;
            valVec = new Vector();
            fanarray = createGeometry(valVec);
            origin_buff = new Point3f(origin);
            normal_buff = new Point3f(normal);
            if (fanarray == null) {
                // s3d.setGeometry(null);
                return;
            }

            doColor();
            doAppearance();
            if (s3d == null) {
                s3d = new Shape3D();
                s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
                s3d.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
                s3d.setAppearance(app);
                shape3dBranch = new BranchGroup();
                shape3dBranch.addChild(s3d);
                addChild(shape3dBranch);
            }

            s3d.setGeometry(fanarray);
        } else {
            doColor();
            doAppearance();
        }
    }

    private DecimalFormat decform = new DecimalFormat("0.00");

    public String toString() {
        // String ret = "plane no. "+String.valueOf(this.ID);
        String ret = "";
        if (minVal != null && maxVal != null) {
            ret += "no. " + String.valueOf(ID) + ", norm: "
                    + decform.format(normal.x) + " " + decform.format(normal.y)
                    + " " + decform.format(normal.z);
            ret += ", orig: " + decform.format(origin.x) + " "
                    + decform.format(origin.y) + " " + decform.format(origin.z);
        }
        return ret;
    }

    public void setOrigin(Point3f origin) {
        super.setOrigin(origin);
        origin.x += 0.00000013244f; /* セルの面と平面が同じだといまいちうまくいかないので原点をほんのちょっとずらす */
        origin.y += 0.00000028553f;
        origin.z += 0.00000034302f;
    }

    //
    // public void setNormalVector(Point3f normal) {
    // super.setNormalVector(normal);
    // // normal.z += 0.0001f;
    // }

    public void configDataUpdate() {
    }

    public void configDataUpdate(boolean rescaleOnUpdate,
            ConfigDataUpdateEvent e) {
        if (e.getType() == ConfigDataUpdateEvent.CELL_CHANGED) {
            force = true;
            setVolumetricData(this.vdata);
            this.jusin = ((TGAtom) parentPanel.getRootTransform()).getJusin();
            this.lenmax = ((TGAtom) parentPanel.getRootTransform()).getLenMax();
            detach();
            recreateContour();
            parentPanel.getRootTransform().addChild(this);
            this.bounds = ((ChaseTransformGroup) parentPanel.getRootTransform())
                    .getEffectiveBounds();
        }
    }

    public boolean needsUpdate() {
        return true;
    }

}
