/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/10/07, 22:59
!  AUTHOR(S): KOGA, Junichiro
!  File : UndoRedo.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.Stack;

import org.apache.log4j.Logger;

/**
 * このパッケージのデータ構造用のUndo/Redo制御クラス
 * 
 * @author
 */
public class UndoRedo {
    private Logger logger = Logger.getLogger(UndoRedo.class.getName());

    private AtomList list;

    private Stack undoStack = new Stack();
    private Stack redoStack = new Stack();

    private Stack undoCount = new Stack();
    private Stack redoCount = new Stack();

    /** Creates a new instance of UndoRedo */
    public UndoRedo(AtomList list) {
        this.list = list;
    }

    /**
     * undo/redo操作を初期状態に戻す.
     */
    protected void init() {
        undoStack = new Stack();
        redoStack = new Stack();
        undoCount = new Stack();
        redoCount = new Stack();
    }

    /**
     * undoするエントリーを足しこむ. nullが足されたら, 対応するundo/redo操作は「削除」
     * 
     * @param atom
     *            足しこみたいAtomオブジェクト
     * @param index
     *            atomのインデックス.
     * @param mode
     *            更新, 削除, 新規登録のいずれか
     */
    protected void addEntry(Atom atom, int index, int mode) {
        Atom copy = null;
        if (atom != null) {
            copy = atom.getCopy();
        }

        UndoRedoObject uro = new UndoRedoObject(copy, index, mode);
        undoStack.push(uro);
        redoStack.clear();
        redoCount.clear();
        logger.debug("pushed: " + uro);
    }

    /**
     * Cellオブジェクトもエントリー足りうることにします. Cellは特に 注目すべき属性などはないと思う.
     * 
     * @param cell
     *            足しこみたいCellオブジェクト
     */
    protected void addEntry(Cell cell) {
        Cell copy = null;
        if (cell != null) {
            copy = cell.getCopy();
        }
        UndoRedoObject uro = new UndoRedoObject(copy);
        undoStack.push(uro);
        redoStack.clear();
        redoCount.clear();
    }

    /**
     * 一回のundo操作につき, 何回先まで戻るかを指定する. これとundoStack との整合性は自動的には取れないので要注意(空だった場合,
     * 1という値が採用される).
     * 
     * @param count
     *            何回先まで戻るか.
     */
    protected void setUndoCount(int count) {
        undoCount.push(new Integer(count));
    }

    /**
     * 一番最初のコマへ戻る.
     * 
     * @return 元々一番最初のコマだった場合falseを返す.
     */
    protected boolean undoToFirstEntry() {
        if (undoStack.empty()) {
            logger.info("undo stack empty.");
            return false;
        }
        while (!undoStack.empty()) {
            undo();
        }
        return true;
    }

    /**
     * 所謂undo操作を行う.
     * 
     * @return undoスタックが空だった場合falseを返す.
     */
    protected boolean undo() {
        if (undoStack.empty()) {
            logger.info("undo stack empty.");
            return false;
        }

        int count = 1;
        if (!undoCount.empty()) {
            count = ((Integer) undoCount.pop()).intValue();
        }

        for (int i = 0; i < count; i++) {
            UndoRedoObject uro = (UndoRedoObject) undoStack.pop();
            redoStack.push(doIt(undoStack, uro));
        }
        redoCount.push(new Integer(count));
        return true;
    }

    /**
     * 所謂redo操作を行う.
     * 
     * @return redo用スタックが空だった場合falseを返す.
     */
    protected boolean redo() {
        if (redoStack.empty()) {
            logger.info("redo stack empty.");
            return false;
        }

        int count = 1;
        if (!redoCount.empty()) {
            count = ((Integer) redoCount.pop()).intValue();
        }

        for (int i = 0; i < count; i++) {
            UndoRedoObject uro = (UndoRedoObject) redoStack.pop();
            undoStack.push(doIt(redoStack, uro));
        }
        undoCount.push(new Integer(count));
        return true;
    }

    private UndoRedoObject doIt(Stack stack, UndoRedoObject uro) {
        if (uro.isCell()) {
            Cell cell = uro.getCell();
            Cell tmpcell = list.getParent().getCell(cell.getCellScheme());
            list.getParent().setCell(cell, false);
            return new UndoRedoObject(tmpcell);
        }

        Atom atom = uro.getAtom();
        int index = uro.getIndex();
        int mode = uro.getMode();
        logger.debug("old UndoRedo: " + uro);

        if (atom == null || mode == UndoRedoObject.ADD) {
            list.removeAtomAt(index, false);
            UndoRedoObject newuro = new UndoRedoObject(atom, index,
                    UndoRedoObject.REMOVE);
            logger.debug("new UndoRedoObject: " + newuro);
            return newuro;
        }

        int retmode = UndoRedoObject.ADD;
        Atom retAtom = null;
        if (mode == UndoRedoObject.REPLACE) {
            if (index < list.size()) {
                retAtom = ((Atom) list.get(index)).getCopy();
                list.replaceAtomAt(index, atom, false);
                logger.debug("at doIt.REPLACE");
                retmode = UndoRedoObject.REPLACE;
            } else {
                retAtom = atom;
                list.addAtom(atom, false);
                logger.debug("at doIt.REPLACE");
            }
        } else if (mode == UndoRedoObject.REMOVE) {
            if (index < list.size()) {
                retAtom = atom;
                list.addAtomAt(index, atom, false);
                logger.debug("at doIt.REMOVE; index: " + index);
            } else {
                retAtom = atom;
                list.addAtom(atom, false);
                logger.debug("at doIt.REMOVE");
            }
        }

        UndoRedoObject newuro = new UndoRedoObject(retAtom, index, retmode);
        logger.debug("new UndoRedoObject: " + newuro);
        return newuro;
    }

    /**
     * 不要になったらなるだけ呼ぼう
     */
    public void nullify() {
        while (!undoStack.empty()) {
            UndoRedoObject uro = (UndoRedoObject) undoStack.pop();
            uro.nullify();
        }
        undoStack = null;
        while (!redoStack.empty()) {
            UndoRedoObject uro = (UndoRedoObject) redoStack.pop();
            uro.nullify();
        }
        redoStack = null;
        list = null;
    }
}

/**
 * Undo/Redo対象となるオブジェクトをカプセル化するクラス
 */
class UndoRedoObject {
    static int REPLACE = 0;
    static int REMOVE = 1;
    static int ADD = 2;

    static int ATOM = 10;
    static int CELL = 11;

    private Atom atom;
    private Cell cell;
    private int index;
    private int mode = REPLACE;
    private int atom_or_cell = ATOM;

    /**
     * 対象となる原子, そのインデックス, そしてこの原子にたいしてどんな操作が行われたか, という情報が必要であり, コンストラクターで定義する.
     * 
     * @param atom
     *            undo/redo対象の原子
     * @param index
     *            その原子のインデックス
     * @param mode
     *            どのような操作が行われたか. REPLACE, REMOVE, ADDのいずれか.
     */
    protected UndoRedoObject(Atom atom, int index, int mode) {
        this.atom = atom;
        this.index = index;
        this.mode = mode;
    }

    /**
     * 対象となるセルを引数にとるコンストラクター. セルの場合は原子ほどややこしくはない.
     * 
     * @param cell
     *            undo/redo対象となるセル.
     */
    protected UndoRedoObject(Cell cell) {
        this.cell = cell;
        atom_or_cell = CELL;
    }

    /**
     * 不要になったらできるだけ呼ぶ
     */
    protected void nullify() {
        atom = null;
        cell = null;
    }

    /**
     * このオブジェクトが対象にしているのがセルなのかどうかを判定.
     * 
     * @return セルならばtrue.
     */
    boolean isCell() {
        return atom_or_cell == CELL;
    }

    /**
     * このオブジェクトに格納されているCellオブジェクトを取得
     * 
     * @return cell このオブジェクトに格納されているCellオブジェクト.
     */
    Cell getCell() {
        return cell;
    }

    /**
     * このオブジェクトに格納されているAtomオブジェクトを取得
     * 
     * @return このオブジェクトに格納されているAtomオブジェクト
     */
    Atom getAtom() {
        return this.atom;
    }

    /**
     * このオブジェクトに格納されているAtomオブジェクトのインデックスを取得
     * 
     * @return このオブジェクトに格納されているAtomオブジェクトのインデックス
     */
    int getIndex() {
        return this.index;
    }

    /**
     * このオブジェクトに格納されているAtomオブジェクトに対し, どのような操作が行われたかを取得
     * 
     * @return このオブジェクトに格納されているAtomオブジェクトに対し, どのような操作が行われたか.
     */
    int getMode() {
        return this.mode;
    }

    public String toString() {
        String smode = "";
        if (mode == REPLACE) {
            smode = "REPLACE";
        } else if (mode == REMOVE) {
            smode = "REMOVE";
        } else if (mode == ADD) {
            smode = "ADD";
        }

        return "atom: " + atom + " index: " + index + " mode: " + smode;
    }
}
