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

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

import ciss.phase_viewer.common.TaggedString;

/**
 * Atomオブジェクトの参照を持っているLinkedList. 基本的にはLinkedListだが, -
 * Atomオブジェクトしか受け付けない/返さないメソッドがある. - UndoRedoオブジェクトの操作を行う などの利点がある.
 * 
 * @author KOGA, Junichiro
 */
public class AtomList extends LinkedList {

    private static Logger logger = Logger.getLogger(AtomList.class.getName());

    private UndoRedo undoRedo;

    private boolean updateUndoStack = true;

    private AtomCoords parent;

    private AtomStateList atomState;

    /**
     * 関連付けたいAtomCoordsオブジェクトを引数にとるコンストラクター
     * 
     * @param parent
     *            関連付けたいAtomCoordsオブジェクト
     */
    public AtomList(AtomCoords parent) {
        this.parent = parent;
        undoRedo = new UndoRedo(this);
    }

    void setState(AtomStateList state) {
        this.atomState = state;
    }

    /**
     * 初期原子も指定できるコンストラクター
     * 
     * @param atom
     *            初期に登録したい原子
     * @param parent
     *            関連付けたいAtomCoordsオブジェクト
     */
    public AtomList(Atom atom, AtomCoords parent) {
        this.parent = parent;
        undoRedo = new UndoRedo(this);
        addAtom(atom);
    }

    /**
     * deep cloneを作成. ただし, parentというフィールドは共通, 原子の情報しかコピーされない, などの制約が.
     * 
     * @return このAtomListのdeep clone
     */
    public AtomList getCopy() {
        AtomList newList = new AtomList(parent);
        for (int i = 0; i < size(); i++) {
            Atom at = getAtomAt(i);
            newList.addAtomAt(i, at.getCopy());
        }
        return newList;
    }

    public void nullify() {
        for (int i = getNumAt() - 1; i >= 0; i--) {
            Atom at = getAtomAt(i);
            at.nullify();
        }
        if (undoRedo != null) {
            undoRedo.nullify();
            undoRedo = null;
        }
        clear();
        parent = null;
    }

    /**
     * このリストをXMLエレメントに変換
     * 
     * @return このリストの, XMLエレメント表記
     */
    public Element getXMLElement() {
        Element element = new Element("atomlist");
        element.setAttribute(new Attribute("numatoms", String.valueOf(size())));
        for (int i = 0; i < size(); i++) {
            Atom atom = getAtomAt(i);
            element.addContent(atom.getXMLElement());
        }
        return element;
    }

    /**
     * 指定のXMLエレメントからこのクラスのインスタンスを作成
     * 
     * @param atomList
     *            XMLエレメント; getXMLElementで作成されているものとする
     */
    public void createFrom(Element atomList) {
        java.util.List at = atomList.getChildren("atom");
        if (at == null || at.size() == 0) {
            logger.error("invalid atomlist");
            return;
        }
        for (int i = 0; i < at.size(); i++) {
            Atom atom = Atom.createFrom((Element) at.get(i));
            addAtom(atom);
        }
    }

    /**
     * このオブジェクトに関連付けられているAtomCoordsオブジェクトを取得
     * 
     * @return このオブジェクトに関連付けられているAtomCoords
     */
    public AtomCoords getParent() {
        return parent;
    }

    protected UndoRedo getUndoRedoObject() {
        return undoRedo;
    }

    /**
     * リストに原子を足しこむ.
     * 
     * @param atom
     *            足しこみたい原子
     */
    public void addAtom(Atom atom) {
        undoRedo.addEntry(atom, size(), UndoRedoObject.ADD);
        addLast(atom);
        if (atomState != null)
            atomState.atomAdded();
    }

    /**
     * リストに原子を足しこむ.
     * 
     * @param atom
     *            足しこみたい原子
     * @param updateStack
     *            undo/redo用のStackを更新しない場合false
     */
    public void addAtom(Atom atom, boolean updateStack) {
        if (updateStack)
            undoRedo.addEntry(atom, size(), UndoRedoObject.ADD);
        addLast(atom);
        if (atomState != null)
            atomState.atomAdded();
    }

    /**
     * リストに原子を足しこむ. 足しこむ場所も指定できる.
     * 
     * @param index
     *            インデックス
     * @param atom
     *            足しこみたい原子
     */
    public void addAtomAt(int index, Atom atom) {
        // undoRedo.addEntry(atom, size(),UndoRedoObject.ADD);
        undoRedo.addEntry(atom, index, UndoRedoObject.ADD);
        add(index, atom);
        if (atomState != null)
            atomState.atomAdded(index);
    }

    /**
     * リストに原子を足しこむ. 足しこむ場所も指定できる.
     * 
     * @param index
     *            インデックス
     * @param atom
     *            足しこみたい原子
     * @param updateStack
     *            undo/redo用Stackを更新しない場合false
     */
    public void addAtomAt(int index, Atom atom, boolean updateStack) {
        // undoRedo.addEntry(atom, size(),UndoRedoObject.ADD);
        if (updateStack)
            undoRedo.addEntry(atom, index, UndoRedoObject.ADD);
        add(index, atom);
        if (atomState != null)
            atomState.atomAdded(index);
    }

    /**
     * 指定の元素名、座標のAtomを足しこむ
     * 
     * @param elementName
     *            足しこみたい原子の元素名
     * @param pos
     *            足しこみたい原子の座標
     */
    public void addAtom(String elementName, String[] pos) {
        Atom atom = new Atom(elementName, pos);
        undoRedo.addEntry(atom, size(), UndoRedoObject.ADD);
        addLast(atom);
        if (atomState != null)
            atomState.atomAdded();
    }

    /**
     * 指定の元素名, 座標, auxiliary情報のAtomを足しこむ
     * 
     * @param elementName
     *            足しこみたい原子の元素名
     * @param pos
     *            足しこみたい原子の座標
     * @param auxil
     *            足しこみたい原子のauxiliaryな情報
     */
    public void addAtom(String elementName, String[] pos, TaggedString[] auxil) {
        Atom atom = new Atom(elementName, pos, auxil);
        undoRedo.addEntry(atom, size(), UndoRedoObject.ADD);
        addLast(atom);
        if (atomState != null)
            atomState.atomAdded();
    }

    /**
     * 指定の元素名, 座標, auxiliary情報のAtomを足しこむ.
     * 
     * @param elementName
     *            足しこみたい原子の元素名
     * @param pos
     *            足しこみたい原子の座標
     * @param auxil
     *            足しこみたい原子のauxiliaryな情報. TaggedString配列のVector
     */
    public void addAtom(String elementName, String[] pos, Vector auxil) {
        Atom atom = new Atom(elementName, pos, auxil);
        undoRedo.addEntry(atom, size(), UndoRedoObject.ADD);
        addLast(atom);
        if (atomState != null)
            atomState.atomAdded();
    }

    /**
     * 一回のアンドゥで何回先までさかのぼるかを指定できる. 普通は一回だが, いっぺんに何かを変更した場合などは複数回も有り得る
     * 
     * @param count
     *            一回のアンドゥで何回遡るか
     */
    public void setUndoCount(int count) {
        undoRedo.setUndoCount(count);
    }

    /**
     * 一番最初のコマまで戻る.
     * 
     * @return 実際は何もしなかった場合false.
     */
    public boolean undoToFirst() {
        boolean ret = undoRedo.undoToFirstEntry();
        return ret;
    }

    /**
     * アンドゥリドゥスタックを初期化
     */
    public void initializeUndoRedo() {
        if (undoRedo == null)
            undoRedo = new UndoRedo(this);
        undoRedo.init();
    }

    /**
     * 指定の原子を取得
     * 
     * @param i
     *            欲しい原子のインデックス
     */
    public Atom getAtomAt(int i) {
        Atom atom;
        try {
            atom = (Atom) get(i);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("failed to get atom at: " + i);
            return null;
        }
        return atom;
    }

    /**
     * 指定の原子を置き換える
     * 
     * @param i
     *            置き換えたい原子のインデックス
     * @param atom
     *            新たに登録したい原子
     */
    public void replaceAtomAt(int i, Atom atom) {
        logger.debug("atom now: " + getAtomAt(i));
        undoRedo.addEntry(getAtomAt(i), i, UndoRedoObject.REPLACE);
        set(i, atom);
        if (atomState != null)
            atomState.atomModified(i);
    }

    /**
     * 指定の原子を置き換える
     * 
     * @param i
     *            置き換えたい原子のインデックス
     * @param atom
     *            新たに登録したい原子
     * @param undo
     *            /redo用のStackを更新する場合によぶ
     */
    public void replaceAtomAt(int i, Atom atom, boolean updateStack) {
        logger.debug("atom now: " + getAtomAt(i));
        if (updateStack) {
            undoRedo.addEntry(getAtomAt(i), i, UndoRedoObject.REPLACE);
        }
        set(i, atom);
        if (atomState != null)
            atomState.atomModified(i);
    }

    /**
     * 指定の原子を削除
     * 
     * @param i
     *            削除したい原子のインデックス
     */
    public void removeAtomAt(int i) {
        undoRedo.addEntry(getAtomAt(i), i, UndoRedoObject.REMOVE);
        remove(i);
        if (atomState != null)
            atomState.atomRemoved(i);
    }

    /**
     * 指定の原子を削除
     * 
     * @param i
     *            削除したい原子のインデックス
     * @param updateStack
     *            undo/redo用のStackを更新しない場合false
     */
    public void removeAtomAt(int i, boolean updateStack) {
        if (updateStack) {
            undoRedo.addEntry(getAtomAt(i), i, UndoRedoObject.REMOVE);
        }
        remove(i);
        if (atomState != null)
            atomState.atomRemoved(i);
    }

    /**
     * 指定の原子を削除
     * 
     * @param atom
     *            削除したい原子
     * @return TODO
     */
    public boolean removeAtom(Atom atom) {
        return removeAtom(atom, true);
    }

    /**
     * 指定の原子を削除
     * 
     * @param atom
     *            削除したい原子
     * @param undo
     *            /redoのスタックを更新する場合true.
     * @return TODO
     */
    public boolean removeAtom(Atom atom, boolean updateStack) {
        int index = indexOf(atom);
        if (index < 0) {
            index = size();
        }
        if (updateStack)
            undoRedo.addEntry(atom, index, UndoRedoObject.REMOVE);
        boolean boo = remove(atom);
        if (atomState != null)
            atomState.atomRemoved(index);
        return boo;
    }

    /**
     * undo操作.
     * 
     * @return 何もしなかった場合false.
     */
    public boolean undo() {
        boolean ret = undoRedo.undo();
        logger.debug(this);
        return ret;
    }

    /**
     * redo操作
     * 
     * @return 何もしなかった場合false.
     */
    public boolean redo() {
        boolean ret = undoRedo.redo();
        logger.debug(this);
        return ret;
    }

    /**
     * 登録されている原子の数を返す. size();メソッドと同じ.
     * 
     * @return 登録されている原子数
     */
    public int getNumAt() {
        return size();
    }

    /**
     * 登録されている原子の内, virtualでないやつを取得.
     */
    public int getRealNumAt() {
        int count = 0;
        for (int i = 0; i < size(); i++)
            if (!getAtomAt(i).isVirtualAtom())
                count++;
        return count;
    }

    public String toString() {
        String ret = "";
        for (int i = 0; i < size(); i++) {
            ret += " atom no. " + i + " " + getAtomAt(i)
                    + System.getProperty("line.separator");
        }
        return ret;
    }
}
