/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : Cell.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.atomcoord;

import java.text.DecimalFormat;

import org.apache.log4j.Logger;
import org.jdom.Element;

import ciss.phase_viewer.common.ConstParameters;
import ciss.phase_viewer.common.VectorOperations;
import ciss.phase_viewer.inputinterface.InputInterface;

/**
 * 「セル」を表すクラス. セルは,
 * {{ax,ay,az},{bx,by,bz},{cx,cy,cz}}と3x3の配列で表記する場合と{{a,b,c},{alpha
 * ,beta,gamma}}という2x3の配列で表す, 二つのケースがある. 前者の場合getCellScheme == CELL_VEC,
 * 後者の場合getCellScheme == CELL_ABCとなる.
 * 
 * @author KOGA, Junichiro
 */
public class Cell {

    private static Logger logger = Logger.getLogger(Cell.class.getName());

    public static final int CELL_VEC = 0;

    public static final int CELL_ABC = 1;

    private int cellScheme = CELL_VEC;

    private String[][] cell;

    private double[] cellOffset = new double[] { 0, 0, 0 };

    private DecimalFormat dformat = new DecimalFormat("#.##########");

    /**
     * このオブジェクトが表すcellの配列と, セルの表示方法を指定するコンストラクター
     * 
     * @param cell
     *            セルの座標を格納した配列
     * @param cellScheme
     *            セルの表記方法; CELL_VECかCELL_ABCを渡す.
     */
    public Cell(String[][] cell, int cellScheme) {
        this.cell = cell;
        this.cellScheme = cellScheme;
    }

    /**
     * CELL_ABCの場合のコンストラクター. 引数は, セルの各成分
     * 
     * @param a
     * @param b
     * @param c
     * @param alpha
     * @param beta
     * @param gamma
     */
    public Cell(String a, String b, String c, String alpha, String beta,
            String gamma) {
        this.cellScheme = CELL_ABC;
        cell = new String[2][3];
        cell[0][0] = a;
        cell[0][1] = b;
        cell[0][2] = c;
        cell[1][0] = alpha;
        cell[1][1] = beta;
        cell[1][2] = gamma;
    }

    /**
     * CELL_VECの場合のコンストラクター
     * 
     * @param a
     *            a軸の配列
     * @param b
     *            b軸の配列
     * @param c
     *            c軸の配列
     */
    public Cell(String[] a, String[] b, String[] c) {
        this.cellScheme = CELL_VEC;
        cell = new String[3][3];
        cell[0] = a;
        cell[1] = b;
        cell[2] = c;
    }

    /**
     * CELL_ABCの場合のコンストラクター; double で指定
     * 
     * @param a
     * @param b
     * @param c
     * @param alpha
     * @param beta
     * @param gamma
     */
    public Cell(double a, double b, double c, double alpha, double beta,
            double gamma) {
        this.cellScheme = CELL_ABC;
        cell = new String[2][3];
        cell[0][0] = Double.toString(a);
        cell[0][1] = Double.toString(b);
        cell[0][2] = Double.toString(c);
        cell[1][0] = Double.toString(alpha);
        cell[1][1] = Double.toString(beta);
        cell[1][2] = Double.toString(gamma);
    }

    /**
     * CELL_VECの場合のコンストラクター; doubleで指定.
     * 
     * @param a
     *            a軸のベクトル
     * @param b
     *            b軸のベクトル
     * @param c
     *            c軸のベクトル
     */
    public Cell(double[] a, double[] b, double[] c) {
        this.cellScheme = CELL_VEC;
        cell = new String[3][3];
        for (int i = 0; i < 3; i++) {
            cell[0][i] = Double.toString(a[i]);
            cell[1][i] = Double.toString(b[i]);
            cell[2][i] = Double.toString(c[i]);
        }
    }

    /**
     * 新しいインスタンスを作成
     * 
     * @return 新しいCellオブジェクト
     */
    public Cell getCopy() {
        return new Cell(cell, cellScheme);
    }

    /**
     * セルの表記方法を返す.
     * 
     * @return CELL_VECないしCELL_ABC
     */
    public int getCellScheme() {
        return this.cellScheme;
    }

    /**
     * セルの配列を返す.
     * 
     * @return セルを表す配列
     */
    public String[][] getCell() {
        return this.cell;
    }

    /**
     * セルを, double配列として返す. doubleに出来なかった場合nullを返す
     * 
     * @param doubleで表記されたセル
     */
    public double[][] getDouble() {
        int irow = 3;
        if (cellScheme == CELL_ABC) {
            irow = 2;
        }

        double[][] ret = new double[irow][3];

        for (int i = 0; i < irow; i++) {
            for (int j = 0; j < 3; j++) {
                try {
                    ret[i][j] = Double.parseDouble(cell[i][j]);
                } catch (NumberFormatException nfe) {
                    logger.warn("failed to get a 'double' representation of cell");
                    return null;
                }
            }
        }

        return ret;
    }

    /**
     * このCellから逆格子ベクトルを作成する.
     * 
     * @return 逆格子ベクトル; 第一要素が逆格子ベクトルのどれか, 第二要素がx,y,z
     */
    public double[][] getReciprocalLattice() {
        double[][] dcell = getDouble();
        double[][] cellVec = null;

        if (dcell == null) {
            return null;
        }

        if (dcell.length < 2) {
            return null;
        }

        if (dcell.length == 2) {
            double[] cellLength = dcell[0];
            double alpha = dcell[1][0] * ConstParameters.ANG_TO_RAD;
            double beta = dcell[1][1] * ConstParameters.ANG_TO_RAD;
            double gamma = dcell[1][2] * ConstParameters.ANG_TO_RAD;
            double temp = 0.;
            cellVec = new double[3][3];

            for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
                    cellVec[i][j] = 0.;
                }
            }
            cellVec[0][0] = 1.;
            cellVec[1][0] = Math.cos(gamma);
            cellVec[2][0] = Math.cos(beta);
            cellVec[1][1] = Math.sin(gamma);
            temp = (Math.cos(alpha) - Math.cos(beta) * Math.cos(gamma))
                    / Math.sin(gamma);
            cellVec[2][1] = temp;
            cellVec[2][2] = Math.sqrt(Math.sin(beta) * Math.sin(beta) - temp
                    * temp);
            for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j) {
                    cellVec[j][i] *= cellLength[j];
                }
            }
        } else if (dcell.length == 3) {
            cellVec = dcell;
        }

        if (cellVec == null) {
            return null;
        }

        double[] b_cross_c = VectorOperations.crossProduct(cellVec[1],
                cellVec[2]);
        double[] c_cross_a = VectorOperations.crossProduct(cellVec[2],
                cellVec[0]);
        double[] a_cross_b = VectorOperations.crossProduct(cellVec[0],
                cellVec[1]);
        double volume = VectorOperations.dotProduct(cellVec[0], b_cross_c);
        if (volume == 0) {
            return null;
        }
        double twoPI = 2 * Math.PI;
        double factor = 2 * Math.PI / volume;
        for (int i = 0; i < 3; i++) {
            b_cross_c[i] *= factor;
            c_cross_a[i] *= factor;
            a_cross_b[i] *= factor;
        }
        return new double[][] { b_cross_c, c_cross_a, a_cross_b };
    }

    /**
     * セルをどのくらいずらすかをセット
     * 
     * @param x
     *            ,y,z方向にセルをいかほどずらすか
     */
    public void setCellOffset(double[] offset) {
        this.cellOffset = offset;
    }

    /**
     * セルをどのくらいずらすかを取得
     * 
     * @return 上記の通り
     */
    public double[] getCellOffset() {
        return this.cellOffset;
    }

    /**
     * セルを, XMLエレメントに変換
     * 
     * @return 変換されたXMLエレメント
     */
    public Element getXMLElement() {
        Element element = new Element("cell");
        if (cellScheme == CELL_VEC) {
            Element a = new Element("a_vec").setText(cell[0][0] + " "
                    + cell[0][1] + " " + cell[0][2]);
            Element b = new Element("b_vec").setText(cell[1][0] + " "
                    + cell[1][1] + " " + cell[1][2]);
            Element c = new Element("c_vec").setText(cell[2][0] + " "
                    + cell[2][1] + " " + cell[2][2]);
            element.addContent(a);
            element.addContent(b);
            element.addContent(c);
        } else {
            Element a = new Element("a").setText(cell[0][0]);
            Element b = new Element("b").setText(cell[0][1]);
            Element c = new Element("c").setText(cell[0][2]);
            Element alpha = new Element("alpha").setText(cell[1][0]);
            Element beta = new Element("beta").setText(cell[1][1]);
            Element gamma = new Element("gamma").setText(cell[1][2]);
            element.addContent(a);
            element.addContent(b);
            element.addContent(c);
            element.addContent(alpha);
            element.addContent(beta);
            element.addContent(gamma);
        }
        return element;
    }

    /**
     * 指定のXMLエレメントからこのクラスのインスタンスを作成
     * 
     * @param elem
     *            作成したいインスタンスの情報が記述されたXMLエレメント
     * @return 結果作成されたCellオブジェクト
     */
    public static Cell[] createFrom(Element elem) {
        Cell[] ret = new Cell[2];
        // vec
        String avec = elem.getChildTextTrim("a_vec");
        String bvec = elem.getChildTextTrim("b_vec");
        String cvec = elem.getChildTextTrim("c_vec");
        if (avec != null && bvec != null && cvec != null) {
            String[] aarray = avec.split("\\s+");
            String[] barray = bvec.split("\\s+");
            String[] carray = cvec.split("\\s+");
            if (aarray != null && aarray.length >= 3 && barray != null
                    && barray.length >= 3 && carray != null
                    && carray.length >= 3) {
                ret[0] = new Cell(aarray, barray, carray);
            }
        }

        // a,b,c,alpha,beta,gamma
        String a = elem.getChildTextTrim("a");
        String b = elem.getChildTextTrim("b");
        String c = elem.getChildTextTrim("c");
        String alpha = elem.getChildTextTrim("alpha");
        String beta = elem.getChildTextTrim("beta");
        String gamma = elem.getChildTextTrim("gamma");
        if (a != null && a.length() != 0 && b != null && b.length() != 0
                && c != null && c.length() != 0 && alpha != null
                && alpha.length() != 0 && beta != null && beta.length() != 0
                && gamma != null && gamma.length() != 0) {
            ret[1] = new Cell(a, b, c, alpha, beta, gamma);
        }
        return ret;
    }

    /**
     * PHASEのInputInterfaceオブジェクトからセルベクトルの部分を抜き出してこのクラスのインスタンスを作成する.
     * うまくいかない場合はnullを返す.
     * 
     * @param inputInterface
     * @return inputInterfaceから作成したCellオブジェクト. 上手くいかない場合null.
     */
    public static Cell createFrom(InputInterface inputInterface) {
        String avec = inputInterface.getInputInterfacePrimitiveEntry(
                "structure.unit_cell.a_vector").getValue();
        String a = inputInterface.getInputInterfacePrimitiveEntry(
                "structure.unit_cell.a").getValue();
        if (avec.trim().length() != 0) {
            String bvec = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.b_vector").getValue();
            String cvec = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.c_vector").getValue();
            try {
                String[] av = avec.split("\\s+");
                String[] bv = bvec.split("\\s+");
                String[] cv = cvec.split("\\s+");
                double[] dav = new double[] { Double.parseDouble(av[0]),
                        Double.parseDouble(av[1]), Double.parseDouble(av[2]) };
                double[] dbv = new double[] { Double.parseDouble(bv[0]),
                        Double.parseDouble(bv[1]), Double.parseDouble(bv[2]) };
                double[] dcv = new double[] { Double.parseDouble(cv[0]),
                        Double.parseDouble(cv[1]), Double.parseDouble(cv[2]) };
                return new Cell(dav, dbv, dcv);
            } catch (NumberFormatException nfe) {
                return null;
            } catch (ArrayIndexOutOfBoundsException ai) {
                return null;
            }
        } else if (a.trim().length() != 0) {
            String b = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.b").getValue();
            String c = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.c").getValue();
            String alpha = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.alpha").getValue();
            String beta = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.beta").getValue();
            String gamma = inputInterface.getInputInterfacePrimitiveEntry(
                    "structure.unit_cell.gamma").getValue();
            try {
                return new Cell(Double.parseDouble(a), Double.parseDouble(b),
                        Double.parseDouble(c), Double.parseDouble(alpha),
                        Double.parseDouble(beta), Double.parseDouble(gamma));
            } catch (NumberFormatException nfe) {
                return null;
            }
        }

        return null;
    }

    public String toString() {
        String[][] ce = getCell();
        String ret = "";
        for (int i = 0; i < ce.length; i++) {
            for (int j = 0; j < ce[i].length; j++) {
                if (ce[i][j] != null)
                    ret += " " + ce[i][j] + " ";
            }
            ret += System.getProperty("line.separator");
        }
        return ret;
    }
}
