#ifndef BWF_FILE_HPP__
#define BWF_FILE_HPP__

#include <array>

#include <QFile>
#include <QMap>
#include <QByteArray>

#include "pimpl_h.hpp"

class QObject;
class QString;
class QAudioFormat;

//
// BWFFile - Broadcast Wave Format File (a.k.a. WAV file)
//
// The  BWF file  format is  a  backward compatible  variation of  the
// Microsoft  WAV file  format. It  contains  an extra  chunk with  id
// 'bext' that contains metadata defined by the EBU in:
//
//  https://tech.ebu.ch/docs/tech/tech3285.pdf
//
// Also relevant is the recommendation document:
//
//  https://tech.ebu.ch/docs/r/r098.pdf
//
// which suggests a format to the free text coding history field.
//
// This class also supports the LIST-INFO chunk type which also allows
// metadata to be  added to a WAV  file, the defined INFO  tag ids are
// documented here:
//
//  http://bwfmetaedit.sourceforge.net/listinfo.html
//
// These  ids  are not  enforced  but  they  are recommended  as  most
// operating systems and audio applications  recognize some or more of
// them. Notably Microsoft Windows is not one of the operating systems
// that  does :(  In fact  there seems  to be  no documented  metadata
// tagging format that Windows Explorer recognizes.
//
// Changes to  the 'bext' fields  and the LIST-INFO dictionary  may be
// made right up  until the file is closed as  the relevant chunks are
// saved to the end of the file after the end of the sample data.
//
// This class emulates the QFile class, in fact it uses a QFile object
// instance internally and forwards many of its operations directly to
// it.
//
// BWFFile  is a  QIODevice subclass  and the  implementation provides
// access to  the audio sample  data contained in  the BWF file  as if
// only that data were  in the file. I.e. the first  sample is at file
// offset zero  and the  size of the  file is the  size of  the sample
// data.  The headers,  trailers and  metadata are  hidden but  can be
// accessed by the operations below.
//
class BWFFile
  : public QIODevice
{
  Q_OBJECT
public:
  using FileHandleFlags = QFile::FileHandleFlags;
  using Permissions = QFile::Permissions;
  using FileError = QFile::FileError;
  using MemoryMapFlags = QFile::MemoryMapFlags;
  using InfoDictionary = QMap<std::array<char, 4>, QByteArray>;
  using UMID = std::array<quint8, 64>;

  explicit BWFFile (QAudioFormat const&, QObject * parent = nullptr);
  explicit BWFFile (QAudioFormat const&, QString const& name,
                    QObject * parent = nullptr);

  // The  InfoDictionary should  contain  valid  WAV format  LIST-INFO
  // identifiers as keys, a list of them can be found here:
  //
  // http://bwfmetaedit.sourceforge.net/listinfo.html
  //
  // For  files  opened for  ReadOnly  access  the dictionary  is  not
  // written to  the file.  For  files opened ReadWrite,  any existing
  // LIST-INFO tags will  be merged into the dictionary  when the file
  // is opened and if the file  is modified the merged dictionary will
  // be written back to the file.
  //
  // Note that the sample  data may no be in the  native endian, it is
  // the   callers   responsibility   to  do   any   required   endian
  // conversions. The  internal data is  always in native  endian with
  // conversions  being handled  automatically. Use  the BWF::format()
  // operation     to    access     the    format     including    the
  // QAudioFormat::byteOrder()  operation to  determine the  data byte
  // ordering.
  //
  explicit BWFFile (QAudioFormat const&, QString const& name,
                    InfoDictionary const&, QObject * parent = nullptr);

  ~BWFFile ();
  QAudioFormat const& format () const;
  InfoDictionary& list_info ();

  //
  // Broadcast Audio Extension fields
  //
  // If any of these modifiers are  called then a "bext" chunk will be
  // written to the file if the  file is writeable and the sample data
  // is modified.
  //
  enum class BextVersion : quint16 {v_0, v_1, v_2};
  BextVersion bext_version () const;
  void bext_version (BextVersion = BextVersion::v_2);

  QByteArray bext_description () const;
  void bext_description (QByteArray const&); // max 256 bytes

  QByteArray bext_originator () const;
  void bext_originator (QByteArray const&);        // max 32 bytes

  QByteArray bext_originator_reference () const;
  void bext_originator_reference (QByteArray const&); // max 32 bytes

  QDateTime bext_origination_date_time () const;
  void bext_origination_date_time (QDateTime const&); // 1s resolution

  quint64 bext_time_reference () const;
  void bext_time_reference (quint64); // samples since midnight at start

  UMID bext_umid () const; // bext version >= 1 only
  void bext_umid (UMID const&);

  quint16 bext_loudness_value () const;
  void bext_loudness_value (quint16); // bext version >= 2 only

  quint16 bext_loudness_range () const;
  void bext_loudness_range (quint16); // bext version >= 2 only

  quint16 bext_max_true_peak_level () const;
  void bext_max_true_peak_level (quint16); // bext version >= 2 only

  quint16 bext_max_momentary_loudness () const;
  void bext_max_momentary_loudness (quint16); // bext version >= 2 only

  quint16 bext_max_short_term_loudness () const;
  void bext_max_short_term_loudness (quint16); // bext version >= 2 only

  QByteArray bext_coding_history () const;
  void bext_coding_history (QByteArray const&); // See EBU R 98


  // Emulate QFile interface
  bool open (OpenMode) override;
  bool open (FILE *, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
  bool open (int fd, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
  bool copy (QString const& new_name);
  bool exists () const;
  bool link (QString const& link_name);
  bool remove ();
  bool rename (QString const& new_name);
  void setFileName (QString const& name);
  QString symLinkTarget () const;
  QString fileName () const;
  Permissions permissions () const;

  // Resize is of the sample data portion, header and trailer chunks
  // are excess to the given size
  bool resize (qint64 new_size);

  bool setPermissions (Permissions permissions);
  FileError error () const;
  bool flush ();
  int handle () const;

  // The mapping offset is relative to the start of the sample data
  uchar * map (qint64 offset, qint64 size,
               MemoryMapFlags = QFile::NoOptions);
  bool unmap (uchar * address);

  void unsetError ();


  //
  // QIODevice implementation
  //

  // The size returned is of the sample data only, header and trailer
  // chunks are hidden and handled internally
  qint64 size () const override;

  bool isSequential () const override;

  // The reset  operation clears the  'bext' and LIST-INFO as  if they
  // were  never supplied.  If the  file  is writable  the 'bext'  and
  // LIST-INFO chunks will not be  written making the resulting file a
  // lowest common denominator WAV file.
  bool reset () override;

  // Seek offsets are relative to the start of the sample data
  bool seek (qint64) override;

  // this can fail due to updating header issues, errors are ignored
  void close () override;

protected:
  qint64 readData (char * data, qint64 max_size) override;
  qint64 writeData (char const* data, qint64 max_size) override;

private:
  class impl;
  pimpl<impl> m_;
};

#endif