/**********************************************************************
 
 Audacity: A Digital Audio Editor
 
 TrackUtilities.cpp
 
 Paul Licameli split from TrackMenus.cpp
 
 **********************************************************************/

#include "TrackUtilities.h"

#include "ProjectHistory.h"
#include "ProjectSettings.h"
#include "ProjectWindow.h"
#include "Track.h"
#include "TrackPanelAx.h"
#include "TrackPanel.h"

namespace TrackUtilities {

void DoRemoveTracks( TenacityProject &project )
{
   auto &tracks = TrackList::Get( project );
   auto &trackPanel = TrackPanel::Get( project );

   std::vector<Track*> toRemove;
   for (auto track : tracks.Selected())
      toRemove.push_back(track);

   // Capture the track preceding the first removed track
   Track *f{};
   if (!toRemove.empty()) {
      auto found = tracks.Find(toRemove[0]);
      f = *--found;
   }

   for (auto track : toRemove)
      tracks.Remove(track);

   if (!f)
      // try to use the last track
      f = *tracks.Any().rbegin();
   if (f) {
      // Try to use the first track after the removal
      auto found = tracks.FindLeader(f);
      auto t = *++found;
      if (t)
         f = t;
   }

   // If we actually have something left, then set focus and make sure it's seen
   if (f) {
      TrackFocus::Get(project).Set(f);
      f->EnsureVisible();
   }

   ProjectHistory::Get( project )
      .PushState(XO("Removed audio track(s)"), XO("Remove Track"));

   trackPanel.UpdateViewIfNoTracks();
}

void DoTrackMute(TenacityProject &project, Track *t, bool exclusive)
{
   const auto &settings = ProjectSettings::Get( project );
   auto &tracks = TrackList::Get( project );

   // Whatever t is, replace with lead channel
   t = *tracks.FindLeader(t);

   // "exclusive" mute means mute the chosen track and unmute all others.
   if (exclusive) {
      for (auto leader : tracks.Leaders<PlayableTrack>()) {
         const auto group = TrackList::Channels(leader);
         bool chosen = (t == leader);
         for (auto channel : group)
            channel->SetMute( chosen ),
            channel->SetSolo( false );
      }
   }
   else {
      // Normal click toggles this track.
      auto pt = dynamic_cast<PlayableTrack *>( t );
      if (!pt)
         return;

      bool wasMute = pt->GetMute();
      for (auto channel : TrackList::Channels(pt))
         channel->SetMute( !wasMute );

      if (settings.IsSoloSimple() || settings.IsSoloNone())
      {
         // We also set a solo indicator if we have just one track / stereo pair playing.
         // in a group of more than one playable tracks.
         // otherwise clear solo on everything.

         auto range = tracks.Leaders<PlayableTrack>();
         auto nPlayableTracks = range.size();
         auto nPlaying = (range - &PlayableTrack::GetMute).size();

         for (auto track : tracks.Any<PlayableTrack>())
            // will set both of a stereo pair
            track->SetSolo( (nPlaying==1) && (nPlayableTracks > 1 ) && !track->GetMute() );
      }
   }
   ProjectHistory::Get( project ).ModifyState(true);

   TrackFocus::Get( project ).UpdateAccessibility();
}

void DoTrackSolo(TenacityProject &project, Track *t, bool exclusive)
{
   const auto &settings = ProjectSettings::Get( project );
   auto &tracks = TrackList::Get( project );
   
   // Whatever t is, replace with lead channel
   t = *tracks.FindLeader(t);

   const auto pt = dynamic_cast<PlayableTrack *>( t );
   if (!pt)
      return;
   bool bWasSolo = pt->GetSolo();

   bool bSoloMultiple = !settings.IsSoloSimple() ^ exclusive;

   // Standard and Simple solo have opposite defaults:
   //   Standard - Behaves as individual buttons, shift=radio buttons
   //   Simple   - Behaves as radio buttons, shift=individual
   // In addition, Simple solo will mute/unmute tracks
   // when in standard radio button mode.
   if ( bSoloMultiple )
   {
      for (auto channel : TrackList::Channels(pt))
         channel->SetSolo( !bWasSolo );
   }
   else
   {
      // Normal click solo this track only, mute everything else.
      // OR unmute and unsolo everything.
      for (auto leader : tracks.Leaders<PlayableTrack>()) {
         const auto group = TrackList::Channels(leader);
         bool chosen = (t == leader);
         for (auto channel : group) {
            if (chosen) {
               channel->SetSolo( !bWasSolo );
               if( settings.IsSoloSimple() )
                  channel->SetMute( false );
            }
            else {
               channel->SetSolo( false );
               if( settings.IsSoloSimple() )
                  channel->SetMute( !bWasSolo );
            }
         }
      }
   }
   ProjectHistory::Get( project ).ModifyState(true);

   TrackFocus::Get( project ).UpdateAccessibility();
}

void DoRemoveTrack(TenacityProject &project, Track * toRemove)
{
   auto &tracks = TrackList::Get( project );
   auto &trackFocus = TrackFocus::Get( project );
   auto &window = ProjectWindow::Get( project );

   // If it was focused, then NEW focus is the next or, if
   // unavailable, the previous track. (The NEW focus is set
   // after the track has been removed.)
   bool toRemoveWasFocused = trackFocus.Get() == toRemove;
   Track* newFocus{};
   if (toRemoveWasFocused) {
      auto iterNext = tracks.FindLeader(toRemove), iterPrev = iterNext;
      newFocus = *++iterNext;
      if (!newFocus) {
         newFocus = *--iterPrev;
      }
   }

   wxString name = toRemove->GetName();

   auto channels = TrackList::Channels(toRemove);
   // Be careful to post-increment over positions that get erased!
   auto &iter = channels.first;
   while (iter != channels.end())
      tracks.Remove( * iter++ );

   if (toRemoveWasFocused)
      trackFocus.Set( newFocus );

   ProjectHistory::Get( project ).PushState(
      XO("Removed track '%s.'").Format( name ),
      XO("Track Remove"));
}

void DoMoveTrack
(TenacityProject &project, Track* target, MoveChoice choice)
{
   auto &tracks = TrackList::Get( project );

   TranslatableString longDesc, shortDesc;

   switch (choice)
   {
   case OnMoveTopID:
      /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
      longDesc = XO("Moved '%s' to Top");
      shortDesc = XO("Move Track to Top");

      // TODO: write TrackList::Rotate to do this in one step and avoid emitting
      // an event for each swap
      while (tracks.CanMoveUp(target))
         tracks.Move(target, true);

      break;
   case OnMoveBottomID:
      /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
      longDesc = XO("Moved '%s' to Bottom");
      shortDesc = XO("Move Track to Bottom");

      // TODO: write TrackList::Rotate to do this in one step and avoid emitting
      // an event for each swap
      while (tracks.CanMoveDown(target))
         tracks.Move(target, false);

      break;
   default:
      bool bUp = (OnMoveUpID == choice);

      tracks.Move(target, bUp);
      longDesc =
         /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
         bUp? XO("Moved '%s' Up")
         : XO("Moved '%s' Down");
      shortDesc =
         /* i18n-hint: Past tense of 'to move', as in 'moved audio track up'.*/
         bUp? XO("Move Track Up")
         : XO("Move Track Down");

   }

   longDesc.Format(target->GetName());

   ProjectHistory::Get( project ).PushState(longDesc, shortDesc);
}

}
