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

  Audacity: A Digital Audio Editor

  Sequence.h

  Dominic Mazzoni

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

#ifndef __AUDACITY_SEQUENCE__
#define __AUDACITY_SEQUENCE__

#include <vector>

#include "SampleFormat.h"
#include "xml/XMLTagHandler.h"
#include "ondemand/ODTaskThread.h"

#include "audacity/Types.h"

class BlockFile;
using BlockFilePtr = std::shared_ptr<BlockFile>;

class DirManager;
class wxFileNameWrapper;

// This is an internal data structure!  For advanced use only.
class SeqBlock {
 public:
   BlockFilePtr f;
   ///the sample in the global wavetrack that this block starts at.
   sampleCount start;

   SeqBlock()
      : f{}, start(0)
   {}

   SeqBlock(const BlockFilePtr &f_, sampleCount start_)
      : f(f_), start(start_)
   {}

   // Construct a SeqBlock with changed start, same file
   SeqBlock Plus(sampleCount delta) const
   {
      return SeqBlock(f, start + delta);
   }
};
class BlockArray : public std::vector<SeqBlock> {};
using BlockPtrArray = std::vector<SeqBlock*>; // non-owning pointers

class PROFILE_DLL_API Sequence final : public XMLTagHandler{
 public:

   //
   // Static methods
   //

   static void SetMaxDiskBlockSize(size_t bytes);
   static size_t GetMaxDiskBlockSize();

   //
   // Constructor / Destructor / Duplicator
   //

   Sequence(const std::shared_ptr<DirManager> &projDirManager, sampleFormat format);

   // The copy constructor and duplicate operators take a
   // DirManager as a parameter, because you might be copying
   // from one project to another...
   Sequence(const Sequence &orig, const std::shared_ptr<DirManager> &projDirManager);

   // Sequence cannot be copied without specifying a DirManager
   Sequence(const Sequence&) PROHIBITED;
   Sequence& operator= (const Sequence&) PROHIBITED;

   ~Sequence();

   //
   // Editing
   //

   sampleCount GetNumSamples() const { return mNumSamples; }

   bool Get(samplePtr buffer, sampleFormat format,
            sampleCount start, size_t len, bool mayThrow) const;

   // Note that len is not size_t, because nullptr may be passed for buffer, in
   // which case, silence is inserted, possibly a large amount.
   void SetSamples(samplePtr buffer, sampleFormat format,
            sampleCount start, sampleCount len);

   // where is input, assumed to be nondecreasing, and its size is len + 1.
   // min, max, rms, bl are outputs, and their lengths are len.
   // Each position in the output arrays corresponds to one column of pixels.
   // The column for pixel p covers samples from
   // where[p] up to (but excluding) where[p + 1].
   // bl is negative wherever data are not yet available.
   // Return true if successful.
   bool GetWaveDisplay(float *min, float *max, float *rms, int* bl,
                       size_t len, const sampleCount *where) const;

   // Return non-null, or else throw!
   std::unique_ptr<Sequence> Copy(sampleCount s0, sampleCount s1) const;
   void Paste(sampleCount s0, const Sequence *src);

   size_t GetIdealAppendLen() const;
   void Append(samplePtr buffer, sampleFormat format, size_t len,
               XMLWriter* blockFileLog=NULL);
   void Delete(sampleCount start, sampleCount len);

   using BlockFileFactory =
      std::function< BlockFilePtr( wxFileNameWrapper, size_t /* len */ ) >;
   // An overload of AppendBlockFile that passes the factory to DirManager
   // which supplies it with a file name
   void AppendBlockFile( const BlockFileFactory &factory, size_t len );

   // Append a blockfile. The blockfile pointer is then "owned" by the
   // sequence. This function is used by the recording log crash recovery
   // code, but may be useful for other purposes. The blockfile must already
   // be registered within the dir manager hash. This is the case
   // when the blockfile is created using SimpleBlockFile or
   // loaded from an XML file via DirManager::HandleXMLTag
   void AppendBlockFile(const BlockFilePtr &blockFile);

   void SetSilence(sampleCount s0, sampleCount len);
   void InsertSilence(sampleCount s0, sampleCount len);

   const std::shared_ptr<DirManager> &GetDirManager() { return mDirManager; }

   //
   // XMLTagHandler callback methods for loading and saving
   //

   bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
   void HandleXMLEndTag(const wxChar *tag) override;
   XMLTagHandler *HandleXMLChild(const wxChar *tag) override;
   void WriteXML(XMLWriter &xmlFile) const /* not override */;

   bool GetErrorOpening() { return mErrorOpening; }

   //
   // Lock/Unlock all of this sequence's BlockFiles, keeping them
   // from being moved.  Call this if you want to copy a
   // track to a different DirManager.  See BlockFile.h
   // for details.
   //

   bool Lock();
   bool Unlock();

   bool CloseLock();//similar to Lock but should be called upon project close.
   // not balanced by unlocking calls.

   //
   // Manipulating Sample Format
   //

   sampleFormat GetSampleFormat() const;

   // Return true iff there is a change
   bool ConvertToSampleFormat(sampleFormat format);

   //
   // Retrieving summary info
   //

   std::pair<float, float> GetMinMax(
      sampleCount start, sampleCount len, bool mayThrow) const;
   float GetRMS(sampleCount start, sampleCount len, bool mayThrow) const;

   //
   // Getting block size and alignment information
   //

   // This returns a possibly large or negative value
   sampleCount GetBlockStart(sampleCount position) const;

   // These return a nonnegative number of samples meant to size a memory buffer
   size_t GetBestBlockSize(sampleCount start) const;
   size_t GetMaxBlockSize() const;
   size_t GetIdealBlockSize() const;

   //
   // This should only be used if you really, really know what
   // you're doing!
   //

   BlockArray &GetBlockArray() { return mBlock; }
   const BlockArray &GetBlockArray() const { return mBlock; }

   ///
   void LockDeleteUpdateMutex(){mDeleteUpdateMutex.Lock();}
   void UnlockDeleteUpdateMutex(){mDeleteUpdateMutex.Unlock();}

   // RAII idiom wrapping the functions above
   struct DeleteUpdateMutexLocker {
      DeleteUpdateMutexLocker(Sequence &sequence)
         : mSequence(sequence)
      {
         mSequence.LockDeleteUpdateMutex();
      }
      ~DeleteUpdateMutexLocker()
      {
         mSequence.UnlockDeleteUpdateMutex();
      }
   private:
      Sequence &mSequence;
   };

 private:

   //
   // Private static variables
   //

   static size_t    sMaxDiskBlockSize;

   //
   // Private variables
   //

   std::shared_ptr<DirManager> mDirManager;

   BlockArray    mBlock;
   sampleFormat  mSampleFormat;

   // Not size_t!  May need to be large:
   sampleCount   mNumSamples{ 0 };

   size_t   mMinSamples; // min samples per block
   size_t   mMaxSamples; // max samples per block

   bool          mErrorOpening{ false };

   ///To block the Delete() method against the ODCalcSummaryTask::Update() method
   ODLock   mDeleteUpdateMutex;

   //
   // Private methods
   //

   int FindBlock(sampleCount pos) const;

   static void AppendBlock
      (DirManager &dirManager,
       BlockArray &blocks, sampleCount &numSamples, const SeqBlock &b);

   static bool Read(samplePtr buffer, sampleFormat format,
             const SeqBlock &b,
             size_t blockRelativeStart, size_t len, bool mayThrow);

   // Accumulate NEW block files onto the end of a block array.
   // Does not change this sequence.  The intent is to use
   // CommitChangesIfConsistent later.
   static void Blockify
      (DirManager &dirManager, size_t maxSamples, sampleFormat format,
       BlockArray &list, sampleCount start, samplePtr buffer, size_t len);

   bool Get(int b, samplePtr buffer, sampleFormat format,
      sampleCount start, size_t len, bool mayThrow) const;

public:

   //
   // Public methods
   //

   // This function throws if the track is messed up
   // because of inconsistent block starts & lengths
   void ConsistencyCheck (const wxChar *whereStr, bool mayThrow = true) const;

   // This function prints information to stdout about the blocks in the
   // tracks and indicates if there are inconsistencies.
   static void DebugPrintf
      (const BlockArray &block, sampleCount numSamples, wxString *dest);

private:
   static void ConsistencyCheck
      (const BlockArray &block, size_t maxSamples, size_t from,
       sampleCount numSamples, const wxChar *whereStr,
       bool mayThrow = true);

   // The next two are used in methods that give a strong guarantee.
   // They either throw because final consistency check fails, or swap the
   // changed contents into place.

   void CommitChangesIfConsistent
      (BlockArray &newBlock, sampleCount numSamples, const wxChar *whereStr);

   void AppendBlocksIfConsistent
      (BlockArray &additionalBlocks, bool replaceLast,
       sampleCount numSamples, const wxChar *whereStr);

};

#endif // __AUDACITY_SEQUENCE__

