/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Undo.java
 *
 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.database.change;

import com.sun.electric.database.Snapshot;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.user.User;

import java.util.ArrayList;
import java.util.List;

/**
 * This interface defines changes that are made to the database.
 */
public class Undo {

    /**
     * Class to describe a batch of changes to the Electric database.
     */
    private static class ChangeBatch {

        private int oldSnapshotId, newSnapshotId;
        private Tool tool;
        private String activity;
        private int startingHighlights = -1;				// highlights before changes made
        private int preUndoHighlights = -1;				// highlights before undo of changes done

        private ChangeBatch(Snapshot oldSnapshot, Tool tool, String activity, int startingHighlights, Snapshot newSnapshot) {
            oldSnapshotId = oldSnapshot.snapshotId;
            this.tool = tool;
            this.activity = activity;
            this.startingHighlights = startingHighlights;
            newSnapshotId = newSnapshot.snapshotId;
        }

        public int getStartingHighlights() {
            return startingHighlights;
        }

        public int getPreUndoHighlights() {
            return preUndoHighlights;
        }

        /**
         * Method to return the Tool associated with this ChangeBatch.
         * @return the Tool associated with this ChangeBatch.
         */
        public Tool getTool() {
            return tool;
        }

        private void describe(String title) {
            String activityFromTool = activity;
            if (tool != null) {
                activityFromTool += " from " + tool.getName() + " tool";
            }
            String message = "*** Batch '" + title + "', " + oldSnapshotId + "->" + newSnapshotId + " (" + activityFromTool + ")";
            System.out.println(message);
        }
    }
    private static int maximumBatches = User.getMaxUndoHistory();
    private static final List<ChangeBatch> doneList = new ArrayList<ChangeBatch>();
    private static final List<ChangeBatch> undoneList = new ArrayList<ChangeBatch>();

    /**
     * Method to terminate the current batch of changes.
     */
    public static int endChanges(Snapshot oldSnapshot, Tool tool, String activity, Snapshot newSnapshot) {
        int highlights = Job.getExtendedUserInterface().saveHighlights();
        int oldId = oldSnapshot.snapshotId;
        int newId = newSnapshot.snapshotId;
        while (!doneList.isEmpty() && doneList.get(doneList.size() - 1).newSnapshotId != oldId) {
            doneList.remove(doneList.size() - 1);
        }
        while (!undoneList.isEmpty() && undoneList.get(0).oldSnapshotId != oldId) {
            undoneList.remove(0);
        }
        int doneIndex = indexOf(doneList, newId);
        int undoneIndex = indexOf(undoneList, newId);
        if (doneIndex >= 0) {
            // undo
            while (doneList.size() > doneIndex) {
                ChangeBatch batch = doneList.remove(doneList.size() - 1);
                System.out.println("Undoing: " + batch.activity);
                batch.preUndoHighlights = highlights;
                highlights = batch.startingHighlights;
                undoneList.add(0, batch);
            }
            Job.getExtendedUserInterface().restoreHighlights(highlights);
        } else if (undoneIndex >= 0) {
            // redo
            for (int i = 0; i < undoneIndex; i++) {
                ChangeBatch batch = undoneList.remove(0);
                System.out.println("Redoing: " + batch.activity);
                batch.startingHighlights = highlights;
                highlights = batch.preUndoHighlights;
                doneList.add(batch);
            }
            Job.getExtendedUserInterface().restoreHighlights(highlights);
        } else {
            // change task
            undoneList.clear();
            doneList.add(new ChangeBatch(oldSnapshot, tool, activity, highlights, newSnapshot));
            highlights = -1;
        }
        updateUndoRedo();
        return highlights;
    }

    private static void invalidate(int snapshotId) {
        int doneIndex = indexOf(doneList, snapshotId);
        int undoneIndex = indexOf(undoneList, snapshotId);
        if (doneIndex >= 0) {
            // undo
            if (doneIndex == 0) {
                doneList.clear();
            } else {
                while (doneList.size() >= doneIndex) {
                    doneList.remove(doneList.size() - 1);
                }
            }
        }
        if (undoneIndex >= 0) {
            // redo
            if (undoneIndex == undoneList.size()) {
                undoneList.clear();
            } else {
                for (int i = 0; i < undoneIndex; i++) {
                    if (!undoneList.isEmpty()) {
                        undoneList.remove(0);
                    }
                }
            }
        }
        updateUndoRedo();
    }

    private static int indexOf(List<ChangeBatch> list, int snapshotId) {
        for (int i = 0; i < list.size(); i++) {
            ChangeBatch batch = list.get(i);
            if (batch.oldSnapshotId == snapshotId) {
                return i;
            }
            if (batch.newSnapshotId == snapshotId) {
                return i + 1;
            }
        }
        return -1;
    }

    private static void updateUndoRedo() {
        // Ignore status if editing a text cell (the text window reports correct undo/redo status)
        Cell cell = Job.getUserInterface().getCurrentCell();
        if (cell != null && cell.getView().isTextView()) {
            return;
        }

        Job.getExtendedUserInterface().showUndoRedoStatus(!doneList.isEmpty(), !undoneList.isEmpty());
    }

    public static String getUndoActivity() {
        //return doneList.get(doneList.size() - 1).activity;}
        if (!doneList.isEmpty()) {
            return doneList.get(doneList.size() - 1).activity;
        }
        System.out.println("Nothing left to undo");
        return "";
    }

    public static void removeLastChangeBatchTask()
    {
        if (!doneList.isEmpty())
        {
            ChangeBatch task = doneList.get(doneList.size() - 1);
            invalidate(task.oldSnapshotId);
        }
    }

    /**
     * Method to undo a change.
     */
    public static void undo() {
        if (!doneList.isEmpty()) {
            new UndoJob("Undo", doneList.get(doneList.size() - 1).oldSnapshotId);
        } else {
            System.out.println("Undo failed");
        }
    }

    /**
     * Method to redo a change.
     */
    public static void redo() {
        if (!undoneList.isEmpty()) {
            new UndoJob("Redo", undoneList.get(0).newSnapshotId);
        } else {
            System.out.println("Redo failed");
        }
    }

    /**
     * Class to schedule an undo in a separate Job.
     */
    public static class UndoJob extends Job {

        int snapshotId;

        public UndoJob(String jobName, int snapshotId) {
            super(jobName, User.getUserTool(), Job.Type.UNDO, null, null, Job.Priority.USER);
            this.snapshotId = snapshotId;
            startJob();
        }

        public int getSnapshotId() {
            return snapshotId;
        }

        public boolean doIt() throws JobException {
            // must not be called.
            throw new IllegalStateException();
        }

        public void terminateFail(Throwable e) {
            invalidate(snapshotId);
            super.terminateFail(e);
        }
    }

//	/**
//	 * Method to undo the last batch of changes.
//	 * @return the batch number that was undone.
//	 * Returns null if nothing was undone.
//	 */
//	public static int undoABatch(int savedHighlights) throws JobException
//	{
//		// get the most recent batch of changes
//		int listSize = doneList.size();
//		if (listSize == 0) return null;
//		ChangeBatch batch = (ChangeBatch)doneList.get(listSize-1);
//		doneList.remove(listSize-1);
//		undoneList.add(batch);
//        updateUndoRedo();
//
//		// save pre undo highlights
//		batch.preUndoHighlights = savedHighlights;
//
//		// look through the changes in this batch
//        Job.undo(batch.oldSnapshotId);
//
//		// Put message in Message Window
//		System.out.println("Undoing: " + batch.activity);
////        batch.describe("Undo");
//		return batch;
//	}
//
//	/**
//	 * Method to redo the last batch of changes.
//	 * @return true if a batch was redone.
//	 */
//	public static ChangeBatch redoABatch() throws JobException
//	{
//		// get the most recent batch of changes
//		int listSize = undoneList.size();
//		if (listSize == 0) return null;
//		ChangeBatch batch = undoneList.get(listSize-1);
//		undoneList.remove(listSize-1);
//		doneList.add(batch);
//        updateUndoRedo();
//
//        Job.undo(batch.newSnapshotId);
//
//        // Put message in Message Window
//		System.out.println("Redoing: " + batch.activity);
////        batch.describe("Redo");
//		return batch;
//	}
//
//	/**
//	 * Method to prevent undo by deleting all change batches.
//	 */
//	public static void noUndoAllowed()
//	{
// 		// kill them all
// 		doneList.clear();
// 		undoneList.clear();
//        updateUndoRedo();
//	}
    /**
     * Method to prevent redo by deleting all undone change batches.
     */
    public static void noRedoAllowed() {
        undoneList.clear();
        updateUndoRedo();
    }

    /**
     * Method to set the size of the history list and return the former size.
     * @param newSize the new size of the history list (number of batches of changes).
     * If not positive, the list size is not changed.
     * @return the former size of the history list.
     */
    public static int setHistoryListSize(int newSize) {
        if (newSize <= 0) {
            return maximumBatches;
        }

        int oldSize = maximumBatches;
        maximumBatches = newSize;
        if (doneList.size() > maximumBatches) {
            doneList.remove(0);
        }
        updateUndoRedo();
        return oldSize;
    }

    /**
     * Method to display all changes.
     */
    public static void showHistoryList() {
        System.out.println("----------  Done batches (" + doneList.size() + ") ----------:");
        for (int i = 0; i < doneList.size(); i++) {
            ChangeBatch batch = doneList.get(i);
            batch.describe("Done");
        }
        System.out.println("----------  Undone batches (" + undoneList.size() + ") ----------:");
        for (int i = 0; i < undoneList.size(); i++) {
            ChangeBatch batch = undoneList.get(i);
            batch.describe("Undone");
        }
    }
}
