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

import org.apache.log4j.Logger;
import org.jdom.Element;

import ciss.phase_viewer.common.TaggedString;

/**
 * 原子を表すクラス. 原子には次の属性がある. - 元素名, - 座標, - 働く力, - その他auxiliaryな情報;
 * TaggedString配列に保存される. たとえばphaseの場合mobile情報など.
 * 
 * @author KOGA, Junichiro
 */
public class Atom {
    private static Logger logger = Logger.getLogger(Atom.class.getName());

    private String elementName = "";

    private String[] pos;

    private TaggedString[] auxil;

    private String[] force;

    private boolean boundary = false;

    /**
     * 元素名と座標を指定.
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標
     */
    public Atom(String elementName, String[] pos) {
        this.elementName = elementName;
        for (int i = 0; i < pos.length; i++)
            pos[i] = pos[i].replaceAll("d", "e").replaceAll("D", "E");
        this.pos = pos;
    }

    /**
     * 元素名と座標のほか,「原子に働く力」も指定.
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標
     * @param force
     *            「原子に働く力」
     */
    public Atom(String elementName, String[] pos, String[] force) {
        this.elementName = elementName;
        for (int i = 0; i < pos.length; i++)
            pos[i] = pos[i].replaceAll("d", "e").replaceAll("D", "E");
        this.pos = pos;
        this.force = force;
    }

    /**
     * 元素名と座標のほか,「auxiliaryな情報」も指定.
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標
     * @param auxil
     *            auxiliaryな情報を格納したTaggedString配列.
     */
    public Atom(String elementName, String[] pos, TaggedString[] auxil) {
        this.elementName = elementName;
        for (int i = 0; i < pos.length; i++)
            pos[i] = pos[i].replaceAll("d", "e").replaceAll("D", "E");
        this.pos = pos;
        this.auxil = auxil;
    }

    /**
     * 元素名と, double 配列で座標を指定. さらにauxiliaryな情報も.
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標, double配列で.
     * @param auxil
     *            auxiliaryな情報を格納したTaggedString配列.
     */
    public Atom(String elementName, double[] pos, TaggedString[] auxil) {
        this.elementName = elementName;
        this.pos = new String[3];
        for (int i = 0; i < 3; i++) {
            this.pos[i] = String.valueOf(pos[i]);
        }
        this.auxil = auxil;
    }

    private boolean issublat = false;

    private boolean savesublat = false;

    /**
     * 副格子の原子な場合, true.
     * 
     * @return
     */
    public boolean isSubLatticeAtom() {
        return issublat;
    }

    /**
     * 副格子の原子を保存対象にしたいばあいtrueを設定する.
     * 
     * @param savesublat
     *            副格子の原子を保存するかいなか
     */
    public void saveSubLatticeAtom(boolean savesublat) {
        this.savesublat = savesublat;
    }

    /**
     * 副格子の原子を保存対象にしたい場合はtrue.
     * 
     * @return
     */
    public boolean saveSubLatticeAtom() {
        return this.savesublat;
    }

    /**
     * この原子オブジェクトが副格子のそれなのか否かを設定.
     * 
     * @param issublat
     */
    public void isSubLatticeAtom(boolean issublat) {
        this.issublat = issublat;
    }

    /**
     * 描画上の都合のみで存在している原子か否かを判定. たとえば, 境界上の原子や副格子の原子.
     * 
     * @return 描画上の都合のみで存在している原子の場合true.
     */
    public boolean isVirtualAtom() {
        return issublat || boundary;
    }

    /**
     * 元素名と, double配列の座標を指定.
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標, double配列で.
     */
    public Atom(String elementName, double[] pos) {
        this.elementName = elementName;
        this.pos = new String[3];
        for (int i = 0; i < 3; i++) {
            this.pos[i] = String.valueOf(pos[i]);
        }
    }

    /**
     * auxiliaryな情報をjava.util.Vectorを使って指定
     * 
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標
     * @param auxil
     *            auxiliaryな情報を格納したTaggedString配列を足しこんだVector.
     */
    public Atom(String elementName, String[] pos, Vector aux) {
        this.elementName = elementName;
        for (int i = 0; i < pos.length; i++)
            pos[i] = pos[i].replaceAll("d", "e").replaceAll("D", "E");
        this.pos = pos;
        if (aux != null) {
            auxil = new TaggedString[aux.size()];
            aux.copyInto(auxil);
        }
    }

    /**
     * @param elementName
     *            元素名
     * @param pos
     *            原子の座標, double配列で.
     * @param auxil
     *            auxiliaryな情報を格納したTaggedString配列を足しこんだVector.
     */
    public Atom(String elementName, double[] pos, Vector aux) {
        this.elementName = elementName;
        this.pos = new String[3];
        for (int i = 0; i < 3; i++) {
            this.pos[i] = String.valueOf(pos[i]);
        }
        if (aux != null) {
            auxil = new TaggedString[aux.size()];
            for (int i = 0; i < aux.size(); i++) {
                try {
                    auxil[i] = (TaggedString) aux.elementAt(i);
                } catch (ClassCastException cce) {
                }
            }
        }
    }

    /**
     * コピーを生成
     * 
     * @return コピーされたAtomオブジェクト
     */
    public Atom getCopy() {
        String elembuf = new String(elementName);
        String[] posbuf = new String[3];
        for (int i = 0; i < posbuf.length; i++) {
            posbuf[i] = new String(pos[i]);
        }
        TaggedString[] aubuf = null;
        if (auxil != null && auxil.length != 0) {
            aubuf = new TaggedString[auxil.length];
            for (int i = 0; i < aubuf.length; i++) {
                if (auxil[i].getTag() != null && auxil[i].getValue() != null) {
                    aubuf[i] = new TaggedString(auxil[i].getTag(),
                            auxil[i].getValue());
                }
            }
        }
        Atom ret = new Atom(elembuf, posbuf, aubuf);
        ret.setForce(force);
        ret.isBoundaryAtom(isBoundaryAtom());
        ret.isSubLatticeAtom(isSubLatticeAtom());
        ret.isInvSymAtom(isInvSymAtom);
        return ret;
    }

    boolean isInvSymAtom = false;

    /**
     * 反転対称の都合で描画された原子か否かを返す.
     * 
     * @return 反転対称の都合で描画されている原子ならtrue.
     */
    public boolean isInvSymAtom() {
        return this.isInvSymAtom;
    }

    /**
     * 反転対称の都合で描画されている原子ならtrue, そうでない普通の原子ならfalseをセットする.
     * 
     * @param 上記の通り
     *            , 当然普通はfalse.
     */
    public void isInvSymAtom(boolean isInvSymAtom) {
        this.isInvSymAtom = isInvSymAtom;
    }

    /**
     * 境界上の原子の場合, trueを返す.
     * 
     * @return 境界上の原子の場合true
     */
    public boolean isBoundaryAtom() {
        return this.boundary;
    }

    /**
     * 境界上の原子か否かを設定.
     * 
     * @param boundary
     *            境界上の原子の場合true
     */
    public void isBoundaryAtom(boolean boundary) {
        this.boundary = boundary;
    }

    private int numValenceElectrons = -1; // 価電子の数; 必ずセットする,
                                          // というわけでもなく気まぐれなので要注意.

    /**
     * 可能な場合, 価電子の数を設定する.
     * 
     * @param numValenceAtoms
     *            価電子の数.
     */
    public void setNumValenceElectrons(int numValenceElectrons) {
        this.numValenceElectrons = numValenceElectrons;
    }

    /**
     * 価電子の数を取得する. 設定されていない場合-1という数を返す.
     * 
     * @return 価電子の数
     */
    public int getNumValenceElectrons() {
        return this.numValenceElectrons;
    }

    /**
     * 元素名取得.
     * 
     * @return 元素名
     */
    public String getElementName() {
        return this.elementName;
    }

    /**
     * 元素名セット
     * 
     * @param elementName
     *            元素名
     */
    public void setElementName(String elementName) {
        this.elementName = elementName;
    }

    /**
     * 「原子に働く力」を取得
     * 
     * @return 原子に働く力
     */
    public String[] getForce() {
        return this.force;
    }

    /**
     * 「原子に働く力」をdoubleで取得
     * 
     * @return 原子に働く力; doubleに変換できない場合nullを返す.
     */
    public double[] getForceDouble() {
        if (force == null || force.length < 3) {
            return null;
        }
        double[] ret = new double[3];
        for (int i = 0; i < 3; i++) {
            try {
                ret[i] = Double.parseDouble(force[i]);
            } catch (Exception exc) {
                return null;
            }
        }
        return ret;
    }

    /**
     * 「原子に働く力」をセット
     * 
     * @param force
     *            原子に働く力
     */
    public void setForce(String[] force) {
        this.force = force;
    }

    /**
     * 座標を取得
     * 
     * @return 座標
     */
    public String[] getPos() {
        return this.pos;
    }

    /**
     * 座標を, double配列として取得
     * 
     * @return double配列で表された座標; doubleで表現できない場合null.
     */
    public double[] getDouble() {
        double[] ret = new double[3];
        for (int i = 0; i < 3; i++) {
            try {
                ret[i] = Double.parseDouble(pos[i]);
            } catch (NumberFormatException nfe) {
                logger.warn("failed to get 'double' representation for atom coords.");
                return null;
            }
        }
        return ret;
    }

    /**
     * 座標をセット
     * 
     * @param pos
     *            座標
     */
    public void setPos(String[] pos) {
        for (int i = 0; i < pos.length; i++)
            pos[i] = pos[i].replaceAll("d", "e").replaceAll("D", "E");
        this.pos = pos;
    }

    /**
     * double配列で座標をセット
     * 
     * @param pos
     *            double配列で指定された座標
     */
    public void setPos(double[] pos) {
        if (pos == null || pos.length < 3) {
            return;
        }
        logger.debug("setting pos: " + pos[0] + " " + pos[1] + " " + pos[2]);
        for (int i = 0; i < 3; i++) {
            this.pos[i] = Double.toString(pos[i]);
        }
    }

    /**
     * auxiliaryな情報を取得
     * 
     * @return auxiliaryな情報; TaggedString#getTag()でその情報のタグ, getValue()で値を取得
     */
    public TaggedString[] getAuxil() {
        return this.auxil;
    }

    /**
     * キーを元に, auxilな情報を得る.
     * 
     * @param key
     *            元となるキー.
     * @return あった場合値, なかった場合null.
     */
    public String getProperty(String key) {
        if (auxil == null)
            return null;
        for (int i = 0; i < auxil.length; i++) {
            if (auxil[i].getTag().equalsIgnoreCase(key))
                return auxil[i].getValue();
        }
        return null;
    }

    /**
     * auxiliaryな情報をセット.
     * 
     * @param auxil
     */
    public void setAuxil(TaggedString[] auxil_) {
        if (auxil_ == null || auxil_.length == 0) {
            return;
        }
        if (auxil != null && auxil.length != 0) {
            Vector vector = new Vector();
            TaggedString[] tmp = new TaggedString[auxil.length];
            for (int i = 0; i < tmp.length; i++) {
                tmp[i] = auxil[i];
            }
            for (int j = 0; j < auxil_.length; j++) {
                boolean exi = false;
                for (int i = 0; i < tmp.length; i++) {
                    if (tmp[i].getTag().equals(auxil_[j].getTag())) {
                        tmp[i].setValue(auxil_[j].getValue());
                        exi = true;
                    }
                }
                if (!exi) {
                    vector.addElement(auxil_[j]);
                }
            }
            if (vector.size() != 0) {
                auxil = new TaggedString[tmp.length + vector.size()];
                for (int i = 0; i < tmp.length; i++) {
                    auxil[i] = tmp[i];
                }
                for (int i = 0; i < vector.size(); i++) {
                    auxil[i + tmp.length] = (TaggedString) vector.get(i);
                }
            } else {
                auxil = tmp;
            }
        } else {
            this.auxil = auxil_;
        }
    }

    /**
     * auxiliaryな情報を, Vectorでセット
     * 
     * @param aux
     *            auxiliaryな情報を足しこんだVector
     */
    public void setAuxil(Vector aux) {
        if (aux != null) {
            TaggedString[] tmpauxil = new TaggedString[aux.size()];
            aux.copyInto(tmpauxil);
            setAuxil(tmpauxil);
        }
    }

    /**
     * この原子の情報を, XMLElementととして返す
     * 
     * @return XMLエレメントとして表された原子の情報
     */
    public Element getXMLElement() {
        Element atom = new Element("atom");
        Element elemname = new Element("element").setText(elementName);
        atom.addContent(elemname);
        if (pos != null && pos.length >= 3) {
            Element elex = new Element("posx").setText(pos[0]);
            Element eley = new Element("posy").setText(pos[1]);
            Element elez = new Element("posz").setText(pos[2]);
            atom.addContent(elex);
            atom.addContent(eley);
            atom.addContent(elez);
        }
        if (force != null && force.length >= 3) {
            Element forcx = new Element("forcx").setText(force[0]);
            Element forcy = new Element("forcy").setText(force[1]);
            Element forcz = new Element("forcz").setText(force[2]);
            atom.addContent(forcx);
            atom.addContent(forcy);
            atom.addContent(forcz);
        }
        if (auxil != null && auxil.length != 0) {
            for (int i = 0; i < auxil.length; i++) {
                if (auxil[i].getValue().trim().length() != 0) {
                    Element aux = new Element(auxil[i].getTag())
                            .setText(auxil[i].getValue());
                    atom.addContent(aux);
                }
            }
        }
        return atom;
    }

    /**
     * 指定のXMLエレメントからAtomオブジェクトを作成
     * 
     * @param atom
     *            XMLエレメント
     * @return 結果作成されたAtomオブジェクト
     */
    public static Atom createFrom(Element atom) {
        Atom ret = null;
        java.util.List list = atom.getChildren();

        String ele = null;
        String px = null;
        String py = null;
        String pz = null;
        String forcx = null;
        String forcy = null;
        String forcz = null;
        Vector auxvector = new Vector();
        for (int i = 0; i < list.size(); i++) {
            Element element = (Element) list.get(i);
            String ident = element.getName().trim();
            if (ident.equals("element")) {
                ele = element.getValue().trim();
            } else if (ident.equals("posx")) {
                px = element.getValue().trim();
            } else if (ident.equals("posy")) {
                py = element.getValue().trim();
            } else if (ident.equals("posz")) {
                pz = element.getValue().trim();
            } else if (ident.equals("forcx")) {
                forcx = element.getValue().trim();
            } else if (ident.equals("forcy")) {
                forcy = element.getValue().trim();
            } else if (ident.equals("forcz")) {
                forcz = element.getValue().trim();
            } else {
                TaggedString tag = new TaggedString(ident, element.getValue()
                        .trim());
                auxvector.addElement(tag);
            }
        }
        if (ele != null && px != null && py != null && pz != null) {
            ret = new Atom(ele, new String[] { px, py, pz });
        }
        if (ret == null) {
            logger.error("invalid atom");
            return null;
        }

        if (forcx != null && forcy != null && forcz != null) {
            ret.setForce(new String[] { forcx, forcy, forcz });
        }

        if (auxvector.size() != 0) {
            ret.setAuxil(auxvector);
        }

        return ret;
    }

    public void nullify() {
        elementName = null;
        pos = null;
        auxil = null;
        force = null;
    }

    public String toString() {
        String ret = "";

        ret = "symbol: " + elementName + " x: " + pos[0] + " y: " + pos[1]
                + " z: " + pos[2];
        if (this.auxil != null) {
            for (int i = 0; i < auxil.length; i++) {
                if (auxil[i] != null && auxil[i].getTag() != null
                        && auxil[i].getValue() != null) {
                    ret += " " + auxil[i].getTag() + ": " + auxil[i].getValue();
                }
            }
        }
        return ret;
    }

    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Atom))
            return false;
        return epsilonEquals((Atom) obj);
        // return ((Atom) obj).pos[0].equals(pos[0])
        // && ((Atom) obj).pos[1].equals(pos[1])
        // && ((Atom) obj).pos[2].equals(pos[2]);
    }

    private double epsilon = 0.0000001d;

    public boolean epsilonEquals(Atom atom) {
        if (atom == null)
            return false;

        double[] mypos = getDouble();
        double[] frompos = atom.getDouble();
        String elem = atom.getElementName();

        return Math.abs(mypos[0] - frompos[0]) < epsilon
                && Math.abs(mypos[1] - frompos[1]) < epsilon
                && Math.abs(mypos[2] - frompos[2]) < epsilon
                && elem.equalsIgnoreCase(elementName);
    }
}
