/*
 !=======================================================================
 !
 !  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
 !
 !  Created on 2006/10/11, 20:10
 !  AUTHOR(S): KOGA, Junichiro
 !
 !  The license of the code and contact address:
 !  See the files, COPYRIGHT and LICENSE (or LICENSE_J.pdf)
 !
 !=======================================================================
 */

package ciss.phase_viewer.common;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JOptionPane;

import org.apache.log4j.Logger;

import ciss.phase_viewer.atomcoord.Atom;
import ciss.phase_viewer.atomcoord.AtomCoords;
import ciss.phase_viewer.atomcoord.AtomList;
import ciss.phase_viewer.atomcoord.Cell;
import ciss.phase_viewer.atomcoord.symmetry.BravaisLattice;
import ciss.phase_viewer.inputinterface.InputInterface;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTable;

/**
 * PHASE操作に便利なstaticメソッドを.
 * 
 * @author
 */
public class PhaseUtils {
    private static Logger logger = Logger.getLogger(PhaseUtils.class.getName());

    /** Creates a new instance of PhaseUtils */
    private PhaseUtils() {
    }

    /**
     * 指定のInputInterfaceオブジェクトからセルベクトルを読み込み, Cellオブジェクトとして返す. a_vector,
     * b_vector, c_vector形式のデータを返す. また, データが読み込めない場合nullを返す.
     * 
     * @param inputInterface
     *            このオブジェクトからデータを読み込む
     * @param セルベクトル
     */
    public static Cell getCellFrom(InputInterface inputInterface) {
        inputInterface.selectRoot();
        inputInterface.selectBlock("structure.unit_cell");
        String avec = inputInterface.getPrimitiveEntry("a_vector").getValue();
        String bvec = inputInterface.getPrimitiveEntry("b_vector").getValue();
        String cvec = inputInterface.getPrimitiveEntry("c_vector").getValue();
        String a = "";
        String b = "";
        String c = "";
        String alpha = "";
        String beta = "";
        String gamma = "";
        if (avec.trim().length() == 0) {
            a = inputInterface.getPrimitiveEntry("a").getValue();
            b = inputInterface.getPrimitiveEntry("b").getValue();
            c = inputInterface.getPrimitiveEntry("c").getValue();
            alpha = inputInterface.getPrimitiveEntry("alpha").getValue();
            beta = inputInterface.getPrimitiveEntry("beta").getValue();
            gamma = inputInterface.getPrimitiveEntry("gamma").getValue();
        }

        inputInterface.selectRoot();

        if (avec.trim().length() != 0) {
            try {
                String[] av = avec.split("\\s+");
                String[] bv = bvec.split("\\s+");
                String[] cv = cvec.split("\\s+");
                double[][] tmp = new double[3][3];
                for (int i = 0; i < 3; i++) {
                    tmp[0][i] = Double.parseDouble(av[i]);
                    tmp[1][i] = Double.parseDouble(bv[i]);
                    tmp[2][i] = Double.parseDouble(cv[i]);
                }
                Cell cel = new Cell(tmp[0], tmp[1], tmp[2]);
                return cel;
            } catch (Exception exc) {
                return null;
            }
        } else {
            try {
                double da = Double.parseDouble(a);
                double db = Double.parseDouble(b);
                double dc = Double.parseDouble(c);
                double dalpha = Double.parseDouble(alpha);
                double dbeta = Double.parseDouble(beta);
                double dgamma = Double.parseDouble(gamma);
                Cell cel = new Cell(da, db, dc, dalpha, dbeta, dgamma);
                AtomCoords coo = new AtomCoords();
                coo.setCell(cel);
                coo.convertCell(Cell.CELL_VEC);
                Cell cell0 = coo.getCell(0);
                return cell0;
            } catch (Exception exc) {
                return null;
            }
        }
    }

    /**
     * 指定のInputInterfaceオブジェクトからまずセルベクトルを読み込み, さらに逆格子ベクトルを作成するメソッド.
     * 上手くいかない場合はnullを返す.
     * 
     * @param inputInterface
     *            このオブジェクトからデータを読み込む
     * @return 逆格子ベクトル; うまくデータを読み込めない場合null
     */
    public static double[][] getReciprocalLatticeFrom(
            InputInterface inputInterface) {
        double[][] cell = null;
        Cell ce = getCellFrom(inputInterface);
        if (ce == null)
            return null;

        if (BravaisLattice.isBravais(inputInterface)) {
            if (ce.getCellScheme() == Cell.CELL_VEC) {
                AtomCoords coord = new AtomCoords();
                coord.setCell(ce);
                coord.convertCell(Cell.CELL_ABC);
                ce = coord.getCell(1);
            }
            double[][] dcell = ce.getDouble();
            BravaisLattice blat = BravaisLattice.getBravaisLattice(dcell[0][0],
                    dcell[0][1], dcell[0][2], dcell[1][0], dcell[1][1],
                    dcell[1][2],
                    BravaisLattice.getLatticeSystemFrom(inputInterface));
            if (blat != null) {
                double[][] prim = blat.getPrimitiveLatticeVector();
                Cell tmpcell = new Cell(prim[0], prim[1], prim[2]);
                cell = tmpcell.getReciprocalLattice();
            }
        }
        if (cell == null) {
            cell = ce.getReciprocalLattice();
            if (cell != null) {
                for (int i = 0; i < 3; i++) {
                    logger.debug("reclat " + cell[i][0] + ", " + cell[i][1]
                            + ", " + cell[i][2]);
                }
            }
        }

        return cell;
    }

    /**
     * 指定のAtomCoordsから逆格子ベクトルを取得する.
     * 
     * @param coords
     *            逆格子ベクトルを取得したいセル
     * @return 逆格子ベクトルの配列; 失敗した場合null.
     */
    public static double[][] getReciprocalLatticeFrom(AtomCoords coords) {
        double[][] cell = new double[3][3];
        try {
            Cell ce = coords.getCell()[0];
            cell = ce.getReciprocalLattice();
        } catch (Exception exc) {
            logger.error("failed to obtain cell.");
            return null;
        }
        return cell;
    }

    /**
     * 引数のAtomCoordsが, ウルトラソフト型の擬ポテンシャルを利用する原子種を持っているかどうか判定する.
     * 
     * @return ウルトラソフト的原子種が一つでもあればtrue.
     */
    public static boolean hasUSPP(AtomCoords coords) {
        AtomList atomList = coords.getAtomList();
        Vector elemNames = new Vector();
        for (int i = 0; i < atomList.getNumAt(); i++) {
            Atom atom = atomList.getAtomAt(i);
            elemNames.add(atom.getElementName());
        }
        return hasUSPPSub(elemNames);
    }

    /**
     * 引数のInputInterfaceに記載されている原子配置が, ウルトラソフト型擬ポテンシャルを利用する原子種を持っているかどうか 判定する.
     * 
     * @return ウルトラソフト型原子種が一つでもあればtrue.
     */
    public static boolean hasUSPP(InputInterface inputInterface) {
        InputInterfaceTable table = inputInterface
                .getInputInterfaceTable("structure.atom_list.atoms");
        int elem = table.getIndexFor("element");
        Vector dat = table.getTableData();
        Vector elemNames = new Vector();
        for (int i = 0; i < dat.size(); i++) {
            String ele = ((String[]) dat.get(i))[elem];
            elemNames.add(ele);
        }
        return hasUSPPSub(elemNames);
    }

    private static boolean hasUSPPSub(Vector elemNames) {
        File ppdir = new File(Utils.getDefaultPPDir());
        File[] ppfiles = ppdir.listFiles();
        Vector elements = Utils.removeDupli(elemNames);
        for (int i = 0; i < elements.size(); i++) {
            for (int j = 0; j < ppfiles.length; j++) {
                String comp = ppfiles[j].getName().split("_")[0] + "\\d*";
                if (elements.get(i).toString().matches(comp)
                        && ppfiles[j].getName().split("_us_").length == 2)
                    return true;
            }
        }
        return false;
    }

    private static int getNumBandsSub(Vector elemNames, double factor) {
        return getNumBandsSub(elemNames, null, factor);
    }

    private static int getNumBandsSub(Vector elemNames, Vector weight,
            double factor) {
        File ppdir = new File(Utils.getDefaultPPDir());
        File[] ppfiles = ppdir.listFiles();
        Vector elements = Utils.removeDupli(elemNames);
        HashMap numElectrons = new HashMap();
        if (elements == null)
            return -1;
        for (int i = 0; i < elements.size(); i++) {
            String name = (String) elements.get(i);
            for (int j = ppfiles.length - 1; j >= 0; j--) {
                logger.debug("name: " + name);
                String comp = ppfiles[j].getName().split("_")[0] + "\\d*";
                if (name.matches(comp)) {
                    BufferedReader reader = null;
                    try {
                        reader = new BufferedReader(new FileReader(ppfiles[j]));
                        String firstLine = "";
                        while (true) {
                            firstLine = reader.readLine().trim();
                            if (!(firstLine.startsWith("#")
                                    || firstLine.startsWith("*") || firstLine
                                        .length() == 0))
                                break;
                        }
                        numElectrons.put(name, firstLine.split("\\s+")[1]);
                    } catch (Exception exc) {
                        exc.printStackTrace();
                        logger.error("encountered error while reading ppfile "
                                + name);
                    } finally {
                        try {
                            reader.close();
                        } catch (Exception exc) {
                        }
                    }
                    break;
                }
            }
        }

        int numbands = 0;
        for (int i = 0; i < elemNames.size(); i++) {
            Object tmp = numElectrons.get(elemNames.get(i));
            int wei = 1;
            if (weight != null && weight.size() > i)
                try {
                    wei = ((Integer) weight.get(i)).intValue();
                } catch (ClassCastException cce) {
                }
            if (tmp == null || tmp.toString().trim().length() == 0)
                continue;
            try {
                numbands += Integer.parseInt(tmp.toString()) * wei;
            } catch (NumberFormatException nfe) {
                logger.error("invalid num. electrons :" + tmp.toString()
                        + " for element: " + elemNames.get(i));
            }
        }
        numbands++;

        logger.debug("total number of valence electrons: " + numbands);

        return (int) Math.round((((double) numbands) * 0.5) * factor);
    }

    /**
     * バンド数を計算する. 最低必要なバンド数に, ある係数を掛けた値を返す.
     * 
     * @param inputInterface
     *            原子の座標が書いてあるInputInterface
     * @param metallic
     *            金属的な系ならtrue
     * @param spin
     *            スピンを考慮しているならtrue
     * @param factor
     *            最低必要なバンド数にこの数字を掛けた値を返す.
     * @return バンド数
     */
    public static int getNumBands(InputInterface inputInterface,
            boolean metallic, boolean spin, double factor) {
        if (metallic)
            factor *= 1.2;
        // if (spin) //勘違い; spinを考慮していても「バンドの数」を増やす必要はない。
        // factor *= 2;

        Vector elemNames = new Vector();
        InputInterfaceTable atmtable = inputInterface
                .getInputInterfaceTable("structure.atom_list.atoms");
        int elemInd = atmtable.getIndexFor("element");
        int weiInd = atmtable.getIndexFor("weight");
        Vector data = atmtable.getTableData();
        Vector weight = new Vector();
        for (int i = 0; i < data.size(); i++) {
            String[] dat = (String[]) data.get(i);
            elemNames.add(dat[elemInd]);
            if (weiInd < dat.length)
                try {
                    weight.add(new Integer(dat[weiInd]));
                } catch (NumberFormatException nfe) {
                    weight.add(new Integer(1));
                }
            else
                weight.add(new Integer(1));
        }
        logger.debug("elemnames.size(): " + elemNames.size());
        logger.debug("size of atmtable: " + atmtable.getTableData().size());
        return getNumBandsSub(elemNames, weight, factor);
    }

    /**
     * 引数のInputInterfaceからおおよそのバンド数を見積もる.
     * 
     * @param inputInterface
     *            原子の座標が書いてあるInputInterface
     * @param metallic
     *            金属的な系ならtrue
     * @param spin
     *            スピンを考慮しているならtrue
     * @return おおよそのバンド数
     */
    public static int getDefaultNumBands(InputInterface inputInterface,
            boolean metallic, boolean spin) {
        return getNumBands(inputInterface, metallic, spin, 1.2);
    }

    /**
     * 引数のAtomCoordsオブジェクトから, おおよそのバンド数を見積もるメソッド
     * 
     * @param coords
     *            調べたい座標データ
     * @param metallic
     *            金属の場合true.
     * @param spin
     *            スピンを考慮しているならtrue
     * @return int おおよそのバンド数
     */
    public static int getDefaultNumBands(AtomCoords coords, boolean metallic,
            boolean spin) {
        double factor = 1.2;
        if (metallic)
            factor = 1.5;
        if (spin)
            factor *= 1.5;

        AtomList atomList = coords.getAtomList();
        Vector elemNames = new Vector();
        Vector weight = new Vector();
        for (int i = 0; i < atomList.getNumAt(); i++) {
            Atom atom = atomList.getAtomAt(i);
            elemNames.add(atom.getElementName());
            String wei = atom.getProperty("weight");
            if (wei != null && wei.trim().equals("2"))
                weight.add(new Integer(2));
            else
                weight.add(new Integer(1));
        }
        return getNumBandsSub(elemNames, weight, factor);
    }

    /**
     * k点メッシュの"デフォルト"を, 引数のセルから推測.
     * 
     * @param cell
     *            このセルを元にk点メッシュの細かさのデフォルト値を. bohr単位で!!
     * @param metallic
     *            金属の場合true. より細かいメッシュが得られる.
     */
    public static int[] getDefaultKmesh(Cell cell, boolean metallic) {
        int[] ret = { 1, 1, 1 };
        double[][] dcell = null;
        double factor = 30d;
        if (metallic)
            factor = 60;
        if (cell != null)
            dcell = cell.getDouble();
        if (dcell == null) {
            logger.error("invalid cell.");
            return ret;
        }
        try {
            double lenx = Math.sqrt(Math.pow(dcell[0][0], 2)
                    + Math.pow(dcell[0][1], 2) + Math.pow(dcell[0][2], 2));
            double leny = Math.sqrt(Math.pow(dcell[1][0], 2)
                    + Math.pow(dcell[1][1], 2) + Math.pow(dcell[1][2], 2));
            double lenz = Math.sqrt(Math.pow(dcell[2][0], 2)
                    + Math.pow(dcell[2][1], 2) + Math.pow(dcell[2][2], 2));
            ret[0] = (int) Math.round(factor / lenx);
            ret[1] = (int) Math.round(factor / leny);
            ret[2] = (int) Math.round(factor / lenz);
            for (int i = 0; i < 3; i++)
                if (ret[i] == 0)
                    ret[i] = 1;
        } catch (Exception exc) {
            logger.error("failed to get default kmesh.");
        }
        return ret;
    }

    /**
     * 現在のバンド数が"充分"かどうかしらべて, 結果をJOptionPaneで表示するGUI.
     * 
     * @param currnumbands
     *            現在のバンド数
     * @param inputInterface
     *            参照するPHASE入力
     * @return 修正する場合, 修正後のバンド数; 修正しないなら負の値
     */
    public static int checkNumBands(JComponent parent, int currnumbands,
            InputInterface inputInterface) {
        int retval = -1;
        try {
            int defaultNumBands = getDefaultNumBands(inputInterface, false,
                    false);
            int minNumBands = getNumBands(inputInterface, false, false, 1);

            if (currnumbands >= defaultNumBands)
                JOptionPane.showInternalMessageDialog(
                        parent,
                        "There are enough number of bands;\n current number of bands: "
                                + String.valueOf(currnumbands)
                                + " minimum number of bands: "
                                + String.valueOf(minNumBands)
                                + " recommended number of bands: "
                                + String.valueOf(defaultNumBands));
            else {
                int ret = JOptionPane
                        .showInternalConfirmDialog(
                                parent,
                                "Current number of bands: "
                                        + String.valueOf(currnumbands)
                                        + ", minimum number of bands: "
                                        + String.valueOf(minNumBands)
                                        + ", recommended number of bands: "
                                        + String.valueOf(defaultNumBands)
                                        + ". Fix it?", "",
                                JOptionPane.WARNING_MESSAGE);
                if (ret == JOptionPane.OK_OPTION)
                    retval = defaultNumBands;
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return retval;
    }

}
