#ifndef BWF_FILE_HPP__
#define BWF_FILE_HPP__

#include "JS8_Include/pimpl_h.h"

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

#include <array>

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;

    quint16 bitsPerSample() const;
    quint16 blockAlign() const;

    // 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
