/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/07/05, 20:11
!  AUTHOR(S): KOGA, Junichiro
!  File : Sorter.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.geom;

import java.util.Arrays;
import java.util.Vector;

import javax.media.j3d.Transform3D;
import javax.vecmath.Point3f;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.VectorOperations;

/**
 * ソート用メソッドを色々付け足そう. たぶんそんなには出来ないけど...
 * 
 * @author
 */
public class Sorter {
    private static Logger logger = Logger.getLogger(Sorter.class.getName());

    private Sorter() {
    }

    /**
     * 指定の点を,「radialな意味で時計回り」にソートする. あまりチェックはしないので"正しい"データを渡すこと!!!
     * 
     * @param data
     *            ソートしたいPoint3f配列
     * @return 引数のデータが, 時計回りにソートされたもの.
     */
    public static Point3f[] radiallyClockWise(Point3f[] data) {
        if (data == null) {
            return null;
        }
        Vector vec = new Vector();
        for (int i = 0; i < data.length; i++) {
            float[] fld = new float[] { data[i].x, data[i].y, data[i].z };
            vec.addElement(fld);
        }
        Vector retvec = radiallyClockWise(vec, null);
        float[][] fret = new float[retvec.size()][];
        retvec.copyInto(fret);
        Point3f[] pret = new Point3f[fret.length];
        for (int i = 0; i < fret.length; i++) {
            pret[i] = new Point3f(fret[i]);
        }
        return pret;
    }

    /**
     * 「radialな意味で時計回り」にソートする. 渡すデータは, float配列の集まりであるとする.
     * あまりチェックはしないので"正しい"データを渡すこと!!! あと, 三次元以外では動かない.
     * 
     * @param data
     *            ソートしたいfloat配列のVector
     * @param associatedData
     *            付随するデータ
     * @return 引数のデータが, 時計回りにソートされたもの. うまくいかない場合はnullを返す.
     */
    public static Vector radiallyClockWise(Vector data, Vector associatedData) {
        return radiallyClockWise(data, associatedData, false);
    }

    /**
     * 「radialな意味で時計回り」にソートする. 反時計周りにもできる. 渡すデータは, float配列の集まりであるとする.
     * あまりチェックはしないので"正しい"データを渡すこと!!! あと, 三次元以外では動かない.
     * 
     * @param data
     *            ソートしたいfloat配列のVector
     * @param associatedData
     *            付随するデータ. null可.
     * @param counterClockWise
     *            反時計まわりにしたい場合true.
     * @return 前半は, 引数のデータが, 時計回りにソートされたもの. 後半は「付随したデータ」うまくいかない場合はnullを返す.
     *         asoociatedDataとしてnullを渡した場合, 前半部のみ.
     */
    public static Vector radiallyClockWise(Vector data, Vector associatedData,
            boolean counterClockWise) {
        float verySmall = 0.000001f;
        float small = 0.001f;

        if (data == null || data.size() <= 2) {
            return null;
        }

        Object[] fdata = new Object[data.size()];
        data.copyInto(fdata);
        float[] f0 = ((float[]) fdata[0]);
        int dim = f0.length;

        Object[] assobj = null;
        if (associatedData != null && associatedData.size() != 0) {
            assobj = new Object[associatedData.size()];
            associatedData.copyInto(assobj);
        }

        float[][] buff = new float[data.size()][f0.length];
        for (int i = 0; i < buff.length; i++) {
            for (int j = 0; j < dim; j++) {
                buff[i][j] = 0.0f;
            }
        }

        for (int i = 0; i < data.size(); i++) {
            float[] fld = (float[]) data.get(i);
            for (int j = 0; j < dim; j++) {
                buff[i][j] = fld[j];
            }
        }

        int ind1 = 0;
        int ind2 = 1;
        boolean xissame = true;
        boolean yissame = true;
        boolean zissame = true;
        for (int i = 0; i < fdata.length - 1; i++) {
            float[] flidat = (float[]) fdata[i];
            for (int j = i + 1; j < fdata.length; j++) {
                float[] fljdat = (float[]) fdata[j];

                for (int k = 0; k < fljdat.length; k++) {
                    if (Math.abs(flidat[0] - fljdat[0]) > verySmall) {
                        xissame = false;
                    }
                    if (Math.abs(flidat[1] - fljdat[1]) > verySmall) {
                        yissame = false;
                    }
                    if (Math.abs(flidat[2] - fljdat[2]) > verySmall) {
                        zissame = false;
                    }
                }
            }
        }
        //
        // if ( xissame ) {/*xの値がすべて等しい場合投影する面を変える*/
        // ind1=1;
        // ind2=2;
        // } else if ( yissame ) { /*yの値がすべて等しい場合投影する面を変える*/
        // ind1=0;
        // ind2=2;
        // }
        //
        // //buffを参照からコピーへ切り替える
        // for ( int i=0 ; i<buff.length ; i++ ) {
        // buff[i] = new float[]{buff[i][0],buff[i][1],buff[i][2]};
        // }
        // Transform3D t3d = new Transform3D();
        //
        // // t3d.rotZ(Math.PI/2);
        // // t3d.rotY(Math.PI/4.6);
        // // t3d.rotZ(Math.PI/8.8);
        // for ( int i=0 ; i<buff.length ; i++ ) {
        // Point3f p = new Point3f(buff[i][0],buff[i][1],buff[i][2]);
        // Point3f out = new Point3f();
        // t3d.transform(p,out);
        // buff[i][0] = out.x;
        // buff[i][1] = out.y;
        // buff[i][2] = out.z;
        // }
        //
        /* z軸に平行な場合少し傾ける */
        float[] f1 = (float[]) fdata[1];
        float[] nor = VectorOperations.getNormalVector(f0, f1);
        // if ( Math.abs(VectorOperations.dotProduct(nor,new float[]{0,0,1})) <
        // small ) {
        // buffを参照からコピーへ切り替える
        // for ( int i=0 ; i<buff.length ; i++ ) {
        // buff[i] = new float[]{buff[i][0],buff[i][1],buff[i][2]};
        // }
        Transform3D t3d = new Transform3D();
        t3d.rotX(Math.PI / 6.0);
        t3d.rotY(Math.PI / 6.0);
        for (int i = 0; i < buff.length; i++) {
            Point3f p = new Point3f(buff[i][0], buff[i][1], buff[i][2]);
            Point3f out = new Point3f();
            t3d.transform(p, out);
            buff[i][0] = out.x;
            buff[i][1] = out.y;
            buff[i][2] = out.z;
        }
        // ind1=0;
        // ind2=2;
        // }

        /* xの値がすべて等しい場合投影する面を変える */
        if (xissame) {
            ind1 = 1;
            ind2 = 2;
        }

        /* yの値がすべて等しい場合投影する面を変える */
        if (yissame) {
            ind1 = 0;
            ind2 = 2;
        }

        /* 重心ゲット */
        float[] origin = new float[dim];
        for (int i = 0; i < dim; i++) {
            origin[i] = 0f;
        }
        for (int i = 0; i < buff.length; i++) {
            for (int j = 0; j < dim; j++) {
                origin[j] += buff[i][j];
            }
        }
        for (int i = 0; i < dim; i++) {
            origin[i] /= (float) data.size();
        }

        /* 参照用ベクトルをゲット */
        float[] ref = new float[dim];
        for (int i = 0; i < ref.length; i++) {
            ref[i] = buff[0][i] - origin[i];
        }
        float norm0 = VectorOperations.norm(ref);
        if (norm0 == 0) {
            for (int i = 0; i < ref.length; i++) {
                ref[i] += verySmall;
            }
            norm0 = VectorOperations.norm(ref);
        }

        /* 角度の比較 */
        AngComp[] comp = new AngComp[data.size()];
        Object obj = null;
        if (assobj != null) {
            obj = assobj[0];
        }
        comp[0] = new AngComp(f0, obj, 0f, counterClockWise);
        for (int i = 1; i < buff.length; i++) {
            float[] refi = new float[dim];
            for (int j = 0; j < dim; j++) {
                refi[j] = buff[i][j] - origin[j];
            }
            float normi = VectorOperations.norm(refi);
            if (normi == 0) {
                // for ( int j=0 ; j<refi.length ; j++ ) {
                // refi[i] += verySmall;
                // }
                // normi = VectorOperations.norm(refi);
                normi = 0.00000001f;
            }
            float cosine = VectorOperations.dotProduct(refi, ref)
                    / (normi * norm0);
            if (cosine > 1) {
                cosine = 1;
            }
            if (cosine < -1) {
                cosine = -1;
            }
            float ang = (float) Math.acos((double) cosine);
            float foo = ref[ind1] * refi[ind2] - refi[ind1] * ref[ind2]; /*
                                                                          * 180
                                                                          * 度以上の場合この値は負になる
                                                                          */
            if (foo < 0) {
                ang = (float) (2 * Math.PI) - ang;
            }
            float[] fldata = (float[]) data.get(i);
            Object obje = null;
            if (assobj != null) {
                obje = assobj[i];
            }
            comp[i] = new AngComp(fldata, obje, ang, counterClockWise);
        }
        Arrays.sort(comp);

        Vector retvec = new Vector();
        for (int i = 0; i < comp.length; i++) {
            retvec.addElement(comp[i].getVal());
            logger.debug("i, ang: " + i + "," + comp[i].getAngle()
                    * ((float) 180 / Math.PI));
        }
        if (assobj != null) {
            for (int i = 0; i < comp.length; i++) {
                retvec.addElement(comp[i].getAssociatedData());
            }
        }
        return retvec;
    }

}

class AngComp implements Comparable {
    private float angle;
    private float[] val;
    private Object associatedData;
    private boolean counterClockWise = false;

    AngComp(float[] val, Object associatedData, float angle) {
        this.val = val;
        this.associatedData = associatedData;
        this.angle = angle;
    }

    AngComp(float[] val, Object associatedData, float angle,
            boolean counterClockWise) {
        this.val = val;
        this.angle = angle;
        this.associatedData = associatedData;
        this.counterClockWise = counterClockWise;
    }

    public int compareTo(Object o) {
        if (((AngComp) o).angle > this.angle) {
            if (counterClockWise) {
                return 1;
            } else {
                return -1;
            }
        } else if (((AngComp) o).angle < this.angle) {
            if (counterClockWise) {
                return -1;
            } else {
                return 1;
            }
        } else {
            return 0;
        }
    }

    float[] getVal() {
        return this.val;
    }

    Object getAssociatedData() {
        return associatedData;
    }

    float getAngle() {
        return this.angle;
    }

    public boolean equals(Object obj) {
        if (obj instanceof AngComp && ((AngComp) obj).angle == this.angle) {
            return true;
        }
        return false;
    }

}
