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

import org.apache.log4j.Logger;

import ciss.phase_viewer.inputinterface.InputInterface;
import ciss.phase_viewer.inputinterface.inputinterfacetable.ComboBoxChoices;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTable;
import ciss.phase_viewer.settings.PropertiesManager;

public class ElementInfo implements ComboBoxChoices {
    private static Logger logger = Logger
            .getLogger(ElementInfo.class.getName());

    int MAX_ELEMENTS;
    int NOSUCHELEMENT;
    public int num_elements; /* !< 元素数 */

    public ElementDef edef[]; /* !< 元素情報を保持するオブジェクト */

    private String elementInfoFile = new String();

    private static ElementInfo elementInfo;

    public static ElementInfo getElementInfo() {
        if (elementInfo == null) {
            elementInfo = new ElementInfo();
        }
        return elementInfo;
    }

    public static void updateElement(InputInterface inpint) {
        if (inpint == null) {
            logger.error("inputinterface is null.");
            return;
        }

        InputInterfaceTable table = inpint
                .getInputInterfaceTable("structure.atom_list.atoms");
        if (table == null) {
            logger.error("tablecolumns are null.");
            return;
        }
        String[] cols = table.getColumnIdentifiers();
        if (cols == null) {
            return;
        }

        int elemIndex = table.getIndexFor("element");
        if (elemIndex < 0) {
            logger.debug("couldn't find 'element' column");
            return;
        }

        Vector vec = table.getTableData();
        if (vec == null || vec.size() == 0) {
            logger.debug("no data.");
            return;
        }

        Vector vec2 = new Vector();
        for (int i = 0; i < vec.size(); i++) {
            String[] row = (String[]) vec.elementAt(i);
            String elem = row[elemIndex];
            if (elem.trim().length() != 0) {
                vec2.addElement(elem.trim());
            }

            logger.debug("element of atom no. " + i + ": " + elem);
        }
        if (vec2.size() == 0) {
            return;
        }

        String[] string = new String[vec2.size()];
        vec2.copyInto(string);
        Vector foo = Utils.removeDupli(string);
        String[] bar = new String[foo.size()];
        foo.copyInto(bar);
        Arrays.sort(bar);

        ElementInfo info = ElementInfo.getElementInfo();
        InputInterfaceTable tableelem = inpint
                .getInputInterfaceTable("structure.element_list");
        Vector exiData = tableelem.getTableData();
        String[] exiIdents = tableelem.getColumnIdentifiers();
        int exiElemInd = tableelem.getIndexFor("element");

        String[] elements = new String[exiData.size()];
        if (exiElemInd >= 0) {
            for (int i = 0; i < exiData.size(); i++) {
                elements[i] = ((String[]) exiData.get(i))[exiElemInd];
            }
            Arrays.sort(elements);
            if (Arrays.equals(bar, elements))
                return;
        }

        tableelem.initializeData();

        String[] identifiers = { "element", "atomicnumber", "mass", "zeta",
                "deviation" };
        for (int i = 0; i < foo.size(); i++) {
            String[] dat = null;
            String el = (String) foo.get(i);
            String[] useThis = null;
            if (exiData != null) {
                for (int j = 0; j < exiData.size(); j++) {
                    String[] exirow = (String[]) exiData.elementAt(j);
                    if (el.equals(exirow[exiElemInd])) {
                        dat = exirow;
                        identifiers = exiIdents;
                    }
                }
            }
            if (dat == null) {
                dat = new String[5];
                dat[0] = el;
                dat[1] = String.valueOf(info.getNumberFromSymbol(el));
                dat[2] = info.getMassFromSymbol(el);
                dat[3] = "0.0";
                dat[4] = "1.83";
            }
            tableelem.insertTableDataRow(identifiers, dat, i);
        }
        inpint.selectRoot();
    }

    /**
     * いくつか定数を定義した後, 必要なプロパティーファイルにアクセスするための オブジェクトを取得, その情報を元に元素情報を保持するオブジェクトに
     * 情報を格納する.
     */
    public ElementInfo() {
        // ConstParameters cp = new ConstParameters();
        this.MAX_ELEMENTS = ConstParameters.MAX_ELEMENTS;
        this.NOSUCHELEMENT = ConstParameters.NOSUCHELEMENT;
        elementInfoFile = (PropertiesManager
                .getGlobalProperties(PropertiesManager.PROPERTIES_ELEMINFO))
                .getFileName();
        setEdef();
        num_elements = edef.length;
    }

    public int getNumEl() {
        int num = 0;
        for (int i = 0; i < edef.length; i++) {
            if (edef[i] == null || edef[i].getSymbol() == null)
                continue;
            num++;
        }
        return num;
    }

    /**
     * 元素の情報を持ったオブジェクトの配列を返す.
     * 
     * @return 元素情報
     */
    public ElementDef[] getElementDef() {
        return edef;
    }

    /**
     * 元素を新たに"足す"
     * 
     * @param ed
     *            新たに足すべき元素情報
     */
    public void addElementDef(ElementDef ed) {
        ElementDef[] newdef = new ElementDef[edef.length + 1];
        for (int i = 0; i < edef.length; i++) {
            newdef[i] = edef[i];
        }
        newdef[newdef.length - 1] = ed;
        edef = newdef;
    }

    private void setEdef() {
        String str = new String();
        Vector vec = new Vector();
        ElementDef ed = new ElementDef(); // 'null' element
        ed.setSymbol("");
        ed.setRadius(0);
        ed.setCovalentRadius(0);
        ed.setMass("0");
        ed.setColor(new float[] { 0, 0, 0 });
        vec.addElement(ed);
        try {
            FileReader reader = new FileReader(elementInfoFile);
            BufferedReader br = new BufferedReader(reader);
            while ((str = br.readLine()) != null) {
                str = str.trim();
                if (!(str.startsWith("#"))) {
                    ElementDef edf = new ElementDef();
                    StringTokenizer st = new StringTokenizer(str);
                    edf.setSymbol(st.nextToken());
                    String rad = st.nextToken(); // rad
                    edf.setRadius(Float.parseFloat(rad));

                    String covrad = st.nextToken(); // covrad
                    edf.setCovalentRadius(Double.parseDouble(covrad));

                    String r = st.nextToken(); // r
                    String g = st.nextToken(); // g
                    String b = st.nextToken(); // b
                    edf.setColor(new float[] { Float.parseFloat(r),
                            Float.parseFloat(g), Float.parseFloat(b) });
                    String mass = st.nextToken();
                    mass = Double.toString(Double.parseDouble(mass)
                            * ConstParameters.AMU);
                    char value[] = new char[10];
                    for (int ic = 0; ic < 10; ic++) {
                        value[ic] = mass.charAt(ic);
                    }
                    edf.setMass(new String(value, 0, 10));
                    if (st.hasMoreTokens()) {
                        String hmax = st.nextToken();
                        edf.hasHbond(true);
                        edf.setMaxHbondDistance(Double.parseDouble(hmax));
                    }
                    vec.addElement(edf);
                }
            }
            reader.close();
        } catch (Exception e) {
            logger.error("failed to read element info");
            e.printStackTrace();
        }
        edef = new ElementDef[vec.size()];
        vec.copyInto(edef);
    }

    /**
     * 原子番号から元素名を取得する.
     * 
     * @param iat
     *            原子番号
     * @return 元素記号
     */
    public String getSymbolFromNumber(int iat) {
        if (iat >= num_elements) {
            return ("");
        } else if (iat < 1) {
            return ("");
        }
        return edef[iat].getSymbol();
    }

    /**
     * 原子番号からその元素の質量を取得する.
     * 
     * @param iat
     *            原子番号
     * @return iatに対応する元素の質量
     */
    public String getMassFromNumber(int iat) {
        if (iat >= num_elements) {
            return "";
        } else if (iat < 1) {
            return "";
        }
        return edef[iat].getMass();
    }

    /**
     * 元素記号からその元素の質量を取得する.
     * 
     * @param symbol
     *            元素記号
     * @return symbolに対応する元素の質量
     */
    public String getMassFromSymbol(String symbol) {
        int i, iret;
        iret = 0;
        for (i = 1; i < num_elements; i++) {
            String comp = edef[i].getSymbol() + "\\d*";
            if (symbol.matches(comp))
                return edef[i].getMass();
        }
        return "";
    }

    /**
     * 元素記号から原子番号を取得する.
     * 
     * @param symbol
     *            元素記号
     * @return symbolに対応した原子番号
     */
    public int getNumberFromSymbol(String symbol) {
        int i, iret;
        iret = 0;
        for (i = 1; i < num_elements; i++) {
            String comp = edef[i].getSymbol() + "\\d*";
            if (symbol.matches(comp))
                return i;
        }
        return -1;
    }

    /**
     * 「元素名配列」を返す.
     * 
     * @return 元素名の配列.
     */
    public Object[] getChoices() {
        String[] ret = new String[num_elements];
        ret[0] = "";
        for (int i = 1; i < num_elements; i++) {
            ret[i] = getSymbolFromNumber(i);
        }
        return ret;
    }

    /**
     * index=0 -> 元素名配列, index=1 -> 原子番号配列. index=2 ->「質量」配列 ...
     * ということに差し当たりしておく.
     * 
     * @param index
     *            どの選択肢種が欲しいか.
     * @return 上記いずれか.
     */
    public Object[] getChoices(int index) {
        if (index == 0) {
            return getChoices();
        } else if (index == 1) {
            String[] ret = new String[num_elements];
            for (int i = 0; i < num_elements; i++) {
                ret[i] = String.valueOf(i + 1);
            }
            return ret;
        } else if (index == 2) {
            String[] ret = new String[num_elements];
            ret[0] = "";
            for (int i = 1; i < num_elements; i++) {
                ret[i] = getMassFromNumber(i);
            }
            return ret;
        }
        logger.error("wrong specification of index!");
        return null;
    }

    /**
     * index=element -> 元素名配列, index=atomic_number -> 原子番号配列. index=mass ->
     * 「質量」配列 ... ということに差し当たりしておく.
     * 
     * @param index
     *            どの選択肢種が欲しいか.
     * @return 上記いずれか.
     */
    public Object[] getChoices(String index) {
        if (index.trim().equals("element")) {
            return getChoices();
        } else if (index.trim().equals("atomic_number")) {
            String[] ret = new String[num_elements];
            for (int i = 0; i < num_elements; i++) {
                ret[i] = String.valueOf(i + 1);
            }
            return ret;
        } else if (index.trim().equals("mass")) {
            String[] ret = new String[num_elements];
            for (int i = 0; i < num_elements; i++) {
                ret[i] = getMassFromNumber(i);
            }
            return ret;
        }
        logger.error("wrong specification of index!");
        return null;
    }

    public String getToolTipTexts(int index) {
        return "";
    }

    private ElementDef[] hbondCandidates;

    public ElementDef[] getHBondDonorAcceptorCandidates() {
        if (hbondCandidates == null) {
            Vector vec = new Vector();
            for (int i = 0; i < edef.length; i++) {
                if (edef[i].hasHbond()) {
                    vec.addElement(edef[i]);
                }
            }
            hbondCandidates = new ElementDef[vec.size()];
            vec.copyInto(hbondCandidates);
        }
        return hbondCandidates;
    }

    public void toDisk() {
        try {
            String write = toString();
            BufferedWriter writer = new BufferedWriter(new FileWriter(
                    elementInfoFile));
            writer.write(write, 0, write.length());
            writer.flush();
            writer.close();
        } catch (Exception e) {
            System.out.println("failed write to elementinfo");
        }
    }

    public String toString() {
        String LS = System.getProperty("line.separator");
        String ret = "#element(symbol)    radius(A)   covalent_radius (A)    color(rgb)    mass   Hbond"
                + LS;
        for (int i = 0; i < edef.length; i++) {
            if (edef[i] == null)
                continue;
            String str = edef[i].toString();
            if (str.trim().length() == 0)
                continue;
            ret += str + LS;
        }
        return ret;
    }

}
