/**
 * Wave file writing, with or without splitting by length.
 *
 * Author: Jan Schlüter <jan.schluter@lis-lab.fr>
 * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr>
 */

#ifndef FILEWRITER_H
#define FILEWRITER_H

#include <cstdint>
#include <string>
#include <vector>
#include <fstream>


/** Abstract base class for writing sample data to files.
 */
class FileWriter {
protected:
    // output file name template
    std::string filename_template;
    std::string generate_filename();

    // sample format options
    size_t num_channels;
    size_t sample_rate;
    size_t depth;
public:
    /** Abstract constructor to be used by subclasses.
     */
    FileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth);
    virtual ~FileWriter();

    /** Writes out the given vector of 8-bit samples.
     * \param[in] samples The samples to write, with interleaved channels.
     */
    void write(std::vector<uint8_t> &samples, std::vector<uint8_t> &imu_data);
    virtual void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) = 0;
};


/** The header of a PCM WAVE file as stored on disk.
 */
typedef struct {
    char chunk_id[4] = {'R', 'I', 'F', 'F'};
    std::uint8_t chunk_size[4] = {0, 0, 0, 0};  // sample data size + 36 = file size - 8, little endian
    char riff_type[4] = {'W', 'A', 'V', 'E'};
    char fmt_id[4] = {'f', 'm', 't', ' '};
    std::uint8_t fmt_length[4] = {16, 0, 0, 0};  // 16, little endian
    std::uint8_t fmt_tag[2] = {1, 0};  // 0x0001 (= PCM), little endian
    std::uint8_t fmt_channels[2] = {0, 0};  // number of channels, little endian
    std::uint8_t fmt_sample_rate[4] = {0, 0, 0, 0};  // samples per second, little endian
    std::uint8_t fmt_byte_rate[4] = {0, 0, 0, 0};  // bytes per second (per channel), little endian
    std::uint8_t fmt_frame_size[2] = {0, 0};  // channels * 16 bit, little endian
    std::uint8_t fmt_bits_per_sample[2] = {16, 0};  // 16, little endian
    char data_id[4] = {'d', 'a', 't', 'a'};
    std::uint8_t data_size[4] = {0, 0, 0, 0};  // sample data size = file size - 44, little endian
} WavFileHeader;
// TODO: possibly use WAVEFORMATEXTENSIBLE for more than two channels:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/extensible-wave-format-descriptors


/** Class for writing sample data to a single PCM WAVE file.
 */
class WavFileWriter: public FileWriter {
private:
    std::ofstream outfile;
    WavFileHeader header;
    size_t samples_written;
public:
    /** Instantiates a wave file writer.
     * \param[in] filename_template The name of the file to write to. Will be
     * created or opened immediately, truncating any existing content. May
     * contain format specifiers as understood by the strftime() function,
     * filled in based on the current system date and local time.
     * \param[in] num_channels The number of channels the sample data to be
     * written will contain.
     * \param[in] sample_rate The number of samples per second (per channel) the
     * sample data to be written will contain.
     * \param[in] depth The number of bytes per samples the
     * sample data to be written will contain.
     */
    WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth);
    /** Instantiates a wave file writer.
     * \param[in] filename_template The name of the file to write to. Will be
     * created or opened immediately, truncating any existing content. May
     * contain format specifiers as understood by the strftime() function,
     * filled in based on the current system date and local time.
     * \param[in] num_channels The number of channels the sample data to be
     * written will contain.
     * \param[in] sample_rate The number of samples per second (per channel) the
     * sample data to be written will contain.
     * \param[in] depth The number of bytes per samples the
     * sample data to be written will contain.
     * \param[in] expected_num_samples The expected total number of samples (per
     * channel) that will be written. The file header will be written
     * accordingly and not rewritten on closing the file if the number matches.
     */
    WavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth,
                  size_t expected_num_samples);
    ~WavFileWriter() override;

    void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override;
};


/** Class for writing sample data to a sequence of PCM WAVE files, split up to
 *  reach a given target length per file.
 */
class SplitWavFileWriter: public FileWriter {
private:
    size_t samples_per_file;
    WavFileWriter *current_file;
    size_t current_file_samples_written;
public:
    /** Instantiates a splitted wave file writer.
     * \param[in] filename_template The name of the file to write to. Will be
     * created or opened when required, truncating any existing content. Should
     * contain format specifiers as understood by the strftime() function,
     * filled in based on the current system date and local time, otherwise it
     * will be repeatedly overwritten.
     * \param[in] num_channels The number of channels the sample data to be
     * written will contain.
     * \param[in] sample_rate The number of samples per second (per channel) the
     * sample data to be written will contain.
     * \param[in] depth The number of bytes per samples the
     * sample data to be written will contain.
     * \param[in] samples_per_file The target number of samples (per channel)
     * that will be written to a file before starting the next one.
     */
    SplitWavFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate,
                       size_t depth, size_t samples_per_file);
    ~SplitWavFileWriter() override;

    void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override;
};


class IMUFileWriter: public FileWriter {
    // const std::string header = "Timestamp,ax,ay,az,gx,gy,gz,mx,my,mz\n";
    const std::string header = "Sensor Type,TimeStamp(ms) or Time, val0,val1,val2,val3,val4,val5,val6,val7\n";
    const size_t frame_size = 32;
    const size_t additional_data_size = 736;
private:
    enum class SensorType {
        Unknown = 0,
        Accel = 1,
        Gyro = 2,
        Mag = 3,
        Temperature = 4,
        Pressure = 5,
        Light = 6,
        Piezo = 7
    };
    struct DateTime {
        unsigned short year;
        unsigned char month;
        unsigned char day;
        unsigned char weekDay;
        unsigned char hour;
        unsigned char minute;
        unsigned char second;
    };
    std::ofstream outfile;
    size_t last_timestamp;
    unsigned int lastAccelTimeStamp;
    unsigned int lastGyroTimeStamp;
    unsigned int lastMagTimeStamp;
    unsigned int lastLightTimeStamp;
    unsigned int lastPressureTimeStamp;
    unsigned int lastTemperatureTimeStamp;
    unsigned int lastTimeStamp;
    DateTime lastGPSDate;
    double lastPPSTimeStampNS;
public:
    /** Instantiates a splitted wave file writer.
     * \param[in] filename_template The name of the file to write to. Will be
     * created or opened when required, truncating any existing content. Should
     * contain format specifiers as understood by the strftime() function,
     * filled in based on the current system date and local time, otherwise it
     * will be repeatedly overwritten.
     * \param[in] num_channels The number of channels the sample data to be
     * written will contain.
     * \param[in] sample_rate The number of samples per second (per channel) the
     * sample data to be written will contain.
     * \param[in] depth The number of bytes per samples the
     * sample data to be written will contain.
     * \param[in] samples_per_file The target number of samples (per channel)
     * that will be written to a file before starting the next one.
     */
    IMUFileWriter(std::string &filename_template, size_t num_channels, size_t sample_rate, size_t depth, size_t timestamp);
    void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override;
    size_t get_last_timestamp();
    float GetFloatSafe(const unsigned char *p, int index);
};

/** Class for writing sample data to a sequence of PCM WAVE files, split up to
 *  reach a given target length per file.
 */
class SplitIMUWavFileWriter: public FileWriter {
private:
    size_t samples_per_file;
    WavFileWriter *current_file;
    size_t current_file_samples_written;
    std::string &imu_name_template;
    IMUFileWriter *imu_file;
    size_t max_timestamp;
public:
    /** Instantiates a splitted wave file writer.
     * \param[in] filename_template The name of the file to write to. Will be
     * created or opened when required, truncating any existing content. Should
     * contain format specifiers as understood by the strftime() function,
     * filled in based on the current system date and local time, otherwise it
     * will be repeatedly overwritten.
     * \param[in] num_channels The number of channels the sample data to be
     * written will contain.
     * \param[in] sample_rate The number of samples per second (per channel) the
     * sample data to be written will contain.
     * \param[in] depth The number of bytes per samples the
     * sample data to be written will contain.
     * \param[in] samples_per_file The target number of samples (per channel)
     * that will be written to a file before starting the next one.
     */
    SplitIMUWavFileWriter(std::string &filename_template, std::string &imu_name_template, size_t num_channels,
                          size_t sample_rate, size_t depth, size_t samples_per_file);
    ~SplitIMUWavFileWriter() override;

    void write(uint8_t *samples, size_t num_samples, uint8_t *imu_data) override;
};


#endif  // FILEWRITER_H