/**
 * SMIoT JASON Qualilife sound recorder command line program.
 *
 * Author: Jan Schlüter <jan.schluter@lis-lab.fr>
 */
#include <iostream>
#include <string>
#include <memory>
#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 [CHUNKLEN [TOTALLEN [DEVICE]]]" << std::endl;
    std::cout << "  CHANNELS: number of channels to record (1 to 5)" << std::endl;
    std::cout << "  RATE: sample rate in Hz to record at (integral number)" << std::endl;
    std::cout << "  FILENAME: output file name; should include strftime() format specifiers" << std::endl;
    std::cout << "    if CHUNKLEN is specified. For miliseconds, use %z. Example: location/recording_%Y%m%d_%H%M%S_%z.wav" << std::endl;
    std::cout << "  CHUNKLEN: length 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 << "  TOTALLEN: total 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: which 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;
}

int record(size_t channels, size_t rate, std::string &filename, float chunklen, float totallen, size_t device) {
    JasonRecorder recorder = JasonRecorder();
    std::cout << "Found " << recorder.get_device_count() << " JASON card(s)." << std::endl;
    if (recorder.get_device_count() == 0) {
        std::cout << "Aborting." << std::endl;
        return 1;
    }
    try {
        // prepare the device
        std::cout << "Selecting device number " << device << "..." << std::endl;
        recorder.set_device(device);
        std::cout << "Setting recording format to " << channels << " channels at " << rate << " Hz..." << std::endl;
        recorder.set_format(channels, rate);
        // 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, chunklen * rate));
        }
        else {
            filewriter.reset(new WavFileWriter(filename, channels, rate, 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;
        std::vector<std::int16_t> samples;
        try {
            recorder.start_recording();
            // 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() / channels;
                    // 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) * channels);
                    }
                    // 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 1;
    }
    return 0;
}

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

    // parse command line options
    int num_channels;
    try {
        num_channels = std::stoi(argv[1]);
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[1] << " as an integer." << std::endl;
        return 1;
    }
    int rate;
    try {
        rate = std::stoi(argv[2]);
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[2] << " as an integer." << std::endl;
        return 1;
    }
    std::string filename = argv[3];
    float chunklen;
    try {
        chunklen = (argc > 4) ? std::stof(argv[4]) : 0;
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[4] << " as a float." << std::endl;
        return 1;
    }
    float totallen;
    try {
        totallen = (argc > 5) ? std::stof(argv[5]) : 0;
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[5] << " as a float." << std::endl;
        return 1;
    }
    int device;
    try {
        device = (argc > 6) ? std::stoi(argv[6]) : 0;
    }
    catch (const std::exception& e) {
        std::cout << "Error: Could not interpret " << argv[6] << " as an integer." << std::endl;
        return 1;
    }

    // check command line options for validity
    if ((num_channels < 1) || (num_channels > 5)) {
        std::cout << "Error: CHANNELS must be in 1..5, got " << num_channels << " instead" << std::endl;
        return 1;
    }
    if ((rate <= 0)) {
        std::cout << "Error: RATE must be positive, got " << rate << " instead" << std::endl;
        return 1;
    }
    if ((chunklen < 0)) {
        std::cout << "Error: CHUNKLEN must be positive or zero, got " << chunklen << " instead" << std::endl;
        return 1;
    }
    if ((totallen < 0)) {
        std::cout << "Error: TOTALLEN must be positive or zero, got " << totallen << " instead" << std::endl;
        return 1;
    }
    if ((device < 0)) {
        std::cout << "Error: DEVICE must be nonnegative, got " << device << " instead" << std::endl;
        return 1;
    }

    // hand over to the recording function
    return record(num_channels, rate, filename, chunklen, totallen, device);
}