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

  Audacity: A Digital Audio Editor

  ODComputeSummaryTask.cpp

  Created by Michael Chinen (mchinen) on 6/8/08.
  Audacity(R) is copyright (c) 1999-2008 Audacity Team.
  License: GPL v2.  See License.txt.

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

\class ODComputeSummaryTask
\brief Computes the summary data for a PCM (WAV) file and writes it to disk,
updating the ODPCMAliasBlockFile and the GUI of the newly available data.

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



#include "ODComputeSummaryTask.h"
#include "../AudacityException.h"
#include "../blockfile/ODPCMAliasBlockFile.h"
#include "../Sequence.h"
#include "../WaveTrack.h"
#include <wx/wx.h>

//36 blockfiles > 3 minutes stereo 44.1kHz per ODTask::DoSome
#define nBlockFilesPerDoSome 36

///Creates a NEW task that computes summaries for a wavetrack that needs to be specified through SetWaveTrack()
ODComputeSummaryTask::ODComputeSummaryTask()
{
   mMaxBlockFiles = 0;
   mHasUpdateRan=false;
}

std::unique_ptr<ODTask> ODComputeSummaryTask::Clone() const
{
   auto clone = std::make_unique<ODComputeSummaryTask>();
   clone->mDemandSample = GetDemandSample();
   // This std::move is needed to "upcast" the pointer type
   return std::move(clone);
}


///releases memory that the ODTask owns.  Subclasses should override.
void ODComputeSummaryTask::Terminate()
{
   //The terminate block won't allow DoSomeInternal and this method to be run async, so this is thread-safe.
   //Deref the block files since they are ref'ed when put into the array.
   mBlockFilesMutex.Lock();
   mBlockFiles.clear();
   mBlockFilesMutex.Unlock();
}

///Computes and writes the data for one BlockFile if it still has a refcount.
void ODComputeSummaryTask::DoSomeInternal()
{
   if(mBlockFiles.size()<=0)
   {
      mPercentCompleteMutex.Lock();
      mPercentComplete = 1.0;
      mPercentCompleteMutex.Unlock();
      return;
   }

   mBlockFilesMutex.Lock();
   for(size_t j=0; j < mWaveTracks.size() && mBlockFiles.size();j++)
   {
      bool success = false;
      const auto bf = mBlockFiles[0].lock();

      sampleCount blockStartSample = 0;
      sampleCount blockEndSample = 0;

      if(bf)
      {
         // WriteSummary might throw, but this is a worker thread, so stop
         // the exceptions here!
         success = GuardedCall<bool>( [&] {
            bf->DoWriteSummary();
            return true;
         } );
         blockStartSample = bf->GetStart();
         blockEndSample = blockStartSample + bf->GetLength();
      }
      else
      {
         success = true;
         // The block file disappeared.
         //the waveform in the wavetrack now is shorter, so we need to update mMaxBlockFiles
         //because now there is less work to do.
         mMaxBlockFiles--;
      }

      if (success)
      {
         //take it out of the array - we are done with it.
         mBlockFiles.erase(mBlockFiles.begin());
      }
      else
         // The task does not make progress
         ;

      //This is a bit of a convenience in case someone tries to terminate the task by closing the trackpanel or window.
      //ODComputeSummaryTask::Terminate() uses this lock to remove everything, and we don't want it to wait since the UI is being blocked.
      mBlockFilesMutex.Unlock();
      wxThread::This()->Yield();
      mBlockFilesMutex.Lock();

      //update the gui for all associated blocks.  It doesn't matter that we're hitting more wavetracks then we should
      //because this loop runs a number of times equal to the number of tracks, they probably are getting processed in
      //the next iteration at the same sample window.
      if (success && bf) {
         mWaveTrackMutex.Lock();
         for(size_t i=0;i<mWaveTracks.size();i++)
         {
            if(success && mWaveTracks[i])
               mWaveTracks[i]->AddInvalidRegion(blockStartSample,blockEndSample);
         }
         mWaveTrackMutex.Unlock();
      }
   }

   mBlockFilesMutex.Unlock();

   //update percentage complete.
   CalculatePercentComplete();
}

///compute the next time we should take a break in terms of overall percentage.
///We want to do a constant number of blockfiles.
float ODComputeSummaryTask::ComputeNextWorkUntilPercentageComplete()
{
   if(mMaxBlockFiles==0)
     return 1.0;

   float nextPercent;
   mPercentCompleteMutex.Lock();
   nextPercent=mPercentComplete + ((float)nBlockFilesPerDoSome/(mMaxBlockFiles+1));
   mPercentCompleteMutex.Unlock();

   return nextPercent;
}

void ODComputeSummaryTask::MarkUpdateRan()
{
   mHasUpdateRanMutex.Lock();
   mHasUpdateRan=true;
   mHasUpdateRanMutex.Unlock();
}

bool ODComputeSummaryTask::HasUpdateRan()
{
   bool ret;
   mHasUpdateRanMutex.Lock();
   ret = mHasUpdateRan;
   mHasUpdateRanMutex.Unlock();
   return ret;
}

void ODComputeSummaryTask::CalculatePercentComplete()
{
   bool hasUpdateRan;
   hasUpdateRan = HasUpdateRan();
   mPercentCompleteMutex.Lock();
   if(hasUpdateRan)
      mPercentComplete = (float) 1.0 - ((float)mBlockFiles.size() / (mMaxBlockFiles+1));
   else
      mPercentComplete =0.0;
   mPercentCompleteMutex.Unlock();
}

///creates the order of the wavetrack to load.
///by default left to right, or frome the point the user has clicked.
void ODComputeSummaryTask::Update()
{
   std::vector< std::weak_ptr< ODPCMAliasBlockFile > > tempBlocks;

   mWaveTrackMutex.Lock();

   for(size_t j=0;j<mWaveTracks.size();j++)
   {
      if(mWaveTracks[j])
      {
         BlockArray *blocks;
         Sequence *seq;

         //gather all the blockfiles that we should process in the wavetrack.
         for (const auto &clip : mWaveTracks[j]->GetAllClips()) {
            seq = clip->GetSequence();
            //This lock may be way too big since the whole file is one sequence.
            //TODO: test for large files and find a way to break it down.
            Sequence::DeleteUpdateMutexLocker locker(*seq);

            //See Sequence::Delete() for why need this for now..
            //We don't need the mBlockFilesMutex here because it is only for the vector list.
            //These are existing blocks, and its wavetrack or blockfiles won't be deleted because
            //of the respective mWaveTrackMutex lock and LockDeleteUpdateMutex() call.
            blocks = clip->GetSequenceBlockArray();
            int i;
            int insertCursor;

            insertCursor =0;//OD TODO:see if this works, removed from inner loop (bfore was n*n)

            for(i=0; i<(int)blocks->size(); i++)
            {
               //if there is data but no summary, this blockfile needs summarizing.
               SeqBlock &block = (*blocks)[i];
               const auto &file = block.f;
               if(file->IsDataAvailable() && !file->IsSummaryAvailable())
               {
                  const auto odpcmaFile =
                     std::static_pointer_cast<ODPCMAliasBlockFile>(file);
                  odpcmaFile->SetStart(block.start);
                  odpcmaFile->SetClipOffset(sampleCount(
                     clip->GetStartTime()*clip->GetRate()
                  ));

                  //these will always be linear within a sequence-lets take advantage of this by keeping a cursor.
                  {
                     std::shared_ptr< ODPCMAliasBlockFile > ptr;
                     while(insertCursor < (int)tempBlocks.size() &&
                           (!(ptr = tempBlocks[insertCursor].lock()) ||
                            ptr->GetStart() + ptr->GetClipOffset() <
                            odpcmaFile->GetStart() + odpcmaFile->GetClipOffset()))
                        insertCursor++;
                  }

                  tempBlocks.insert(tempBlocks.begin() + insertCursor++, odpcmaFile);
               }
            }
         }
      }
   }
   mWaveTrackMutex.Unlock();

   //get the NEW order.
   mBlockFilesMutex.Lock();
   OrderBlockFiles(tempBlocks);
   mBlockFilesMutex.Unlock();

   MarkUpdateRan();
}



///Computes the summary calculation queue order of the blockfiles
void ODComputeSummaryTask::OrderBlockFiles
   (std::vector< std::weak_ptr< ODPCMAliasBlockFile > > &unorderedBlocks)
{
   mBlockFiles.clear();
   //Order the blockfiles into our queue in a fancy convenient way.  (this could be user-prefs)
   //for now just put them in linear.  We start the order from the first block that includes the ondemand sample
   //(which the user sets by clicking.)
   //Note that this code assumes that the array is sorted in time.

   //find the startpoint
   auto processStartSample = GetDemandSample();
   std::shared_ptr< ODPCMAliasBlockFile > firstBlock;
   for(auto i = unorderedBlocks.size(); i--;)
   {
      auto ptr = unorderedBlocks[i].lock();
      if(ptr)
      {
         //test if the blockfiles are near the task cursor.  we use the last mBlockFiles[0] as our point of reference
         //and add ones that are closer.
         if(firstBlock &&
            ptr->GetGlobalEnd() >= processStartSample &&
                ( firstBlock->GetGlobalEnd() < processStartSample ||
                  ptr->GetGlobalStart() <= firstBlock->GetGlobalStart())
            )
         {
            //insert at the front of the list if we get blockfiles that are after the demand sample
            firstBlock = ptr;
            mBlockFiles.insert(mBlockFiles.begin(), unorderedBlocks[i]);
         }
         else
         {
            //otherwise no priority
            if ( !firstBlock )
               firstBlock = ptr;
            mBlockFiles.push_back(unorderedBlocks[i]);
         }
         if(mMaxBlockFiles< (int) mBlockFiles.size())
            mMaxBlockFiles = mBlockFiles.size();
      }
      else
      {
         // The block file disappeared.
         // Let it be deleted and forget about it.
      }
   }
}
