﻿// Created by Daniele Giardini - 2011 - Holoville - http://www.holoville.com
// Mesh Creator team found this at http://wiki.unity3d.com/index.php?title=EditorUndoManager
// we stripped out many of the comments and changed names of things

using UnityEditor;
using UnityEngine;

public class MeshCreatorUndoManager
{

    // VARS ///////////////////////////////////////////////////

    private Object defTarget;
    private string defName;
    private bool autoSetDirty;
    private bool listeningForGuiChanges;
    private bool isMouseDown;
    private Object waitingToRecordPrefab; // If different than NULL indicates the prefab instance that will need to record its state as soon as the mouse is released. 

    // ***********************************************************************************
    // CONSTRUCTOR
    // ***********************************************************************************

    /// Creates a new MeshCreatorUndoManager,
    /// setting it so that the target is marked as dirty each time a new undo is stored. 
    /// params:
    /// The default Object you want to save undo info for.
    /// The default name of the thing to undo (displayed as "Undo [name]" in the main menu).
    public MeshCreatorUndoManager(Object p_target, string p_name) : this(p_target, p_name, true) { }

    /// Creates a new MeshCreatorUndoManager.
    /// params:
    /// The default Object you want to save undo info for.
    /// The default name of the thing to undo (displayed as "Undo [name]" in the main menu).
    /// If TRUE, marks the target as dirty each time a new undo is stored.
    public MeshCreatorUndoManager(Object p_target, string p_name, bool p_autoSetDirty)
    {
        defTarget = p_target;
        defName = p_name;
        autoSetDirty = p_autoSetDirty;
    }

    // ===================================================================================
    // METHODS ---------------------------------------------------------------------------

    /// Call this method BEFORE any undoable UnityGUI call.
    /// Manages undo for the default target, with the default name.
    public void CheckUndo() { CheckUndo(defTarget, defName); }

    /// Call this method BEFORE any undoable UnityGUI call.
    /// Manages undo for the given target, with the default name.
    /// params:
    /// The object you want to save undo info for.
    public void CheckUndo(Object p_target) { CheckUndo(p_target, defName); }

    /// Call this method BEFORE any undoable UnityGUI call.
    /// Manages undo for the given target, with the given name.
    /// params:
    /// The object you want to save undo info for.
    /// The name of the thing to undo (displayed as "Undo [name]" in the main menu).
    public void CheckUndo(Object p_target, string p_name)
    {
        Event e = Event.current;

        if (waitingToRecordPrefab != null)
        {
            // Record eventual prefab instance modification.
            // TODO Avoid recording if nothing changed (no harm in doing so, but it would be nicer).
            switch (e.type)
            {
                case EventType.MouseDown:
                case EventType.MouseUp:
                case EventType.KeyDown:
                case EventType.KeyUp:
                    PrefabUtility.RecordPrefabInstancePropertyModifications(waitingToRecordPrefab);
                    break;
            }
        }

        if ((e.type == EventType.MouseDown && e.button == 0) || (e.type == EventType.KeyUp && e.keyCode == KeyCode.Tab))
        {
            // When the LMB is pressed or the TAB key is released,
            // store a snapshot, but don't register it as an undo
            // (so that if nothing changes we avoid storing a useless undo).
            Undo.SetSnapshotTarget(p_target, p_name);
            Undo.CreateSnapshot();
            Undo.ClearSnapshotTarget(); // Not sure if this is necessary.
            listeningForGuiChanges = true;
        }
    }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Manages undo for the default target, with the default name,
    /// and returns a value of TRUE if the target is marked as dirty.
    public bool CheckDirty() { return CheckDirty(defTarget, defName); }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Manages undo for the given target, with the default name,
    /// and returns a value of TRUE if the target is marked as dirty.
    /// params:
    /// The object you want to save undo info for.
    public bool CheckDirty(Object p_target) { return CheckDirty(p_target, defName); }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Manages undo for the given target, with the given name,
    /// and returns a value of TRUE if the target is marked as dirty.
    /// params:
    /// The object you want to save undo info for.
    /// The name of the thing to undo (displayed as "Undo [name]" in the main menu).
    public bool CheckDirty(Object p_target, string p_name)
    {
        if (listeningForGuiChanges && GUI.changed)
        {
            // Some GUI value changed after pressing the mouse
            // or releasing the TAB key.
            // Register the previous snapshot as a valid undo.
            SetDirty(p_target, p_name);
            return true;
        }
        return false;
    }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Forces undo for the default target, with the default name.
    /// Used to undo operations that are performed by pressing a button,
    /// which doesn't set the GUI to a changed state.
    public void ForceDirty() { ForceDirty(defTarget, defName); }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Forces undo for the given target, with the default name.
    /// Used to undo operations that are performed by pressing a button,
    /// which doesn't set the GUI to a changed state.
    /// params:
    /// The object you want to save undo info for.
    public void ForceDirty(Object p_target) { ForceDirty(p_target, defName); }

    /// Call this method AFTER any undoable UnityGUI call.
    /// Forces undo for the given target, with the given name.
    /// Used to undo operations that are performed by pressing a button,
    /// which doesn't set the GUI to a changed state.
    /// params:
    /// The object you want to save undo info for.
    /// The name of the thing to undo (displayed as "Undo [name]" in the main menu).
    public void ForceDirty(Object p_target, string p_name)
    {
        if (!listeningForGuiChanges)
        {
            // Create a new snapshot.
            Undo.SetSnapshotTarget(p_target, p_name);
            Undo.CreateSnapshot();
            Undo.ClearSnapshotTarget();
        }
        SetDirty(p_target, p_name);
    }

    // ===================================================================================
    // PRIVATE METHODS -------------------------------------------------------------------

    private void SetDirty(Object p_target, string p_name)
    {
        Undo.SetSnapshotTarget(p_target, p_name);
        Undo.RegisterSnapshot();
        Undo.ClearSnapshotTarget(); // Not sure if this is necessary.
        if (autoSetDirty) EditorUtility.SetDirty(p_target);
        listeningForGuiChanges = false;

        if (CheckTargetIsPrefabInstance(p_target))
        {
            // Prefab instance: record immediately and also wait for value to be changed and than re-record it
            // (otherwise prefab instances are not updated correctly when using Custom Inspectors).
            PrefabUtility.RecordPrefabInstancePropertyModifications(p_target);
            waitingToRecordPrefab = p_target;
        }
        else
        {
            waitingToRecordPrefab = null;
        }
    }

    private bool CheckTargetIsPrefabInstance(Object p_target)
    {
        return (PrefabUtility.GetPrefabType(p_target) == PrefabType.PrefabInstance);
    }
}