/* DefaultUndoableActionManager.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		2013/7/25, Created by Dennis.Chen
}}IS_NOTE

Copyright (C) 2013 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under GPL Version 2.0 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zssex.ui.impl;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import org.zkoss.lang.Objects;
import org.zkoss.zss.api.CellRef;
import org.zkoss.zss.api.AreaRef;
import org.zkoss.zss.api.model.Sheet;
import org.zkoss.zss.ui.Spreadsheet;
import org.zkoss.zss.ui.event.Events;
import org.zkoss.zss.ui.event.UndoableActionManagerEvent;
import org.zkoss.zss.ui.sys.UndoableAction;
import org.zkoss.zss.ui.sys.UndoableActionManager;
/**
 * default undoable action manager
 * @author dennis
 *
 */
public class UndoableActionManagerImpl implements UndoableActionManager, Serializable {
	private static final long serialVersionUID = -5134810698803633947L;
	
	int _maxHistory = 10;
	int _index = -1;
	LinkedList<UndoableAction> _actionHisotry = new LinkedList<UndoableAction>();
	
	Spreadsheet _spreadsheet;
	public UndoableActionManagerImpl(){
	}
	
	@Override
	public void doAction(UndoableAction action) {
		action.doAction();
		while(_actionHisotry.size()>_index+1){
			_actionHisotry.removeLast();
		}
		while(_actionHisotry.size()>=_maxHistory){
			_actionHisotry.remove(0);
		}
		_actionHisotry.add(action);
		_index = _actionHisotry.size()-1;
		org.zkoss.zk.ui.event.Events.postEvent(new UndoableActionManagerEvent(
				Events.ON_AFTER_UNDOABLE_MANAGER_ACTION, _spreadsheet,
				UndoableActionManagerEvent.Type.DO, action));
	}
	
	private UndoableAction current(){
		return _index>=0 && _index<_actionHisotry.size()?_actionHisotry.get(_index):null;
	}
	private UndoableAction next(){
		int i = _index+1;
		return i>=0 && i<_actionHisotry.size()?_actionHisotry.get(i):null;
	}

	@Override
	public boolean isUndoable() {
		UndoableAction action = current();
		return action!=null && action.isUndoable();
	}

	@Override
	public String getUndoLabel() {
		UndoableAction action = current();
		return action!=null?action.getLabel():null;
	}

	@Override
	public void undoAction() {
		UndoableAction action = current();
		if(action!=null){
			if(action.isUndoable()){
				//to prevent undo on different sheet
				Sheet sheet = action.getUndoSheet();
				if(_spreadsheet!=null && sheet!=null && !sheetEquals(_spreadsheet.getSelectedSheet(),sheet)){
					_spreadsheet.setSelectedSheet(sheet.getSheetName());
					_spreadsheet.focus();
					return;
				}
				action.undoAction();
				_index--;
				//to solve performance when select all
				AreaRef selection = getVisibleArea(action.getUndoSelection());
				if(_spreadsheet!=null && selection!=null){
					_spreadsheet.setSelection(selection);
					_spreadsheet.setCellFocus(new CellRef(selection.getRow(),selection.getColumn()));
				}
				
			}else{
				clear();
			}
			org.zkoss.zk.ui.event.Events.postEvent(new UndoableActionManagerEvent(
					Events.ON_AFTER_UNDOABLE_MANAGER_ACTION, _spreadsheet,
					UndoableActionManagerEvent.Type.UNDO, action));
		}
	}

	@Override
	public boolean isRedoable() {
		UndoableAction action = next();
		return action!=null && action.isRedoable();
	}
	
	@Override
	public String getRedoLabel() {
		UndoableAction action = next();
		return action!=null?action.getLabel():null;
	}

	@Override
	public void redoAction() {
		UndoableAction action = next();
		if(action!=null){
			if(action.isRedoable()){
				Sheet sheet = action.getRedoSheet();
				if(_spreadsheet!=null && sheet!=null && !sheetEquals(_spreadsheet.getSelectedSheet(),sheet)){
					_spreadsheet.setSelectedSheet(sheet.getSheetName());
					_spreadsheet.focus();
					return;
				}
				
				action.doAction();
				_index++;
				//to solve performance when select all
				AreaRef selection = getVisibleArea(action.getRedoSelection());
				if(_spreadsheet!=null && selection!=null){
					_spreadsheet.setSelection(selection);
					_spreadsheet.setCellFocus(new CellRef(selection.getRow(),selection.getColumn()));
				}
			}else{
				clear();
			}
			org.zkoss.zk.ui.event.Events.postEvent(new UndoableActionManagerEvent(
					Events.ON_AFTER_UNDOABLE_MANAGER_ACTION, _spreadsheet,
					UndoableActionManagerEvent.Type.REDO, action));
		}
	}
	
	private boolean sheetEquals(Sheet sheet1,Sheet sheet2){
		try{
			return Objects.equals(sheet1, sheet2);
		}catch(Exception x){
			return false;
		}
	}
	
	private AreaRef getVisibleArea(AreaRef rect) {
		return rect == null ? null : new AreaRef(rect.getRow(), rect.getColumn(),
				Math.min(rect.getLastRow(), _spreadsheet.getCurrentMaxVisibleRows()), //ZSS-1084
				Math.min(rect.getLastColumn(), _spreadsheet.getCurrentMaxVisibleColumns())); //ZSS-1084
	}

	@Override
	public void clear() {
		_index = -1;
		_actionHisotry.clear();
		org.zkoss.zk.ui.event.Events.postEvent(new UndoableActionManagerEvent(
				Events.ON_AFTER_UNDOABLE_MANAGER_ACTION, _spreadsheet,
				UndoableActionManagerEvent.Type.CLEAR, null));
	}
	@Override
	public void setMaxHsitorySize(int size) {
		if(size < 1){
			throw new IllegalArgumentException("must large than 1");
		}
		//i simply reset it
		clear();
		_maxHistory = size;
	}
	@Override
	public void bind(Spreadsheet spreadsheet) {
		this._spreadsheet = spreadsheet;
	}

}
