/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : Nfdynm2AtomCoords.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.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.TaggedString;
import ciss.phase_viewer.mainpanel.ChaseProgressMonitor;
import ciss.phase_viewer.mainpanel.ProgressInfo;

/**
 * PHASE標準の座標データ出力形式である, F_DYNM形式のファイルからAtomCoordsオブジェクトを作成するクラス
 * 
 * @author KOGA, Junichiro
 */
public class Nfdynm2AtomCoords implements ProgressInfo {
    private static Logger logger = Logger.getLogger(Nfdynm2AtomCoords.class
            .getName());
    public static final int OFF = 0;
    public static final int ON = 1;
    private String fileName;
    private final String HASH = "#";
    private final String SPACE = "[\\s]+";

    private String[] avec = new String[3];
    private String[] bvec = new String[3];
    private String[] cvec = new String[3];

    private int NTyp = 1;
    private int NAtom = 1;
    private int NAtomNow = 0;

    private Vector atomSpecies = new Vector();
    private Vector speciesName = new Vector();

    private AtomCoords coords;

    private Cell cell;

    private int cellCount = 0;

    private Vector frames = new Vector();

    private int periodic = OFF;
    private int natm0 = 0;

    private int istart;
    private int iend;
    private int igap;

    private int iframeNow;
    private int nFrames = -1;

    private boolean dryrun = false;

    /**
     * ファイル名を渡す
     * 
     * @param fileName
     *            F_DYNMファイルのファイル名
     */
    public Nfdynm2AtomCoords(String fileName) {
        this.fileName = fileName;
        coords = new AtomCoords();
        coords.isCart(true);
        coords.setUnit(AtomCoords.BOHR);
        setDefault();
    }

    public Nfdynm2AtomCoords(String fileName, int periodic) {
        this.fileName = fileName;
        coords = new AtomCoords();
        coords.isCart(true);
        coords.setUnit(AtomCoords.BOHR);
        this.periodic = periodic;
        setDefault();
        logger.debug("num frames: " + frames.size());
    }

    public void setStartEndGap(int istart, int iend, int igap) {
        this.istart = istart;
        this.iend = iend;
        this.igap = igap;
    }

    private void setDefault() {
        istart = 0;
        iend = getNumFrames();
        igap = 1;
    }

    private boolean isDone = false;
    private LineNumberReader lreader;

    private void parse() {

        frames = new Vector();
        iframeNow = 0;
        try {
            FileReader freader = new FileReader(fileName);
            lreader = new LineNumberReader(freader);
            ChaseProgressMonitor monitor = ChaseProgressMonitor.getMonitor();
            monitor.setProgress(this);
            String str = new String();
            while ((str = lreader.readLine()) != null) {
                if (!parseString(str)) {
                    logger.warn("failed parse at line: "
                            + lreader.getLineNumber());
                }
            }
        } catch (IOException e) {
            logger.warn("failed read from file " + fileName);
        } finally {
            isDone = true;
            try {
                lreader.close();
            } catch (Exception exc) {
                logger.error(exc);
            }
        }
    }

    private boolean parseString(String str) {
        if (str.trim().startsWith(HASH)) {
            return parseHeader(str.trim());
        } else if (str.trim().startsWith("cps")) {
            return true;
        } else {
            return parseData(str.trim());
        }
    }

    private boolean parseHeader(String str) {
        char[] cha = str.toCharArray();
        String data = new String();
        for (int i = 1; i < cha.length; i++) {
            data += String.valueOf(cha[i]);
        }
        data = data.trim();

        if (data.startsWith("a_vector") || data.startsWith("b_vector")
                || data.startsWith("c_vector")) {
            String[] vec = data.split("=");
            if (vec.length <= 1) {
                logger.warn("invalid cell data");
                return false;
            }
            String vector = vec[1].trim();
            String[] vals = vector.split(SPACE);
            if (vals.length <= 2) {
                logger.warn("invalid cell data");
                return false;
            }
            for (int i = 0; i < 3; i++) {
                vals[i] = vals[i].trim();
            }
            logger.debug("got cell vector: " + vals[0] + " " + vals[1] + " "
                    + vals[2]);
            try {
                for (int i = 0; i < 3; i++) {
                    Double.parseDouble(vals[i]);
                }
            } catch (NumberFormatException nfe) {
                logger.warn("cell vector is not a number");
                return false;
            }
            if (data.startsWith("a_vector")) {
                for (int i = 0; i < 3; i++)
                    avec[i] = vals[i];
                cellCount++;

            } else if (data.startsWith("b_vector")) {
                for (int i = 0; i < 3; i++)
                    bvec[i] = vals[i];
                cellCount++;

            } else if (data.startsWith("c_vector")) {
                for (int i = 0; i < 3; i++)
                    cvec[i] = vals[i];
                cellCount++;

            }
            if (cellCount == 3) {
                cell = new Cell(avec, bvec, cvec);
                coords.setCell(cell);
            }
            return true;
        }

        if (data.startsWith("ntyp")) {
            String[] ntyp = data.split("=");
            if (ntyp.length <= 2) {
                logger.warn("invalid specification for ntyp and/or natm");
                return false;
            }
            String nty = ntyp[1].trim();
            String[] nt = nty.split(SPACE);
            if (nt.length <= 1) {
                logger.warn("invalid specification for ntyp and/or natm");
                return false;
            }
            try {
                NTyp = Integer.parseInt(nt[0].trim());
                NAtom = Integer.parseInt(ntyp[2].trim());
            } catch (NumberFormatException nfe) {
                logger.warn("either ntyp or natm was not a number");
                return false;
            }
            logger.debug("ntyp: " + NTyp + " natm: " + NAtom);
            return true;
        }

        if (data.startsWith("(natm->type)")) {
            String[] species = data.split(SPACE);
            if (species.length <= 1) {
                logger.warn("no specification for atom species");
                return false;
            }
            for (int i = 1; i < species.length; i++) {
                atomSpecies.addElement(species[i]);
            }
            for (int i = 0; i < atomSpecies.size(); i++) {
                logger.debug("species for atom " + i + " is "
                        + atomSpecies.get(i));
            }
            return true;
        }

        if (data.startsWith("(speciesname)")) {
            String[] names = data.split(SPACE);
            if (names.length <= 3) {
                logger.warn("invalid specification for species name");
                return false;
            }
            String tag = names[1];
            String value = names[3];
            logger.debug("tag & value for species: " + tag + " " + value);
            TaggedString ts = new TaggedString(tag, value);
            speciesName.addElement(ts);
            return true;
        }
        return true;
    }

    private boolean parseData(String str) {
        NAtomNow++;
        String[] data = str.split(SPACE);

        if (data.length <= 4) {
            logger.warn("invalid data");
            return false;
        }

        String[] xyz = new String[3];
        String type = new String();
        try {
            int index = Integer.parseInt(data[0].trim()) - 1;
            type = (String) atomSpecies.get(index);
        } catch (NumberFormatException nfe) {
            logger.warn("found a non-numeric index");
            return false;
        }

        String elementName = getElementName(type);
        if (elementName == null) {
            logger.warn("inconsistentcy found among species name and data");
            return false;
        }

        try {
            for (int i = 1; i < 4; i++) {
                Double.parseDouble(data[i].trim());
            }
        } catch (NumberFormatException nfe) {
            logger.warn("xyz-data is not a number");
            return false;
        }

        xyz[0] = data[1].trim();
        xyz[1] = data[2].trim();
        xyz[2] = data[3].trim();

        String[] force = new String[3];
        if (data.length >= 7) {
            force[0] = data[4].trim();
            force[1] = data[5].trim();
            force[2] = data[6].trim();
        }
        logger.debug("got data for atom no. " + NAtomNow + " : " + elementName
                + " " + xyz[0] + " " + xyz[1] + " " + xyz[2]);
        if (targetFrame()) {
            Atom atom = new Atom(elementName, xyz, force);
            coords.getAtomList().addAtom(atom);
        }
        if (NAtomNow == NAtom) {
            if (targetFrame()) {
                coords.convert(AtomCoords.TO_CART, AtomCoords.TO_ANG);
                frames.addElement(coords);
            }

            coords = new AtomCoords();
            coords.setUnit(AtomCoords.BOHR);
            coords.isCart(true);
            coords.setCell(cell);
            NAtomNow = 0;
            iframeNow += 1;
        }
        return true;
    }

    private boolean targetFrame() {
        if (dryrun)
            return false;
        if (iframeNow < istart || iframeNow > iend)
            return false;
        if (iframeNow % igap != 0)
            return false;
        return true;
    }

    private String getElementName(String type) {
        int numSpecies = speciesName.size();
        for (int i = 0; i < numSpecies; i++) {
            TaggedString ts = (TaggedString) speciesName.get(i);
            if (ts.getTag().trim().equals(type.trim())) {
                return ts.getValue();
            }
        }
        return null;
    }

    /**
     * 引数で指定した番号のAtomCoordsを返す
     * 
     * @param i
     *            どのAtomCoordsが欲しいか
     * @return i番目のAtomCoordsオブジェクト
     */
    public AtomCoords getFrameAt(int i) {
        if (frames == null || frames.size() == 0) {
            parse();
        }
        if (i < 0) {
            return (AtomCoords) frames.elementAt(frames.size() - 1);
        }
        int framenum = i;
        if (frames.size() <= i) {
            framenum = frames.size() - 1;
        }
        return (AtomCoords) frames.elementAt(framenum);
    }

    /**
     * 全フレームを取得
     * 
     * @return AtomCoordsオブジェクトを足しこんだjava.util.Vector
     */
    public Vector getFrames() {
        if (frames == null || frames.size() == 0) {
            parse();
        }
        return frames;
    }

    /**
     * フレーム数を取得.
     * 
     * @return フレーム数
     */
    public int getNumFrames() {
        // if (frames == null || frames.size() == 0) {
        // parse();
        // }
        // return frames.size();
        if (frames != null && frames.size() > 0)
            return frames.size();
        if (nFrames >= 1)
            return nFrames;
        dryrun = true;
        parse();
        dryrun = false;
        nFrames = iframeNow;
        return nFrames;
    }

    /**
     * プログレスバー用のメソッド
     */
    public boolean isDone() {
        return isDone;
    }

    /**
     * プログレスバー用のメソッド
     */
    public String getNameOfProgress() {
        return "converting F_DYNM ...";
    }

    /**
     * プログレスバー用のメソッド
     */
    public int getLength() {
        return ciss.phase_viewer.common.Utils.getNumLines(fileName);
    }

    /**
     * プログレスバー用のメソッド
     */
    public String getCurrentMessage() {
        return null;
    }

    /**
     * プログレスバー用のメソッド
     */
    public int getCurrent() {
        int ret = -1;
        try {
            ret = lreader.getLineNumber();
        } catch (Exception exc) {
        }
        return ret;
    }

    public void done() {
    }

}
