/**********************************************************************

Audacity: A Digital Audio Editor

ProjectHistory.cpp

Paul Licameli split from ProjectManager.cpp

**********************************************************************/

#include "ProjectHistory.h"

#include "Project.h"
#include "ProjectFileIO.h"
#include "Tags.h"
#include "Track.h"
#include "UndoManager.h"
#include "ViewInfo.h"

static TenacityProject::AttachedObjects::RegisteredFactory sProjectHistoryKey {
   []( TenacityProject &project ) {
      return std::make_shared< ProjectHistory >( project );
   }
};

ProjectHistory &ProjectHistory::Get( TenacityProject &project )
{
   return project.AttachedObjects::Get< ProjectHistory >( sProjectHistoryKey );
}

const ProjectHistory &ProjectHistory::Get( const TenacityProject &project )
{
   return Get( const_cast< TenacityProject & >( project ) );
}

ProjectHistory::~ProjectHistory() = default;

//
// Undo/History methods
//

void ProjectHistory::InitialState()
{
   auto &project = mProject;
   auto &tracks = TrackList::Get( project );
   auto &viewInfo = ViewInfo::Get( project );
   auto &undoManager = UndoManager::Get( project );
   auto &tags = Tags::Get( project );

   undoManager.ClearStates();

   undoManager.PushState(
      &tracks, viewInfo.selectedRegion, tags.shared_from_this(),
      XO("Created new project"), {});

   undoManager.StateSaved();
}

bool ProjectHistory::UndoAvailable() const
{
   auto &project = mProject;
   auto &tracks = TrackList::Get( project );
   auto &undoManager = UndoManager::Get( project );
   return undoManager.UndoAvailable() &&
       !tracks.HasPendingTracks();
}

bool ProjectHistory::RedoAvailable() const
{
   auto &project = mProject;
   auto &tracks = TrackList::Get( project );
   auto &undoManager = UndoManager::Get( project );
   return undoManager.RedoAvailable() &&
      !tracks.HasPendingTracks();
}

namespace {
   void AutoSaveOrThrow( ProjectFileIO &projectFileIO )
   {
      if ( !projectFileIO.AutoSave() )
         throw SimpleMessageBoxException{
            ExceptionType::Internal,
            XO("Automatic database backup failed."),
            XO("Warning"),
            "Editing_Part_2#your-disk-is-full-or-not-writable"
         };
   }
}

void ProjectHistory::PushState(
   const TranslatableString &desc, const TranslatableString &shortDesc)
{
   PushState(desc, shortDesc, UndoPush::NONE);
}

void ProjectHistory::PushState(const TranslatableString &desc,
                                const TranslatableString &shortDesc,
                                UndoPush flags )
{
   auto &project = mProject;
   auto &projectFileIO = ProjectFileIO::Get( project );
   if((flags & UndoPush::NOAUTOSAVE) == UndoPush::NONE)
      AutoSaveOrThrow( projectFileIO );

   // remaining no-fail operations "commit" the changes of undo manager state
   auto &tracks = TrackList::Get( project );
   auto &viewInfo = ViewInfo::Get( project );
   auto &undoManager = UndoManager::Get( project );
   auto &tags = Tags::Get( project );
   undoManager.PushState(
      &tracks, viewInfo.selectedRegion, tags.shared_from_this(),
      desc, shortDesc, flags);

   mDirty = true;
}

void ProjectHistory::RollbackState()
{
   auto &project = mProject;
   auto &undoManager = UndoManager::Get( project );
   SetStateTo( undoManager.GetCurrentState(), false );
}

void ProjectHistory::ModifyState(bool bWantsAutoSave)
{
   auto &project = mProject;
   auto &projectFileIO = ProjectFileIO::Get( project );
   if (bWantsAutoSave)
      AutoSaveOrThrow( projectFileIO );

   // remaining no-fail operations "commit" the changes of undo manager state
   auto &tracks = TrackList::Get( project );
   auto &viewInfo = ViewInfo::Get( project );
   auto &undoManager = UndoManager::Get( project );
   auto &tags = Tags::Get( project );
   undoManager.ModifyState(
      &tracks, viewInfo.selectedRegion, tags.shared_from_this());
}

// LL:  Is there a memory leak here as "l" and "t" are not deleted???
// Vaughan, 2010-08-29: No, as "l" is a TrackList* of an Undo stack state.
//    Need to keep it and its tracks "t" available for Undo/Redo/SetStateTo.
void ProjectHistory::PopState(const UndoState &state, bool doAutosave)
{
   auto &project = mProject;
   auto &projectFileIO = ProjectFileIO::Get( project );
   if (doAutosave)
      AutoSaveOrThrow( projectFileIO );

   // remaining no-fail operations "commit" the changes of undo manager state
   auto &dstTracks = TrackList::Get( project );
   auto &viewInfo = ViewInfo::Get( project );

   viewInfo.selectedRegion = state.selectedRegion;

   // Restore tags
   Tags::Set( project, state.tags );

   TrackList *const tracks = state.tracks.get();

   dstTracks.Clear();

   for (auto t : tracks->Any())
   {
      dstTracks.Add(t->Duplicate());
   }

}

void ProjectHistory::SetStateTo(unsigned int n, bool doAutosave)
{
   auto &project = mProject;
   auto &undoManager = UndoManager::Get( project );

   undoManager.SetStateTo(n,
      [this, doAutosave]( const UndoStackElem &elem ){
         PopState(elem.state, doAutosave); } );
}
