/**
 * SMIoT JASON Qualilife sound recorder command line program.
 *
 * Author: Jan Schlüter <jan.schluter@lis-lab.fr>
 * Author: Maxence Ferrari <maxence.ferrari@lis-lab.fr>
 */
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include "recorder.h"
#include "filewriter.h"
#include "cleanexit.h"

void print_usage(char *name) {
    std::cout << "SMIoT JASON Qualilife sound recorder";
#ifdef JASONREC_VERSION
    std::cout << " v" << JASONREC_VERSION;
#endif
    std::cout << std::endl;
    std::cout << "Usage: " << name << " channels rate filename [--help, -h] [--chunk_len, -c CHUNK_LEN] "
                                   << "[--total_len, -t TOTAL_LEN] [--device, -d DEVICE] [--bit_depth, -b BIT_DEPTH] "
                                   << "[--filter, -f FILTER] [--verbose, -v]" << std::endl;
    std::cout << "Positional arguments:" << std::endl;
    std::cout << "  CHANNELS:\tnumber of channels to record (1 to 5)" << std::endl;
    std::cout << "  RATE:\tsample rate in Hz to record at (integral number)" << std::endl;
    std::cout << "  FILENAME:\toutput file name; should include strftime() format specifiers" << std::endl;
    std::cout << "    if CHUNK_LEN is specified. For miliseconds, use %z. Example: location/recording_%Y%m%d_%H%M%S_%z.wav" << std::endl;
    std::cout << "Optional arguments:" << std::endl;
    std::cout << "-h, --help\t\tshow this help message and exit" << std::endl;

    std::cout << "  --bit_depth, -b\tBIT_DEPTH:\tSize of each samples in bits. Must be a multiple of 8. (Default: 16)" << std::endl;
    std::cout << "  --filter, -f\tFILTER:\tNumber of the filter to use. Must be between 0 and 2. (Default: 0)" << std::endl;
    std::cout << "  --chunk_len, -c\tCHUNK_LEN:\tlength per output file in seconds; will start a new file whenever" << std::endl;
    std::cout << "    this length is reached. If not given or zero, will record a single file." << std::endl;
    std::cout << "  --total_len, -t\tTOTAL_LEN:\tTotal recording length; will stop when this length is reached." << std::endl;
    std::cout << "    If not given or zero, will record continuously until killed." << std::endl;
    std::cout << "  --device, -d\tDEVICE:\tWhich device to use in case multiple JASON cards are connected," << std::endl;
    std::cout << "    where 0 is the first, 1 is the second card found (and so on)." << std::endl;
    std::cout << "  --verbose, -v\t\tEnable the printing of status message " << std::endl;
}

int record(size_t channels, size_t rate, size_t depth, size_t filter,  std::string &filename, float chunklen, float totallen, size_t device, bool verbose) {
    JasonRecorder recorder = JasonRecorder(verbose);
    std::cout << "Found " << recorder.get_device_count() << " JASON card(s)." << std::endl;
    if (recorder.get_device_count() == 0) {
        std::cout << "Aborting." << std::endl;
        return 2;
    }
    try {
        // prepare the device
        std::cout << "Selecting device number " << device << "..." << std::endl;
        recorder.set_device(device);
        // prepare the file writer
        std::unique_ptr<FileWriter> filewriter;
        if (chunklen > 0) {
            // implementation note: in C++14 we would use std::make_unique<SplitWavFileWriter>(...)
            filewriter.reset(new SplitWavFileWriter(filename, channels, rate, depth, chunklen * rate));
        }
        else {
            filewriter.reset(new WavFileWriter(filename, channels, rate, depth, totallen * rate));
        }
        // start the recording loop
        std::cout << "Starting to record..." << std::endl;
        allow_clean_exit();
        size_t total_samples_wanted = totallen * rate;
        size_t total_samples_read = 0;
        size_t failed_attempts = 0;
        size_t sample_size = channels * depth;
        std::vector<std::uint8_t> samples;
        try {
            std::cout << "Setting recording format to " << channels << " channels at " << rate << " Hz " << (1<<(2+depth)) << " bits" << std::endl;
            recorder.start_recording(channels, rate, depth, filter);
            // we will record until we have enough (or forever, if totallen == 0)
            while ((total_samples_wanted == 0) || (total_samples_read < total_samples_wanted)) {
                if (exit_requested()) {
                    std::cout << "Termination requested." << std::endl;
                    break;
                }
                recorder.get_samples(samples, false, 500);
                if (samples.size() > 0) {
                    total_samples_read += samples.size() / sample_size;
                    // if we have too much now, crop the last packet
                    if ((total_samples_wanted > 0) && (total_samples_read > total_samples_wanted)) {
                        samples.resize(samples.size() - (total_samples_read - total_samples_wanted) * sample_size);
                    }
                    // pass it on to the file writer
                    filewriter->write(samples);
                    failed_attempts = 0;
                }
                else {
                    // if we received no message or no audio data 20x in a row, abort
                    failed_attempts += 1;
                    if (failed_attempts >= 20) {
                        throw std::runtime_error("Device does not send audio data.");
                    }
                }
            }
            recorder.stop_recording();
            std::cout << "Stopped recording." << std::endl;
        }
        catch (const std::exception& e) {
            recorder.stop_recording();
            throw;
        }
    }
    catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
        return 2;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 4) {
        print_usage(argv[0]);
        return 2;
    }

    // parse command line options
    int num_channels;
    try {
        num_channels = std::stoi(argv[1]);
        if ((num_channels < 1) || (num_channels > MAX_CHANNELS)) {
            std::cout << "Error: CHANNELS must be in 1.." << MAX_CHANNELS << ", got " << num_channels << " instead" << std::endl;
            return 2;}
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[1] << " as an integer." << std::endl;
        return 2;
    }
    int rate;
    try {
        rate = std::stoi(argv[2]);
        if (rate!= 32000 && rate!=64000 && rate!=128000 && rate!=256000 && rate!=512000) {
            std::cout << "Error: RATE must be a power 2 times 32kHz, got " << rate << " instead" << std::endl;
            return 2;
        }
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[2] << " as an integer." << std::endl;
        return 2;
    }
    std::string filename = argv[3];

    int i=4;

    int device(0), bit_depth(16), filter(0);
    float chunklen(0), totallen(0);
    bool verbose(false);
    while (i < argc){
        try{
            if (strcmp(argv[i], "--chunk_len") == 0 || strcmp(argv[i], "-c") == 0) {
                chunklen = atof(argv[++i]);
                if ((chunklen < 0)) {
                    std::cout << "Error: CHUNKLEN must be positive or zero, got " << chunklen << " instead" << std::endl;
                    return 2;
                }}
            else if (strcmp(argv[i], "--device") == 0 || strcmp(argv[i], "-d") == 0) {
                device = atoi(argv[++i]);
                if ((device < 0)) {
                    std::cout << "Error: DEVICE must be nonnegative, got " << device << " instead" << std::endl;
                    return 2;
                }}
            else if (strcmp(argv[i], "--total_len") == 0 || strcmp(argv[i], "-t") == 0) {
                totallen = atof(argv[++i]);
                if ((totallen < 0)) {
                    std::cout << "Error: TOTALLEN must be positive or zero, got " << totallen << " instead" << std::endl;
                    return 2;
                }}
            else if (strcmp(argv[i], "--bit_depth") == 0 || strcmp(argv[i], "-b") == 0) {
                bit_depth = atoi(argv[++i]);
                if (not(bit_depth % 8)) {
                    std::cout << "Error: DEPTH must be a multiple of 8, got " << bit_depth << " instead" << std::endl;
                    return 2;
                }}
            else if (strcmp(argv[i], "--filter") == 0 || strcmp(argv[i], "-f") == 0) {
                filter = atoi(argv[++i]);
                if (filter < 0 || filter > 2 ) {
                    std::cout << "Error: filter must be between 0 and 2, got " << filter << " instead" << std::endl;
                    return 2;
                }}
            else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0){
                print_usage(argv[0]);
                return 1;
            }
            else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0){
                verbose = true;
            }
            else {
                std::cout << "Unrecognized argument " << argv[i] << std::endl;
                return 2;
            }}
        catch (const std::exception& e) {
            std::cout << "Error: Could not interpret " << argv[i] << " ( "  << argv[i-1] << " ) " " as a number." << std::endl;
            return 2;
        }
        i++;
    }
    // hand over to the recording function
    return record(num_channels, rate, bit_depth/8, filter,  filename, chunklen, totallen, device, verbose);
}