/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/11/09, 20:08
!  AUTHOR(S): KOGA, Junichiro
!  File : Cube2AtomCoords.java
!
!  Contact address :  Phase System Consortium
!
!   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.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.ConstParameters;
import ciss.phase_viewer.common.ElementInfo;

/**
 * converts a Gaussian cube file to an AtomCoords object.
 * 
 * @author
 */
public class Cube2AtomCoords {
    private Logger logger = Logger.getLogger(Cube2AtomCoords.class.getName());
    private String fileName;
    private AtomCoords coords;
    private boolean atomParseEnd = false;

    private Vector densityVector = new Vector();
    private float[][] chargeDensity;

    private String title = "";
    private String comment = "";

    /**
     * Creates a new instance of Cube2AtomCoords
     * 
     * @param fileName
     *            the name of the Gaussian cube file, in full path.
     */
    public Cube2AtomCoords(String fileName) {
        this.fileName = fileName;
    }

    private int lineCount;
    private boolean molecularOrbitalParseEnd = true;
    private Vector molinds;

    /**
     * AtomCoordsオブジェクトを作成し, それを返す.
     * 
     * @return 作成されたAtomCoordsオブジェクト
     */
    private int numdensitydata = 1;

    public AtomCoords getAtomCoords() {
        if (coords == null) {
            coords = new AtomCoords();
            coords.isCart(true);
            coords.setUnit(AtomCoords.BOHR);
        }
        FileReader freader = null;
        BufferedReader breader = null;
        try {
            freader = new FileReader(fileName);
            breader = new BufferedReader(freader);
            String line;

            title = breader.readLine();
            comment = breader.readLine();

            breader.close();

            breader = new BufferedReader(new FileReader(fileName));
            while ((line = breader.readLine()) != null) {
                if (atomParseEnd && molecularOrbitalParseEnd) {
                    String[] density = line.trim().split("\\s+");
                    if (density != null) {
                        for (int i = 0; i < density.length; i++) {
                            densityVector.addElement(density[i]);
                        }
                    }
                } else {
                    if (atomParseEnd && isMolecularOrbital) {
                        molecularOrbitalParseEnd = true;
                        String[] molorbs = line.split("\\s+");
                        molinds = new Vector();
                        if (molorbs.length >= 2)
                            for (int i = 1; i < molorbs.length; i++)
                                molinds.add(molorbs[i]);
                    } else {
                        parseLine(line.trim());
                    }
                }
                lineCount++;
            }
        } catch (IOException ioe) {
            logger.error("error in parsing file: " + fileName);
            ioe.printStackTrace();
            return null;
        } catch (Exception exc) {
            exc.printStackTrace();
        } finally {
            try {
                breader.close();
                freader.close();
            } catch (Exception exc) {
            }
        }

        createAtomCoords();

        return coords;
    }

    private void createChargeDensityArray() {
        if (densityVector.size() == 0) {
            logger.warn("no charge info found.");
        } else {
            logger.debug("number of 'density points': " + densityVector.size());
            int nchargedata = densityVector.size() / numdensitydata;
            chargeDensity = new float[nchargedata][numdensitydata];
            try {
                int count = 0;
                for (int i = 0; i < nchargedata; i++) {
                    for (int j = 0; j < numdensitydata; j++) {
                        chargeDensity[i][j] = Float
                                .parseFloat(((String) densityVector
                                        .elementAt(count)).replace('D', 'E'));
                        count++;
                    }
                }
            } catch (NumberFormatException exc) {
                logger.error("found non-float value for charge density.");
            }
        }
    }

    private VolumetricData vdata;
    private Vector vdatas = new Vector();

    private void createAtomCoords() {
        String[][] strcell = new String[3][3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                strcell[i][j] = String.valueOf(deltaCell[i][j]
                        * (float) nCell[i]
                        / ((float) ConstParameters.BOHR_2_ANG));
            }
        }
        Cell cell = new Cell(strcell[0], strcell[1], strcell[2]);
        coords.setCell(cell);
        for (int i = 0; i < numAt; i++) {
            double[] tmp = new double[3];
            for (int j = 0; j < 3; j++) {
                tmp[j] = (double) atomPos[i][j] - origin[j];
            }
            Atom atom = new Atom(elementName[i], tmp);
            atom.setNumValenceElectrons((int) numValenceElectrons[i]);
            coords.getAtomList().addAtom(atom);
        }
        coords.convert(AtomCoords.TO_CART, AtomCoords.TO_ANG);
        createChargeDensityArray();
        for (int i = 0; i < chargeDensity.length; i++) {
            vdata = new VolumetricData(chargeDensity[i], nCell, deltaCell,
                    origin);
            // vdata.setTitleAndComment(title, comment);
            coords.setChargeDensity(vdata);
            if (isMolecularOrbital) {
                vdata.setMode(VolumetricData.MOLECULAR_ORBITAL);
                vdata.addMolecularOrbitalIndex(molinds.get(i));
            }
            vdatas.add(vdata);
        }

    }

    public VolumetricData getChargeDensity() {
        return getChargeDensities()[0];
    }

    public VolumetricData[] getChargeDensities() {
        VolumetricData[] vd = new VolumetricData[vdatas.size()];
        for (int i = 0; i < vd.length; i++)
            vd[i] = (VolumetricData) vdatas.get(i);
        return vd;
    }

    private int numAt;
    private int[] nCell = new int[3];
    private float[][] deltaCell = new float[3][3];

    private float[] numValenceElectrons;
    private float[][] atomPos;
    private String[] elementName;
    private float[] origin = new float[3];

    private void allocateArrays() {
        numValenceElectrons = new float[numAt];
        atomPos = new float[numAt][3];
        elementName = new String[numAt];
    }

    private void parseLine(String line) throws IOException {
        if (lineCount <= 1) {
            return;
        }
        String[] arline = line.split("\\s+");
        if (arline == null || arline.length == 0) {
            throw new IOException();
        }

        if (lineCount == 2) {
            if (!parseFirstRow(arline)) {
                throw new IOException();
            }
            return;
        }

        if (lineCount >= 3 && lineCount <= 5) {
            if (!parseCell(arline)) {
                throw new IOException();
            }
            return;
        }

        if (lineCount >= 6 && lineCount < 6 + numAt) {
            if (!parseAtoms(arline)) {
                throw new IOException();
            }

            if (lineCount == numAt + 5) {
                atomParseEnd = true;
            }
            return;
        }

    }

    private boolean parseAtoms(String[] arline) {
        if (arline.length < 5) {
            return false;
        }

        int atnum = 0;
        try {
            atnum = (int) Float.parseFloat(arline[0]);
            numValenceElectrons[lineCount - 6] = Float.parseFloat(arline[1]);
            for (int i = 0; i < 3; i++) {
                atomPos[lineCount - 6][i] = Float.parseFloat(arline[i + 2]);
            }
        } catch (NumberFormatException nfe) {
            nfe.printStackTrace();
            return false;
        } catch (ArrayIndexOutOfBoundsException aiobe) {
            aiobe.printStackTrace();
            return false;
        }

        ElementInfo ei = ElementInfo.getElementInfo();
        elementName[lineCount - 6] = ei.getSymbolFromNumber(atnum);

        return true;
    }

    private boolean isMolecularOrbital = false;

    private boolean parseFirstRow(String[] arline) {
        if (arline.length < 4) {
            return false;
        }
        String num = arline[0];
        try {
            numAt = Integer.parseInt(num);
            if (numAt < 0) {
                isMolecularOrbital = true;
                molecularOrbitalParseEnd = false;
                numAt *= -1;
            }
        } catch (NumberFormatException nfe) {
            logger.error("invalid numAt.");
            return false;
        }
        allocateArrays();
        try {
            origin[0] = Float.parseFloat(arline[1]);
            origin[1] = Float.parseFloat(arline[2]);
            origin[2] = Float.parseFloat(arline[3]);
        } catch (NumberFormatException nfe) {
            logger.error("invalid origin.");
            return false;
        }
        return true;
    }

    private boolean parseCell(String[] arline) {
        if (arline.length < 4) {
            return false;
        }
        try {
            nCell[lineCount - 3] = Integer.parseInt(arline[0]);
            float factor = (float) ConstParameters.BOHR_2_ANG;
            if (nCell[lineCount - 3] < 0) {
                factor = 1f;
                nCell[lineCount - 3] *= -1;
            }
            numdensitydata *= nCell[lineCount - 3];
            for (int i = 0; i < 3; i++) {
                deltaCell[lineCount - 3][i] = Float.parseFloat(arline[i + 1])
                        * factor;
            }
        } catch (NumberFormatException npe) {
            npe.printStackTrace();
            return false;
        } catch (ArrayIndexOutOfBoundsException aiob) {
            aiob.printStackTrace();
            return false;
        }
        return true;
    }

}
