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

import java.awt.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

public class NfenergyParser extends OldOutputParser {

    /** number of columns */
    private int numColumns;

    /** number of rows */
    private int numRows;

    /** counter */
    private int numEig = 0;

    /** number of energy bands */
    private int numBands = 0;

    /** spin */
    private int nspin = 1;

    private final String UP = "UP";
    private final String DOWN = "DOWN";

    /** either 'up' or 'down' */
    private String updown = UP;

    /** number of k-points */
    private int numKpoints = 0;

    private final int numKdata = 5;

    /** flag to distinguish whether the column name has already been set */
    private boolean isColumnNameSet = false;

    /** for nspin=2 */
    private Vector colNametmp = new Vector();
    private Vector kinfo = new Vector();
    private Vector eigUp = new Vector();
    private Vector eigDown = new Vector();

    /** 'length' of kvector */
    private double dk = 0.;
    private double dkp = 0.;
    private double dkxp = 0.;
    private double dkyp = 0.;
    private double dkzp = 0.;
    private double korigin = 0.d;
    private boolean iskoriginSet = false;

    private double[][] bvec = new double[3][3];
    private boolean calrk = false;

    private String projdir = new String();
    private String FS = System.getProperty("file.separator");
    private String defaultkinPath = System.getProperty("user.home") + FS
            + ".phase-viewer" + FS + "templates" + FS + "kpoints";

    /**
     * ヘッダーを登録し, さらにband_kpoint.pl用入力ファイルの有無を調べる. 存在しない場合, 指定を促す.
     * 
     * @param filename
     *            パースすべきF_ENFファイル.
     */
    public NfenergyParser(String filename) {
        super(filename);
        colNametmp.addElement("ik");
        colNametmp.addElement("kx");
        colNametmp.addElement("ky");
        colNametmp.addElement("kz");

        File file = new File(filename);
        projdir = file.getParent();
        file = new File(projdir + System.getProperty("file.separator")
                + "bandkpoint.in");

        if (file.exists()) {
            readBandKpointIn(file);
        } else {
            askForBandKpointIn();
        }

        colNametmp.addElement("|k|");
    }

    private void readBandKpointIn(File file) {
        String readString = new String();
        calrk = true;
        try {
            System.out.println("reading " + file + " ...");
            FileReader freader = new FileReader(file);
            BufferedReader rein = new BufferedReader(freader);
            readString = rein.readLine();
            System.out.println(readString);
            for (int i = 0; i < 3; i++) {
                readString = rein.readLine();
                System.out.println(readString);
                StringTokenizer st = new StringTokenizer(readString);
                bvec[i][0] = Double.parseDouble(st.nextToken());
                bvec[i][1] = Double.parseDouble(st.nextToken());
                bvec[i][2] = Double.parseDouble(st.nextToken());
            }
            rein.close();
            freader.close();
        } catch (Exception e) {
            System.out.println("failed read from: " + file);
            calrk = false;
        }
    }

    private void askForBandKpointIn() {
        // allow user to select band_kpoint.in file
        Object[] init = { "specify input file", "ignore" };
        Component component = null;
        ImageIcon icon = new ImageIcon("logo.png");

        int ret = JOptionPane.showInternalOptionDialog(
                ciss.phase_viewer.mainpanel.Desk.getDesktop(),
                "couldn't find input file for band_kpoint.pl!", "warning",
                JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null,
                init, init[0]);
        if (ret == JOptionPane.YES_OPTION) {
            ChaseFileChooser fdlg = new ChaseFileChooser();
            fdlg.setDialogType(JFileChooser.CUSTOM_DIALOG);
            fdlg.setDialogTitle("Open");
            fdlg.setCurrentDirectory(new File(projdir));
            fdlg.setFileSelectionMode(JFileChooser.FILES_ONLY);
            if (fdlg.showDialog(component, "open") != JFileChooser.APPROVE_OPTION) {
                System.out.println("no specification for bandkpoint.in ...");
                calrk = false;
            } else {
                String bandkpointin = fdlg.getSelectedFile().getAbsolutePath();
                readBandKpointIn(new File(bandkpointin));
            }
        } else {
            System.out.println("no specification for bandkpoint.in ...");
            calrk = false;
        }
    }

    /**
     * パースのルールをここで設定する. ここで定義しているのは, 一行のみの情報でパースできる分.
     * 
     * @param str
     *            パースしたい文字列(一行).
     */
    public void parseString(String str) {
        StringTokenizer st = new StringTokenizer(str);
        String strdata = new String();
        boolean bloopisData = false;

        /* header */
        if (str.trim().startsWith("num_kpoints")) {
            st.nextToken();
            st.nextToken();
            try {
                numKpoints = Integer.parseInt(st.nextToken());
            } catch (Exception e) {
                System.out.println("invalid num_kpoints!");
            }
            addHeader(str);
            addHeader(OldOutputParser.CR);
            bloopisData = false;
        } else if (str.trim().startsWith("num_bands")) {
            st.nextToken();
            st.nextToken();
            try {
                numBands = Integer.parseInt(st.nextToken());
            } catch (Exception e) {
                System.out.println("invalid num_bands!");
            }
            addHeader(str);
            addHeader(OldOutputParser.CR);
            bloopisData = false;
        } else if (str.trim().startsWith("nspin")) {
            st.nextToken();
            st.nextToken();
            try {
                nspin = Integer.parseInt(st.nextToken());
            } catch (Exception e) {
                System.out.println("invalid nspin!");
            }
            // System.out.println("at numspin: "+numEig+" "+numBands);
            addHeader(str);
            addHeader(OldOutputParser.CR);
            bloopisData = false;
        } else if (str.trim().startsWith("Valence")) {
            addHeader(str);
            addHeader(OldOutputParser.CR);
            bloopisData = false;
        } else if (str.trim().startsWith("===")) {
            bloopisData = false;
        } else if (str.trim().startsWith("Fermi")) {
            bloopisData = false;
            addHeader(str);
            addHeader(OldOutputParser.CR);
        } else {
            while (st.hasMoreTokens()) {
                strdata = st.nextToken();
                if (strdata.trim().startsWith("ik")) {
                    st.nextToken();
                    String ik = st.nextToken();
                    st.nextToken();
                    String kx = st.nextToken();
                    String ky = st.nextToken();
                    String kz = st.nextToken();
                    // kz can end with a ')'
                    if (kz.endsWith(")")) {
                        char[] ch = kz.toCharArray();
                        kz = new String(ch, 0, ch.length - 1);
                    }
                    String next = st.nextToken();
                    if (nspin == 2) {
                        updown = next;
                    }
                    if (updown.equals(UP)) {
                        kinfo.addElement(ik);
                        kinfo.addElement(kx);
                        kinfo.addElement(ky);
                        kinfo.addElement(kz);
                        if (calrk) {
                            double dkx = Double.parseDouble(kx);
                            double dky = Double.parseDouble(ky);
                            double dkz = Double.parseDouble(kz);
                            double[] dkc = new double[3];
                            for (int i = 0; i < 3; i++) {
                                dkc[i] = bvec[i][0] * (dkxp - dkx) + bvec[i][1]
                                        * (dkyp - dky) + bvec[i][2]
                                        * (dkzp - dkz);
                            }
                            dk = dkp
                                    + Math.sqrt(Math.pow(dkc[0], 2)
                                            + Math.pow(dkc[1], 2)
                                            + Math.pow(dkc[2], 2));
                            if (!iskoriginSet) {
                                korigin = dk;
                                iskoriginSet = true;
                            }

                            kinfo.addElement(Double.toString(dk - korigin));
                            dkxp = dkx;
                            dkyp = dky;
                            dkzp = dkz;
                            dkp = dk;

                        } else {
                            kinfo.addElement("0.0");
                        }
                    }
                    /*
                     * System.out.println(ik+" "+kx +" "+ky+" "+kz+" "+updown);
                     */
                    bloopisData = false;
                    return;
                } else {
                    numEig++;
                    bloopisData = true;
                    if (updown.equals(UP)) {
                        eigUp.addElement(strdata);
                    } else if (updown.equals(DOWN)) {
                        eigDown.addElement(strdata);
                    }
                    if (!(isColumnNameSet)) {
                        if (nspin == 1) {
                            colNametmp.addElement("eig" + numEig);
                        } else if (nspin == 2) {
                            colNametmp
                                    .addElement("eig" + numEig + " " + updown);
                        }
                    }
                }
            }
        }

        if (bloopisData && (numEig == numBands * nspin)) {
            isColumnNameSet = true;
            numEig = 0;
        }

    }

    /**
     * 行ずつのパースでは行いきれないパース操作をここで行う.
     */
    protected void processData() {
        // set column name
        int numcols = colNametmp.size();
        for (int i = 0; i < numcols; i++) {
            addColumnName((String) colNametmp.get(i));
        }
        setNumColumns(numcols);

        // set data
        int numData = kinfo.size();
        int numLoop = numData / numKdata;
        // System.out.println("numCols, numData, numLoop: "
        // +numcols+" "+numData+" "+numLoop);

        for (int i = 0; i < numLoop; i++) {
            for (int j = 0; j < numKdata; j++) {
                addDataColumn((String) kinfo.get(i * numKdata + j));
            }
            for (int j = 0; j < numBands; j++) {
                addDataColumn((String) eigUp.get(i * numBands + j));
            }
            if (nspin == 2) {
                for (int j = 0; j < numBands; j++) {
                    addDataColumn((String) eigDown.get(i * numBands + j));
                }
            }
            addDataRow();
        }
    }

}
