/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/06/29, 18:52
!  AUTHOR(S): KOGA, Junichiro
!  File : ColorBar.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.colormap;

import java.awt.Font;
import java.text.DecimalFormat;

import javax.media.j3d.BranchGroup;
import javax.media.j3d.QuadArray;
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.Point3f;
import javax.vecmath.Vector3f;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.J3DPanel;
import ciss.phase_viewer.acviewer.scenegraphelements.CapableText2D;
import ciss.phase_viewer.atomcoord.VolumetricData;
import ciss.phase_viewer.settings.GlobalProperties;
import ciss.phase_viewer.settings.PropertiesManager;

/**
 * 「カラーバー」を実現.
 * 
 * @author
 */
public class ColorBar extends BranchGroup {
    private Logger logger = Logger.getLogger(ColorBar.class.getName());
    private ColorMap map;
    private boolean invert = false;

    private float width = 0.1f;
    private float widthh = width * 0.5f;
    private float height = 0.6f;
    private Point3f center = new Point3f(-0.85f, -0.8f, 0.0f);

    private int fontSize = 12;
    private Color3f fontColor = new Color3f(0.f, 0.f, 0.6f);

    private Float minval;
    private Float maxval;
    private int division = 5;

    private DecimalFormat format = new DecimalFormat("0.00E0");

    /** 値のスケールを対数で行う場合modeとしてこれを指定 */
    private static int LOG_SCALE = VolumetricData.LOG;

    /** 値のスケールを線形に行う場合modeとしてこれを指定 */
    private static int LINEAR_SCALE = VolumetricData.LINEAR;

    private int mode = LOG_SCALE;

    private GlobalProperties gp = PropertiesManager
            .getGlobalProperties(PropertiesManager.PROPERTIES_ACV);
    private J3DPanel parent;

    private String title = ""; // タイトル

    /**
     * @param map
     *            カラーバーのレファランスデータを取得するのに必要.
     * @param invert
     *            カラーバーの表示を, 通常の逆にする場合はこの値をtrueとする
     */
    public ColorBar(ColorMap map, boolean invert, J3DPanel parent) {
        this.map = map;
        this.invert = invert;
        this.parent = parent;
        init();
    }

    /**
     * カラーバーの脇に最小値, 最大値などを表示するためのFloatを渡す.
     * 
     * @param map
     *            カラーバーのレファランスデータを取得するのに必要.
     * @param invert
     *            カラーバーの表示を, 通常の逆にする場合はこの値をtrueとする.
     * @param minval
     *            最小値
     * @param maxval
     *            最大値
     */
    public ColorBar(ColorMap map, boolean invert, Float minval, Float maxval,
            J3DPanel parent) {
        this.map = map;
        this.invert = invert;
        this.minval = minval;
        this.maxval = maxval;
        this.parent = parent;
        init();
    }

    /**
     * カラーバーの脇に最小値, 最大値などを表示するためのFloatを渡す. また, タイトルも渡せる
     * 
     * @param map
     *            カラーバーのレファランスデータを取得するのに必要.
     * @param invert
     *            カラーバーの表示を, 通常の逆にする場合はこの値をtrueとする.
     * @param minval
     *            最小値
     * @param maxval
     *            最大値
     * @param title
     *            タイトル
     */
    public ColorBar(ColorMap map, boolean invert, Float minval, Float maxval,
            J3DPanel parent, String title) {
        this.map = map;
        this.invert = invert;
        this.minval = minval;
        this.maxval = maxval;
        this.parent = parent;
        this.title = title;
        init();
    }

    /**
     * カラーバーの脇に最小値, 最大値などを表示するためのFloatを渡す. さらに, どれくらい分割するかも指定できる.
     * 
     * @param map
     *            カラーバーのレファランスデータを取得するのに必要.
     * @param invert
     *            カラーバーの表示を, 通常の逆にする場合はこの値をtrueとする.
     * @param minval
     *            最小値
     * @param maxval
     *            最大値
     * @param division
     *            スケール表示の分割数
     */
    public ColorBar(ColorMap map, boolean invert, Float minval, Float maxval,
            int division, J3DPanel parent) {
        this.map = map;
        this.invert = invert;
        this.minval = minval;
        this.maxval = maxval;
        this.division = division;
        this.parent = parent;
        init();
    }

    /**
     * カラーバーの脇に最小値, 最大値などを表示するためのFloatを渡す. どれくらい分割するかも指定できる. さらに,
     * 値のスケールの仕方をLOG_SCALEとLINEAR_SCALEで選べる. デフォルトはLOG_SCALE.
     * 
     * @param map
     *            カラーバーのレファランスデータを取得するのに必要.
     * @param invert
     *            カラーバーの表示を, 通常の逆にする場合はこの値をtrueとする.
     * @param minval
     *            最小値
     * @param maxval
     *            最大値
     * @param division
     *            スケール表示の分割数
     * @param mode
     *            スケールの仕方を指定
     */
    public ColorBar(ColorMap map, boolean invert, Float minval, Float maxval,
            int division, int mode, J3DPanel parent) {
        this.map = map;
        this.invert = invert;
        this.minval = minval;
        this.maxval = maxval;
        this.division = division;
        this.parent = parent;
        init();
    }

    private Shape3D shape3D;

    private void init() {
        setAttributes();

        setCapability(BranchGroup.ALLOW_DETACH);
        setCapability(BranchGroup.ALLOW_CHILDREN_READ);
        setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        shape3D = new Shape3D();
        shape3D.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
        addChild(shape3D);
        /*
         * QuadArray qarray = getQuadArray(); shape3D = new Shape3D(qarray);
         * shape3D.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
         * addChild(shape3D); if ( minval != null && maxval != null ) {
         * createScale(); }
         */
        /*
         * Canvas3D canv = parent.getCanvas(); BoundingSphere bounds=new
         * BoundingSphere(new Point3d(0.0,0.0,0.0),100.0); PickMouseBehavior
         * trans = new PickTranslateBehavior(this,canv,bounds); addChild(trans);
         */
    }

    /**
     * タイトルを設定する
     * 
     * @param title
     *            設定したい"タイトル"
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * 「場所」の設定
     * 
     * @param pos
     *            場所
     */
    public void setPos(Point3f center) {
        this.center = center;
    }

    /**
     * このカラーバーの場所を返す
     * 
     * @return このカラーバーの描かれている場所
     */
    public Point3f getPos() {
        return this.center;
    }

    /**
     * このカラーバーのタイトルを取得
     * 
     * @return このカラーバーのタイトル; nullを返すことはない, と思う.
     */
    public String getTitle() {
        return this.title;
    }

    /**
     * 値が線形に変化するのか対数的に変化するのかをセット. 変数は, VolumetricDataクラスのそれを利用せよ
     * 
     * @param 上記の通り
     */
    public void setInterpolationMode(int mode) {
        this.mode = mode;
    }

    /**
     * カラーバーを作成する
     */
    public void create() {
        this.doRecreate();
    }

    /**
     * 属性値などを再設定して再作成
     */
    public void recreate() {
        this.setAttributes();
        this.doRecreate();
    }

    private void setAttributes() {
        try {
            String[] pos = gp.getProperty("colorbar_position").split(",");
            center = new Point3f(Float.parseFloat(pos[0]),
                    Float.parseFloat(pos[1]), Float.parseFloat(pos[2]));
            height = Float.parseFloat(gp.getProperty("colorbar_height"));
            width = Float.parseFloat(gp.getProperty("colorbar_width"));
            widthh = width * 0.5f;
            fontSize = Integer.parseInt(gp
                    .getProperty("colorbar_scale_fontsize"));
            String[] color = gp.getProperty("colorbar_scale_fontcolor").split(
                    ",");
            fontColor = new Color3f(Float.parseFloat(color[0]),
                    Float.parseFloat(color[1]), Float.parseFloat(color[2]));
            division = Integer.parseInt(gp
                    .getProperty("colorbar_scale_division"));
            String formatString = gp.getProperty("colorbar_scale_format");
            format = new DecimalFormat(formatString);
        } catch (Exception exc) {
            logger.error("failed to set colorbar attributes.");
            exc.printStackTrace();
        }
    }

    public void recreate(ColorMap map, boolean invert, Float minval,
            Float maxval) {
        // if ( this.map == map && this.invert == invert &&
        // this.minval.equals(minval) && this.maxval.equals(maxval) ) {
        // return;
        // }
        this.map = map;
        this.invert = invert;
        this.minval = minval;
        this.maxval = maxval;
        doRecreate();
    }

    public void recreate(ColorMap map, boolean invert) {
        // if ( this.map == map && this.invert == invert &&
        // this.minval.equals(minval) && this.maxval.equals(maxval) ) {
        // return;
        // }
        this.map = map;
        this.invert = invert;
        doRecreate();
    }

    private void doRecreate() {
        // setAttributes();

        QuadArray qr = getQuadArray();
        try {
            qr.setCapability(QuadArray.ALLOW_INTERSECT);
        } catch (RestrictedAccessException rae) {
        }
        shape3D.setGeometry(qr);
        try {
            for (int i = 0; i < numChildren(); i++) {
                if (getChild(i) instanceof ScaleBG) {
                    ((ScaleBG) getChild(i)).detach();
                }
            }
        } catch (Exception exc) {
            exc.printStackTrace();
        }
        if (minval != null && maxval != null) {
            createScale();
        }
    }

    private QuadArray getQuadArray() {
        int resolution = map.getResolution();
        float delta = height / ((float) resolution) / 4.f;
        Point3f[] coordinates = new Point3f[resolution * 4];
        Color3f[] color = new Color3f[resolution * 4];
        for (int i = 0; i < resolution * 4; i += 4) {
            coordinates[i] = new Point3f(center.x - widthh, center.y + delta
                    * i, center.z);
            coordinates[i + 1] = new Point3f(center.x + widthh, center.y
                    + delta * i, center.z);
            coordinates[i + 2] = new Point3f(center.x + widthh, center.y
                    + delta * (i + 4), center.z);
            coordinates[i + 3] = new Point3f(center.x - widthh, center.y
                    + delta * (i + 4), center.z);
            float val = ((float) i) / (((float) resolution) * 4);
            color[i] = color[i + 1] = color[i + 2] = color[i + 3] = map
                    .getColor3f(val, invert);
        }
        QuadArray qarray = new QuadArray(coordinates.length,
                QuadArray.COORDINATES | QuadArray.COLOR_3);
        for (int i = 0; i < resolution * 4; i += 4) {
            Point3f[] p = new Point3f[] { coordinates[i], coordinates[i + 1],
                    coordinates[i + 2], coordinates[i + 3] };
            Color3f[] c = new Color3f[] { color[i], color[i + 1], color[i + 2],
                    color[i + 3] };
            qarray.setCoordinates(i, p);
            qarray.setColors(i, c);
        }
        return qarray;
    }

    private void createScale() {
        ScaleBG bg = new ScaleBG();
        if (minval == null && maxval == null) {
            return;
        }
        float min = minval.floatValue();
        float max = maxval.floatValue();
        if (mode == LOG_SCALE) {
            if (min <= 0) {
                min = 1.0f - 10;
            }
            if (max <= 0) {
                max = 2.0f - 10;
            }
            if (min > max) {
                float tmp = min;
                min = max;
                max = tmp;
            }
            if (min == max) {
                max += 1.0f - 10;
            }
        }
        CapableText2D mintext = new CapableText2D(format.format(min),
                fontColor, "TimesRoman", fontSize, Font.PLAIN);
        CapableText2D maxtext = new CapableText2D(format.format(max),
                fontColor, "TimesRoman", fontSize, Font.PLAIN);

        CapableText2D titletext = new CapableText2D(this.title, fontColor,
                "TimesRoman", fontSize, Font.PLAIN);

        Transform3D mintrans = new Transform3D();
        mintrans.setTranslation(new Vector3f(center.x + width * 0.5f,
                center.y - 0.05f, center.z));
        Transform3D maxtrans = new Transform3D();
        maxtrans.setTranslation(new Vector3f(center.x + width * 0.5f, center.y
                + height - 0.05f, center.z));

        Transform3D titletrans = new Transform3D();
        titletrans.setTranslation(new Vector3f(center.x - width * 0.5f,
                center.y + height + 0.05f, center.z));

        TransformGroup tgmin = new TransformGroup(mintrans);
        TransformGroup tgmax = new TransformGroup(maxtrans);
        TransformGroup tgtitle = new TransformGroup(titletrans);

        tgmin.addChild(mintext);
        tgmax.addChild(maxtext);
        tgtitle.addChild(titletext);

        bg.addChild(tgmin);
        bg.addChild(tgmax);
        bg.addChild(tgtitle);

        float div = height / ((float) division);
        float delta = 1.f / ((float) division);
        for (int i = 1; i < division; i++) {
            float yval = i * div;
            float scaledVal = i * delta;
            float origVal = 0f;
            if (mode == LOG_SCALE) {
                origVal = (float) BaseColorMap.getValueFromLogScaledValue(min,
                        max, scaledVal);
            } else if (mode == LINEAR_SCALE) {
                origVal = (float) BaseColorMap.getValueFromLinearScaledValue(
                        min, max, scaledVal);
            }
            logger.debug("origVal: " + origVal);
            CapableText2D text = new CapableText2D(format.format(origVal),
                    fontColor, "TimesRoman", fontSize, Font.PLAIN);
            Transform3D trans = new Transform3D();
            trans.setTranslation(new Vector3f(center.x + width * 0.5f, center.y
                    + yval - 0.05f, center.z));
            TransformGroup tg = new TransformGroup(trans);
            tg.addChild(text);
            bg.addChild(tg);
        }
        addChild(bg);
    }

    public String toString() {
        String ret = title;
        ret += " " + String.valueOf(minval) + " - " + String.valueOf(maxval);
        return ret;
    }

    class ScaleBG extends BranchGroup {
        public ScaleBG() {
            setCapability(BranchGroup.ALLOW_DETACH);
        }
    }

}
