/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.undo;

import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.plugin.ModelPlugin;

public class XUndoManager {
    private XModel model = null;
    private XUndoableImpl start;
    private XUndoableImpl current;
    private int transaction = -1;
    private Object listener = null;

    public XUndoManager() {
        reset();
    }

    public void setModel(XModel model) {
        this.model = model;
    }

    public void reset() {
        start = new XUndoableT(null);
        current = start;
        fire();
    }

    public void undo() {
        if(!canUndo()) return;
        current.undo();
        current = current.prev();
    }

    public void redo() {
        if(!canRedo()) return;
        current = current.next();
        current.redo();
    }

    public boolean canUndo() {
        return (current != start && current.canUndo());
    }

    public boolean canRedo() {
        return (current.next() != null && current.next().canRedo());
    }

    public void addUndoable(XUndoable u) {
        XUndoableImpl w = (u instanceof XUndoableImpl) ? (XUndoableImpl)u :
                          new XUndoableT(u);
        if(current.merge(w)) return;
        current.setNext(w);
        current = w;
        if(transaction >= 0) ++transaction;
        reduce();
        fire();
    }

    private void reduce() {
        if(transaction >= 0) return;
        int capacity = 10;
        
          XModelObject root = model.getRoot("Preferences"); //$NON-NLS-1$
          
          if(root!=null) {
        	  String c = root.getAttributeValue("undo capacity"); //$NON-NLS-1$
        	  try {
        		  capacity = Integer.parseInt(c);
        	  } catch (NumberFormatException e) {
        		  //ignore
        	  }
          }          
        if(capacity == 0) {
            start.setNext(null);
            current = start;
            return;
        }
        int i = 0;
        XUndoableImpl c = current;
        while(true) {
            ++i;
            if(c == start) return;
            if(i >= capacity) {
                start.setNext(c);
                return;
            }
            c = c.prev();
        }
    }

    public XUndoList getList() {
        XUndoList list = new XUndoList(this);
        XUndoableImpl u = start;
        int s = -1;
        if(u == current) list.setCurrent(s);
        while(u.next() != null) {
            u = u.next();
            ++s;
            if(u == current) list.setCurrent(s);
            XUndoItem item = new XUndoItem(u.getDescription());
            item.setIcons(u.icons());
            list.add(item);
        }
        return list;
    }

    public void beginTransaction() {
        transaction = 0;
    }

    public int getTransactionStatus() {
        return transaction;
    }

    public void commitTransaction() {
        for (int i = 0; i < transaction; i++) current = current.prev();
        current.setNext(null);
        transaction = -1;
    }

    public void rollbackTransaction() {
        for (int i = 0; i < transaction; i++) undo();
        current.setNext(null);
        transaction = -1;
        fire();
    }

    public void rollbackTransactionInProgress() {
        if(current.next() != null || !(current instanceof XTransactionUndo)) return;
        XTransactionUndo t = (XTransactionUndo)current;
        if(!t.isInProgress()) return;
       	if(t.canUndo()) t.undo();
        current = t.prev();
        current.setNext(null);
        fire();
    }

    public void addListener(Object listener) {
        this.listener = listener;
    }

    private void fire() {
        if(listener == null) return;
        synchronized (listener) {
            listener.notify();
        }
    }

}

class XUndoableT extends XUndoableImpl {

    private XUndoable delegate = null;

    public XUndoableT(XUndoable delegate) {
        this.delegate = delegate;
    }

    public void doUndo() {}
    public void doRedo() {}
    public boolean canUndo() {
        return (delegate != null && delegate.canUndo());
    }
    public boolean canRedo() {
        return (delegate != null && delegate.canRedo());
    }
}

