/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/11/11, 21:08
!  AUTHOR(S): KOGA, Junichiro
!  File : Output000Parser.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.plugins.projectmanipulator.phase.resultsviewerpanel.parsers;

import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.outputinterface.OutputData;
import ciss.phase_viewer.outputinterface.OutputParser;

/**
 * PHASEの標準出力用parser.
 * 
 * @author
 */
public class Output000Parser extends OutputParser {
    private Logger logger = Logger.getLogger(Output000Parser.class.getName());

    private Vector energyData = new Vector();

    private Vector chargeData = new Vector();

    private Vector cpuData = new Vector();

    private Vector stressData = new Vector();

    private String energy_start_tag = "TOTAL ENERGY FOR";

    private String energy_start_tag2 = "KI=";

    private String energy_start_tag3 = "NL=";

    private String energy_start_tag4 = "PHYSICALLY CORRECT";

    private String CPU_start_tag = "<< CPU";

    private String CPU_end_tag = "Total cpu";

    private String charge_start_tag = "!NEW total charge";

    private String stress_start_tag = "STRESS TENSOR";

    private boolean in_energy = false;

    private boolean in_cpu = false;

    private String npes_start = "npes =";

    private String neg_kv3_start = "!|| neg";

    private String fermi_start = "EFermi";

    private String is_metallic_start = "--- The system is";

    private String eigen_start = "======  Energy Eigen Values ======";

    private String occup_start = "======  Occupations ======";

    private String num_bands_start = "!** num_bands";

    private String num_kpoints_in_irrbz = "number of k-points in irreducible BZ";

    private String kpoints_generated_tag = "=== k-points generated";

    /** Creates a new instance of Output000Parser */
    public Output000Parser(String fileName) {
        super(fileName);
        // output000は別スレッドだとどうにもうまく行かない. やっつけな対処だけど.
        super.invokeNewThread = false;
    }

    protected boolean parse() {
        // if ( reader == null ) {
        // return false;
        // }
        // try{
        // //if ( !reader.ready() ) {
        // FileReader freader = new FileReader(fileName);
        // reader = new LineNumberReader(freader);
        // //}
        // } catch(Exception exc){}
        String line = "";
        try {
            FileReader freader = new FileReader(fileName);
            reader = new LineNumberReader(freader);
            while ((line = reader.readLine()) != null) {
                parseLine(line.trim());
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
            // return false;
        } catch (Exception exc) {
            exc.printStackTrace();
        } finally {
            isDone = true;
            // try{
            // reader.close();
            // } catch(IOException ioe) {
            // logger.error("failed to close file: "+fileName);
            // }
        }

        super.initializeOutputDataVector();
        String[][] energy2d = null;
        if (energyData.size() != 0) {
            energy2d = new String[energyData.size()][energy_ident.length];
            energyData.copyInto(energy2d);
            energyData = new Vector();
            OutputData energy = new OutputData("stdout(energy)",
                    "energy at each SCF step", energy_ident, energy2d, "");
            energy.setFileName(fileName);
            super.addOutputData(energy);
        }

        String[][] cpu2d = null;
        if (cpuData.size() != 0) {
            cpu2d = new String[cpuData.size()][cpu_idents.length];
            cpuData.copyInto(cpu2d);
            cpuData = new Vector();
            OutputData cpu = new OutputData("stdout(time)", "cpu statistics",
                    cpu_idents, cpu2d, "");
            cpu.setFileName(fileName);
            super.addOutputData(cpu);
        }

        // if ( stressData.size() != 0 ) {
        // String [][] stress2d = new
        // String[stressData.size()][stress_ident.length];
        // stressData.copyInto(stress2d);
        // OutputData outputData = new OutputData("stress tensor","elements of
        // the stress tensor (in a.u.)",stress_ident,stress2d,"");
        // super.addOutputData(outputData);
        // }

        if (chargeData.size() != 0) {
            String[][] charge2d = new String[chargeData.size()][charge_ident.length];
            chargeData.copyInto(charge2d);
            chargeData = new Vector();
            OutputData outputData = new OutputData("spin density",
                    "spin density at each SCF step", charge_ident, charge2d, "");
            outputData.setFileName(fileName);
            super.addOutputData(outputData);
        }

        return true;
    }

    private void parseLine(String line) {
        if (line.startsWith(energy_start_tag)) {
            in_energy = true;
        }

        if (line.startsWith(CPU_start_tag)) {
            in_cpu = true;
        }

        if (in_energy) {
            parseEnergy(line);
        }

        if (in_cpu) {
            parseCPU(line);
        }

        if (line.equals(stress_start_tag)) {
            parseStress(line);
        }

        if (line.startsWith(charge_start_tag)) {
            parseCharge(line);
        }

        if (line.startsWith(npes_start)) {
            parseNproc(line);
        }

        if (line.startsWith(neg_kv3_start)) {
            parseNeKv3(line);
        }

        if (line.startsWith(num_bands_start)) {
            parseNumBands(line);
        }
        if (line.startsWith(is_metallic_start)) {
            parseIsMetallic(line);
        }

        if (line.startsWith(fermi_start)) {
            parseFermi(line);
        }

        if (line.startsWith(eigen_start)) {
            parseEigen(line);
        }

        if (line.startsWith(occup_start)) {
            parseOccup(line);
        }

        if (line.startsWith(num_kpoints_in_irrbz)) {
            parseNumKpoints(line);
        }

        if (line.startsWith(kpoints_generated_tag))
            parseKpointsGenerated(line);
    }

    private void parseKpointsGenerated(String line) {
        try {
            int nkp = -1;
            reader.readLine(); // read ik CARTS BUCS ...
            while (true) {
                String li = reader.readLine();
                if (li == null)
                    break;
                String[] kpoints = li.trim().split("\\s+");
                logger.debug("kpoints[0]: " + kpoints[0]);
                try {
                    nkp = Integer.parseInt(kpoints[0]);
                } catch (NumberFormatException nfe) {
                    if (nkp > 0)
                        kv3 = String.valueOf(nkp);
                    break;
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    private void parseNumBands(String line) {
        String[] arline = line.split("\\s+");
        if (arline == null || arline.length < 4) {
            return;
        }
        neg = arline[3];
    }

    private String[] kind;

    private String[][] kpoint;

    private String[][] eigen_values;

    private String[][] occupations;

    int num_bands = 0;

    int num_kpoints = 0;

    private void parseEigen(String line) {
        logger.debug("eigenparse start");
        try {
            logger.debug("neg, kv3 prior to parseInt: " + neg + ", " + kv3);
            num_bands = Integer.parseInt(neg);
            num_kpoints = Integer.parseInt(kv3);
            logger.debug("num_bands & num_kpoints: " + num_bands + " "
                    + num_kpoints);
        } catch (NumberFormatException npe) {
            // logger.error("invalid neg.");
            return;
        }

        if (num_bands == 0 || num_kpoints == 0) {
            return;
        }

        kpoint = new String[num_kpoints][4];
        eigen_values = new String[num_kpoints][num_bands];
        parseEachNegandKpoint(eigen_values);
    }

    private void parseOccup(String line) {
        if (num_bands == 0 || num_kpoints == 0) {
            return;
        }
        occupations = new String[num_kpoints][num_bands];
        parseEachNegandKpoint(occupations);
    }

    // private void parseEachNegandKpointDynamically(String [][] array2d) {
    // Vector kpvec = new Vector();
    // Vector eigvec = new Vector();
    // while(true) {
    // try{
    // String foo = reader.readLine();
    // if ( foo == null || foo.trim().length() == 0 ) {
    // break;
    // }
    // String [] arr = foo.split("\\s++");
    // boolean kp = true;
    // try{
    // Integer.parseInt(arr[0]);
    // } catch(NumberFormatException nfe) {
    // kp = false;
    // }
    // if ( kp ) {
    // kpvec.addElement(arr);
    // }
    // } catch(Exception exc){
    // exc.printStackTrace();
    // }
    // }
    // }

    private void parseEachNegandKpoint(String[][] array2d) {
        String[] ark = null;
        int bandCount = 0;
        int kpointCount = 0;
        boolean readKpoint = false;
        while (true) {
            try {
                String foo = reader.readLine();
                if (foo == null || foo.trim().length() == 0) {
                    break;
                }
                ark = foo.trim().split("\\s+");
                if (ark == null) {
                    return;
                }
                if (!readKpoint) {
                    if (ark.length == 4) {
                        kpoint[kpointCount][0] = "    ";
                        kpoint[kpointCount][1] = ark[1];
                        kpoint[kpointCount][2] = ark[2];
                        kpoint[kpointCount][3] = ark[3];
                    } else if (ark.length == 5) {
                        String WS = "";
                        if (ark[1].equals("UP")) {
                            WS = "      ";
                        }
                        kpoint[kpointCount][0] = ark[1] + WS;
                        kpoint[kpointCount][1] = ark[2];
                        kpoint[kpointCount][2] = ark[3];
                        kpoint[kpointCount][3] = ark[4];
                    }
                    readKpoint = true;
                } else {
                    for (int i = 0; i < ark.length; i++) {
                        array2d[kpointCount][bandCount] = ark[i];
                        bandCount++;
                        if (bandCount == num_bands) {
                            bandCount = 0;
                            readKpoint = false;
                            kpointCount++;
                            if (kpointCount == num_kpoints) {
                                return;
                            }
                            break;
                        }
                    }
                }

            } catch (IOException ioe) {
                logger.error("failed read");
                ioe.printStackTrace();
                return;
            } catch (ArrayIndexOutOfBoundsException aio) {
                logger.error("inconsistent neg/kv3");
                aio.printStackTrace();
                return;
            }
        }
    }

    private String NA = "(not available)";

    private String neg = NA;

    private String kv3 = NA;

    private void parseNeKv3(String line) {
        String[] arline = line.split("\\s+");
        if (arline.length < 6) {
            return;
        }
        neg = arline[4];
        kv3 = arline[5];
    }

    private void parseNumKpoints(String line) {
        String[] arline = line.split("=");
        if (arline.length < 2)
            return;
        kv3 = arline[1].trim();
    }

    private String fermi = NA;

    private void parseFermi(String line) {
        String[] arline = line.split("\\s+");
        if (arline.length < 3) {
            return;
        }
        fermi = arline[2];
    }

    private String is_metallic = NA;

    private void parseIsMetallic(String line) {
        String[] arline = line.split("\\s+");
        if (arline.length < 5) {
            return;
        }
        if (arline[4].startsWith("meta")) {
            is_metallic = "yes";
        } else if (arline[4].startsWith("insu")) {
            is_metallic = "no";
        }
    }

    /**
     * Output000ファイルが持つ, もろもろのデータを取得. キーのリスト: nproc -> np,ne,nkの配列を返す. negkv3 ->
     * neg, kv3の配列 fermi -> efermi, metallic か否かの配列. kpoint_index ->
     * indexで指定されるk点の配列, spinありの場合第一要素がUP/DOWNに相当. occup_index ->
     * indexで指定されるoccupationの配列; エネルギーの低い順 eigen_index ->
     * indexで指定されるeigenvalueの配列; エネルギーの低い順 num_kpoints -> k点の数
     * 
     * @param key
     *            識別子.
     * @return なんらかの結果.
     */
    public String[] getGenericData(String key) {
        try {
            if (key.equals("nproc")) {
                return getNpNeNk();
            }
            if (key.equals("negkv3")) {
                return new String[] { neg, kv3 };
            }
            if (key.equals("fermi")) {
                return new String[] { fermi, is_metallic };
            }

            if (key.startsWith("kpoint_")) {
                String[] foo = key.split("_");
                int index = Integer.parseInt(foo[1]);
                return kpoint[index];
            }
            if (key.startsWith("occup_")) {
                String[] foo = key.split("_");
                int index = Integer.parseInt(foo[1]);
                if (occupations != null)
                    return occupations[index];
            }
            if (key.startsWith("eigen_")) {
                String[] foo = key.split("_");
                int index = Integer.parseInt(foo[1]);
                return eigen_values[index];
            }
            if (key.startsWith("num_kpoints")) {
                return new String[] { String.valueOf(num_kpoints) };
            }
        } catch (NumberFormatException e) {
        } catch (NullPointerException npe) {
        }

        return super.getGenericData(key);
    }

    /**
     * Output000ファイルが持つ, もろもろの二次元データを取得. キーのリスト: stress -> ストレステンソル
     * 
     * @param key
     *            識別子.
     * @return なんらかの結果.
     */
    public String[][] getGenericData2D(String key) {
        if (key.equals("stress")) {
            if (stressData.size() != 0) {
                String[][] stress2d = new String[stressData.size()][stress_ident.length];
                stressData.copyInto(stress2d);
                return stress2d;
            }
        }
        return super.getGenericData2D(key);
    }

    private String npes = "---";

    private String nk = "---";

    private String ne = "---";

    private void parseNproc(String line) {
        String[] arline = line.split("\\s+");
        int readCount = 0;
        if (line.startsWith(npes_start)) {
            if (arline.length >= 3) {
                npes = arline[2];
            }
        }

        String tmpline = "";
        while (true) {
            try {
                tmpline = reader.readLine().trim();
                if (tmpline.trim().startsWith("ne")) {
                    String[] ar = tmpline.split("\\s+");
                    if (ar == null || ar.length < 5) {
                        return;
                    }
                    ne = ar[3];
                    nk = ar[4];
                    return;
                }
                readCount++;
                if (readCount >= 10) {
                    return;
                }
            } catch (IOException ioe) {
                logger.error("failed to parse np, ne and nk");
                return;
            }
        }

    }

    /**
     * returns the value of np, ne and nk as a String array, where the 0-th
     * element is np, the 1st element is ne and the 2nd element is nk.
     * 
     * @return a String array representing np, ne, and nk.
     */
    public String[] getNpNeNk() {
        return new String[] { npes, ne, nk };
    }

    private String[] charge_ident = { "iteration", "up", "down", "sum" };

    private void parseCharge(String line) {
        String[] chdata = line.split("\\s+");
        if (chdata.length < 12) {
            return;
        }

        String[] ch = { iter, chdata[7], chdata[9], chdata[11] };
        chargeData.addElement(ch);
    }

    private String[] stress_ident = { "xx,xy,xz", "yx,yy,yz", "zx,zy,zz" };

    private void parseStress(String line) {
        stressData = new Vector();
        try {
            String xstr = reader.readLine().trim();
            String ystr = reader.readLine().trim();
            String zstr = reader.readLine().trim();
            logger.debug("xstr,ystr,zstr");
            logger.debug(xstr);
            logger.debug(ystr);
            logger.debug(zstr);
            String[] x = xstr.split("\\s+");
            String[] y = ystr.split("\\s+");
            String[] z = zstr.split("\\s+");
            stressData.addElement(x);
            stressData.addElement(y);
            stressData.addElement(z);
        } catch (Exception exc) {
            logger.error("failed to parse stress tensor.");
            exc.printStackTrace();
        }

    }

    private String iter;

    private String total_energy;

    private String edelt;

    private String KI;

    private String HA;

    private String XC;

    private String LO;

    private String NL;

    private String EW;

    private String PC;

    private String EN;

    private String physically_correct_energy;

    private String[] energy_ident = { "iteration", "total energy", "edelt",
            "KI", "HA", "XC", "LO", "NL", "EW", "PC", "EN", "corrected energy" };

    private void parseEnergy(String line) {
        String[] arline = line.split("\\s+");
        if (line.startsWith(energy_start_tag)) {
            if (arline.length >= 10) {
                iter = arline[3];
                total_energy = arline[6];
                edelt = arline[9];
            }
            return;
        }
        if (line.startsWith(energy_start_tag2)) {
            if (arline.length >= 8) {
                KI = arline[1];
                HA = arline[3];
                XC = arline[5];
                LO = arline[7];
            }
            return;
        }
        if (line.startsWith(energy_start_tag3)) {
            if (arline.length >= 8) {
                NL = arline[1];
                EW = arline[3];
                PC = arline[5];
                EN = arline[7];
            }
            return;
        }
        if (line.startsWith(energy_start_tag4)) {
            if (arline.length >= 5) {
                physically_correct_energy = arline[4];
            }
        }

        in_energy = false;
        String[] rowData = new String[] { iter, total_energy, edelt, KI, HA,
                XC, LO, NL, EW, PC, EN, physically_correct_energy };
        energyData.addElement(rowData);
    }

    private String[] cpu_idents = { "iter", "rank", "sub.name", "(sec.)",
            "(%)", "count" };

    private void parseCPU(String line) {
        String newLine = "";
        int num_line = 0;
        int max_line = 12;
        try {
            while (true) {
                String newL = reader.readLine();
                if (newL == null || newL.trim().length() == 0) {
                    break;
                }
                newLine = newL.trim();
                num_line++;
                String iter_cpu = "";
                String total = "";
                String total_accum = "";
                String rank = "";
                String subname = "";
                String sec = "";
                String percentage = "";
                String occurance = "";
                if (newLine.startsWith("no"))
                    continue;
                String[] arline = newLine.split("\\s+");

                if (arline.length < 6) {
                    continue;
                }

                if (Utils.isNumber(arline[0])) {
                    rank = arline[0];
                    int nextind = 4;
                    iloop: for (int i = 2; i < arline.length; i++) {
                        char[] chary = arline[i].toCharArray();
                        jloop: for (int j = 0; j < chary.length; j++) {
                            if (Character.isDigit(chary[j])) {
                                nextind = i;
                                break iloop;
                            }
                        }
                        subname += arline[i];
                    }
                    String tmp = arline[nextind];
                    if (tmp.indexOf("sec") > 0) {
                        char[] tmpar = tmp.toCharArray();
                        logger.debug("tmp str here: " + tmp);
                        sec = new String(tmpar, 0, tmpar.length - 6);
                    } else
                        sec = tmp;

                    String tmpper = arline[nextind + 1];
                    logger.debug("and tmp str here: " + tmpper);
                    if (tmpper.indexOf("%") > 0) {
                        char[] tmpar2 = tmpper.toCharArray();
                        percentage = new String(tmpar2, 0, tmpar2.length - 3);
                    } else
                        percentage = tmpper;

                    occurance = arline[nextind + 2];
                }
                if (newLine.startsWith(CPU_end_tag) || num_line >= max_line) {
                    if (arline.length > 5) {
                        iter = arline[5];
                    }
                    break;
                }

                String[] row_cpu = { iter, rank, subname, sec, percentage,
                        occurance };
                cpuData.addElement(row_cpu);
            }
            in_cpu = false;
        } catch (IOException ioe) {
            logger.error("failed to extract 'CPU statistics' from stdout file.");
        } catch (ArrayIndexOutOfBoundsException aiob) {
            aiob.printStackTrace();
        } catch (NullPointerException npe) {
            npe.printStackTrace();
        }
    }

    public String getCurrentMessage() {
        return null;
    }

}
