/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/07/04, 16:25
!  AUTHOR(S): KOGA, Junichiro
!  File : PartitionedCell.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.Vector;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import org.apache.log4j.Logger;

import ciss.phase_viewer.acviewer.geom.Plane;
import ciss.phase_viewer.acviewer.geom.Sorter;
import ciss.phase_viewer.atomcoord.VolumetricData;

/**
 * 分割されたセルのデータ構造
 * 
 * @author
 */
public class PartitionedCell {
    private Logger logger = Logger.getLogger(PartitionedCell.class.getName());

    private Point3f[] vertices = new Point3f[8]; /* セルの頂点; 8個あるはず */
    private float[] vertexValues = new float[8]; /* セルの頂点の'値'; 値のある場合. */

    private int[][] sides = { /* 「辺」の定義; 頂点ベクトルのインデックスの組二つ */
    { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, { 4, 5 }, { 5, 6 }, { 6, 7 },
            { 7, 4 }, { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 } };

    private int[][] planes = { /* 「面」の定義; 辺のインデックスx4で面 */
    { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 0, 8, 4, 9 }, { 1, 10, 5, 9 },
            { 2, 11, 6, 10 }, { 3, 11, 7, 8 } };

    private final static int sidePlaneTable[][] = { { 0, 2 }, { 0, 3 },
            { 0, 4 }, { 0, 5 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 },
            { 2, 5 }, { 2, 3 }, { 3, 4 }, { 4, 5 } };

    private float[][][][] coordinates; /*
                                        * 格子点の座標 ... 第一要素: a軸, 第二要素: b軸, 第三要素:
                                        * c軸, 第四要素: x,y,zのインデックス
                                        */
    private float[][][] value; /* 格子点の値 ... 第一要素: a軸, 第二要素: b軸, 第三要素: c軸のインデックス */

    public PartitionedCell(float[][][][] coordinates, float[][][] value) {
        this.coordinates = coordinates;
        this.value = value;
    }

    /**
     * 0番目の頂点の格子点のインデックスをセット; これを元に適切な準備を行う. 添え字チェックは行わない; 要注意
     * 
     * @param a
     *            a軸のインデックス
     * @param b
     *            b軸のインデックス
     * @param c
     *            c軸のインデックス
     * @return 無視すべきデータを見つけた場合false
     */
    public boolean setData(int a, int b, int c) {
        int a1 = a + 1;
        int b1 = b + 1;
        int c1 = c + 1;

        /* v0 */
        vertices[0] = new Point3f(coordinates[a][b][c]);

        /* v0+a */
        vertices[1] = new Point3f(coordinates[a1][b][c]);

        /* v0+a+b */
        vertices[2] = new Point3f(coordinates[a1][b1][c]);

        /* v0+b */
        vertices[3] = new Point3f(coordinates[a][b1][c]);

        /* v0+c */
        vertices[4] = new Point3f(coordinates[a][b][c1]);

        /* v0+a+c */
        vertices[5] = new Point3f(coordinates[a1][b][c1]);

        /* v0+a+b+c */
        vertices[6] = new Point3f(coordinates[a1][b1][c1]);

        /* v0+b+c */
        vertices[7] = new Point3f(coordinates[a][b1][c1]);

        /* pbc <-- これはデータ構造の方で処理することにした */
        /*
         * if ( a1 == value.length ) { a1 = 0; } if ( b1 == value[0].length ) {
         * b1 = 0; } if ( c1 == value[0][0].length ) { c1 = 0; }
         */
        // boolean foo = false;
        boolean foo = true;
        if (value != null) {
            vertexValues[0] = value[a][b][c];
            vertexValues[1] = value[a1][b][c];
            vertexValues[2] = value[a1][b1][c];
            vertexValues[3] = value[a][b1][c];
            vertexValues[4] = value[a][b][c1];
            vertexValues[5] = value[a1][b][c1];
            vertexValues[6] = value[a1][b1][c1];
            vertexValues[7] = value[a][b1][c1];
            for (int i = 0; i < vertexValues.length; i++) {
                if (vertexValues[i] != VolumetricData.meaninglessValue) {
                    foo = true;
                }
            }
        }

        return foo;
    }

    private Plane[] clippingPlane;
    private float[] origin = { 0f, 0f, 0f };

    /**
     * クリップする面の配列をセットする. たとえば逆空間ビューアーの場合, BZの境界面. セットされない場合, クリップ操作は行われない.
     * 
     * @param clippingPlane
     *            クリップする面の配列
     */
    public void setClippingPlane(Plane[] clippingPlane) {
        this.clippingPlane = clippingPlane;
    }

    /**
     * クリップする面の配列をセットする. たとえば逆空間ビューアーの場合, BZの境界面. セットされない場合, クリップ操作は行われない.
     * 
     * @param clippingPlane
     *            クリップする面の配列
     * @param origin
     *            系の"原点"
     */
    public void setClippingPlane(Plane[] clippingPlane, float[] origin) {
        this.clippingPlane = clippingPlane;
        this.origin = origin;
    }

    /**
     * このセルに, 指定の面が通るか否か判定
     * 
     * @param plane
     *            判定したい面
     * @return このセルを通る面の場合真.
     */
    private boolean planeCrosses(Plane plane) {
        Point3f origin = plane.getOrigin();
        Point3f norm = plane.getNormalVector();
        Vector3f normal = new Vector3f(norm);

        /* セル内の最も長い対角線を決める */
        float d = vertices[0].distance(vertices[6]);
        float dw = vertices[2].distance(vertices[4]);
        if (d < dw)
            d = dw;
        dw = vertices[3].distance(vertices[5]);
        if (d < dw)
            d = dw;
        dw = vertices[1].distance(vertices[7]);
        if (d < dw)
            d = dw;
        d = d / 2f;

        float cellCenterX = 0;
        for (int i = 0; i < 8; i++)
            cellCenterX = cellCenterX + vertices[i].x;
        cellCenterX = cellCenterX / 8f;
        float cellCenterY = 0;
        for (int i = 0; i < 8; i++)
            cellCenterY = cellCenterY + vertices[i].y;
        cellCenterY = cellCenterY / 8f;
        float cellCenterZ = 0;
        for (int i = 0; i < 8; i++)
            cellCenterZ = cellCenterZ + vertices[i].z;
        cellCenterZ = cellCenterZ / 8f;

        Vector3f p = new Vector3f(cellCenterX - origin.x, cellCenterY
                - origin.y, cellCenterZ - origin.z);
        float h = Math.abs(p.dot(normal));

        return (h < d) ? true : false;
    }

    private double epsilon = 0.00001;

    /**
     * 指定の面を横切る辺の座標と補間された値を計算し, Vectorに放り込む. 戻り値は, "横切った回数". 横切ら無い場合は-1を返す.
     * BioStationViewerのマネ.
     * 
     * @param plane
     *            判定したい面
     * @param valueVector
     *            値をaddしていくVector
     * @param posVector
     *            座標をaddしていくVector
     * @return 横切った回数; TriangleFanArrayの作成に役立つ.
     */
    public int doPlaneCross(Plane plane, Vector valueVector, Vector posVector) {
        if (!planeCrosses(plane)) {
            return -1;
        }
        int sizeh = 0;

        int crossed = 0;
        Vector tmppos = new Vector();
        Vector tmpval = new Vector();
        for (int i = 0; i < sides.length; i++) {
            int[] vertind = sides[i];

            Point3f p1 = vertices[vertind[0]];
            Point3f p2 = vertices[vertind[1]];
            float v1 = vertexValues[vertind[0]];
            float v2 = vertexValues[vertind[1]];

            float d1 = plane.getDistanceFrom(p1);
            float d2 = plane.getDistanceFrom(p2);
            // if ( v1==VolumetricData.meaninglessValue ||
            // v2==VolumetricData.meaninglessValue ) continue;
            if (d1 == 0 && d2 != 0) {
                tmppos.addElement(new float[] { p1.x, p1.y, p1.z });
                tmpval.addElement(new Float(v1));
            } else if (d2 == 0 && d1 != 0) {
                tmppos.addElement(new float[] { p2.x, p2.y, p2.z });
                tmpval.addElement(new Float(v2));
            } else if (d2 == 0 && d1 == 0) {
                tmppos.addElement(new float[] { p1.x, p1.y, p1.z });
                tmppos.addElement(new float[] { p2.x, p2.y, p2.z });
                tmpval.addElement(new Float(v1));
                tmpval.addElement(new Float(v2));
            } else if ((d1 < 0 && d2 > 0) || (d1 > 0 && d2 < 0)) { /* この場合辺に面が通る */
                float ad1 = Math.abs(d1);
                float ad2 = Math.abs(d2);
                float scale = ad1 / (ad1 + ad2);

                Point3f p3 = new Point3f();
                Point3f tmp = new Point3f();
                tmp.sub(p2, p1);
                p3.scaleAdd(scale, tmp, p1);
                tmppos.addElement(new float[] { p3.x, p3.y, p3.z });

                /* 線形補間; その内logも. */
                float v3 = v1 + scale * (v2 - v1);
                tmpval.addElement(new Float(v3));
            }
        }

        if (tmppos.size() < 3) {
            return -1;
        }
        Vector vec = Sorter.radiallyClockWise(tmppos, tmpval, false);
        if (vec == null)
            return -1;

        sizeh = vec.size() / 2;
        Vector tmpposvec = new Vector();
        Vector tmpvalvec = new Vector();
        for (int i = 0; i < sizeh; i++) {
            float[] p = (float[]) vec.elementAt(i);
            tmpposvec.addElement(p);
        }
        for (int i = 0; i < sizeh; i++) {
            Float fl = (Float) vec.elementAt(i + sizeh);
            tmpvalvec.addElement(fl);
        }

        // クリップ操作
        if (clippingPlane != null && clippingPlane.length != 0) {
            doClip(tmpposvec, tmpvalvec);
        }

        if (tmpposvec.size() < 3) {
            return -1;
        }

        for (int i = 0; i < tmpposvec.size(); i++) {
            posVector.addElement(new Point3f((float[]) tmpposvec.get(i)));
        }
        for (int i = 0; i < tmpvalvec.size(); i++) {
            valueVector.addElement(tmpvalvec.get(i));
        }

        return tmpposvec.size();
    }

    private void doClip(Vector posVector, Vector valueVector) {
        if (posVector.size() == 1)
            return;
        // Point3f [] points = new Point3f[posVector.size()];
        float[][] points = new float[posVector.size()][];
        Float[] vals = new Float[valueVector.size()];
        float dbg = 1f;
        posVector.copyInto(points);
        valueVector.copyInto(vals);
        boolean changed = false;
        Point3f genten = new Point3f(0f, 0f, 0f);
        Point3f orig = new Point3f(origin);
        for (int i = 0; i < points.length; i++) {
            Point3f p1 = new Point3f(points[i]);
            int secondIndex = i + 1;
            if (i == points.length - 1) {
                secondIndex = 0;
            }
            Point3f p2 = new Point3f(points[secondIndex]);

            int count1 = 0;
            int count2 = 0;
            float nearest1 = 10000f;
            float nearest2 = 10000f;
            Plane nearestPlane1 = null;
            Plane nearestPlane2 = null;
            int nearestPlaneIndex1 = -1;
            int nearestPlaneIndex2 = -1;
            for (int j = 0; j < clippingPlane.length; j++) {
                Plane pl = clippingPlane[j];
                float dg = pl.getDistanceFrom(genten);
                float d1 = pl.getDistanceFrom(p1);
                float d2 = pl.getDistanceFrom(p2);
                if (Math.abs(nearest1) > Math.abs(d1)) {
                    nearest1 = d1;
                    nearestPlane1 = pl;
                }
                if (Math.abs(nearest2) > Math.abs(d2)) {
                    nearest2 = d2;
                    nearestPlane2 = pl;
                }
                if (d1 > 0 && dg > 0 || d1 < 0 && dg < 0) {
                    count1++;
                }
                if (d2 > 0 && dg > 0 || d2 < 0 && dg < 0) {
                    count2++;
                }
            }

            nearest1 = nearestPlane1.getDistanceFrom(p1);
            nearest2 = nearestPlane2.getDistanceFrom(p2);

            if (count1 == clippingPlane.length
                    && count2 == clippingPlane.length)
                continue;

            if (count1 != clippingPlane.length
                    && count2 != clippingPlane.length) {
                changed = true;
                int ind1 = posVector.indexOf(points[i]);
                if (ind1 >= 0) {
                    posVector.remove(ind1);
                    valueVector.remove(ind1);
                }
                int ind2 = posVector.indexOf(points[secondIndex]);
                if (ind2 >= 0) {
                    posVector.remove(ind2);
                    valueVector.remove(ind2);
                }
                continue;
            }

            changed = true;
            if (count1 != clippingPlane.length) {
                int ind = posVector.indexOf(points[i]);
                if (ind >= 0) {
                    posVector.remove(ind);
                    valueVector.remove(ind);
                }
                Point3f newPoint = new Point3f();
                newPoint.sub(p1, p2);
                float scale = Math.abs(nearest2)
                        / (Math.abs(nearest1) + Math.abs(nearest2));
                // System.out.println("nearest 1 & 2 "+nearest1+", "+nearest2);
                newPoint.scale(scale);
                Point3f foo = new Point3f();
                foo.add(newPoint, p2);
                float newval = (vals[i].floatValue() - vals[secondIndex]
                        .floatValue()) * scale + vals[secondIndex].floatValue();
                posVector.add(new float[] { foo.x, foo.y, foo.z });
                valueVector.add(new Float(newval));
            } else if (count2 != clippingPlane.length) {
                int ind = posVector.indexOf(points[secondIndex]);
                if (ind >= 0) {
                    posVector.remove(ind);
                    valueVector.remove(ind);
                }
                Point3f newPoint = new Point3f();
                newPoint.sub(p2, p1);
                float scale = Math.abs(nearest1)
                        / (Math.abs(nearest1) + Math.abs(nearest2));
                newPoint.scale(scale);
                Point3f foo = new Point3f();
                foo.add(newPoint, p1);
                float newval = (vals[secondIndex].floatValue() - vals[i]
                        .floatValue()) * scale + vals[i].floatValue();
                posVector.add(new float[] { foo.x, foo.y, foo.z });
                valueVector.add(new Float(newval));
            }
        }

        if (!changed)
            return;

        Vector vec = Sorter.radiallyClockWise(posVector, valueVector, false);
        posVector.clear();
        valueVector.clear();
        if (vec == null)
            return;
        int sizeh = vec.size() / 2;
        for (int i = 0; i < sizeh; i++) {
            float[] p = (float[]) vec.elementAt(i);
            posVector.addElement(p);
        }
        for (int i = 0; i < sizeh; i++) {
            Float fl = (Float) vec.elementAt(i + sizeh);
            valueVector.addElement(fl);
        }
    }

}
