/*
   HARK Library
   Copyright (C) 2021 Honda Research Institute Japan Inc.
 */

#include "hark-lib/node/AudioStreamFromMemory.h"

#include "hark-lib/matrix.h"
#include "hark-lib/internal/lock.h"
#include "hark-lib/internal/mutex.h"
#include "hark-lib/internal/condition_variable.h"

#include <istream>
#include <fstream>
#include <condition_variable>

namespace hark
{
namespace node
{
    AudioStreamFromMemory::AudioStreamFromMemory():
        NodeDef("AudioStreamFromMemory"),
        _length(0),
        _advance(0),
        _channels(0),
        _bytes_per_sample(0),
        _sampling_rate(0),
        _maximum_buffer_length(0),
        _use_wait(false),
        // _last_input_count(0),
        _audio_buffer(),
        // _audio_buffer_offset(0),
        // _audio_buffer_count(0),
        _audio_closed(false),
        _mutex(new Mutex()),
        _condvar(new ConditionVariable())
    {
        *this
            << Terminal::in("INPUT")
            << Terminal::in("SAMPLING_RATE").def(16000)
            << Terminal::in("SAMPLE_BITS").def(16)
            << Terminal::in("CHANNEL_COUNT").def(1)
            << Terminal::in("LENGTH", "The frame length of each channel (in samples) [default: 512].").def(512)
            << Terminal::in("ADVANCE", "The shift length beween adjacent frames (in samples)[default: 160].").def(160)
            << Terminal::in("USE_WAIT", "If true, real recording is simulated [default: false].").def(false)
            << Terminal::in("MAXIMUM_BUFFER_LENGTH").def(10.0)
            >> Terminal::out("AUDIO", "Windowed sound data. A row index is channel, and a column index is time.")
            >> Terminal::out("NOT_EOF", "True unless End-of-File.");
    }

    void
    AudioStreamFromMemory::reset()
    {
        this->_sampling_rate = 0;
        this->_channels = 0;
        this->_bytes_per_sample = 0;
        this->_maximum_buffer_length = 0;
        this->_use_wait = false;

        this->_length = 0;
        this->_advance = 0;
        this->_buffer.clear();
        this->_fbuffer.clear();

        this->_audio_buffer.clear();
        // this->_audio_buffer_count = 0;
        // this->_audio_buffer_offset = 0;
        this->_audio_closed = false;
        // this->_last_input_count = 0;
    }

    void
    AudioStreamFromMemory::initialize(InputDataBundle &input)
    {
        this->_sampling_rate = input["SAMPLING_RATE"].last().as<int>();
        this->_channels = input["CHANNEL_COUNT"].last().as<int>();
        this->_bytes_per_sample = input["SAMPLE_BITS"].last().as<int>() / 8;
        this->_maximum_buffer_length = input["MAXIMUM_BUFFER_LENGTH"].last().as<double>();

        this->_length = input["LENGTH"].last().as<int>();
        this->_advance = input["ADVANCE"].last().as<int>();
        this->_use_wait = input["USE_WAIT"].last().as<bool>();
        this->_buffer.resize(this->_channels * this->_length * sizeof(short int));
        this->_fbuffer.resize(this->_channels * this->_length * sizeof(short int));

        this->_audio_buffer.clear();
        // size_t maximum_buffer_size = (size_t)(this->_sampling_rate * this->_maximum_buffer_length);
        // this->_audio_buffer.resize(maximum_buffer_size);
        // this->_audio_buffer_count = 0;
        // this->_audio_buffer_offset = 0;
        this->_audio_closed = false;
        // this->_last_input_count = 0;
    }

    size_t
    AudioStreamFromMemory::unshift_audio(char *dst, size_t count, bool block)
    {
        UniqueLock lock(this->_mutex.get());

        size_t nread = 0;

        nread = std::min(this->_audio_buffer.size(), count);
        std::copy(this->_audio_buffer.begin(), this->_audio_buffer.begin() + nread, dst);
        this->_audio_buffer.erase(this->_audio_buffer.begin(), this->_audio_buffer.begin() + count);

        // while(count > 0){
        //     size_t cursor = 0;
        //     size_t toread = 0;
        //     do {
        //         cursor = this->_audio_buffer_offset % this->_audio_buffer.size();
        //         toread = std::min(std::min(count, this->_audio_buffer.size() - cursor), this->_audio_buffer_count - this->_audio_buffer_offset);
        //         if(toread == 0){
        //             if(!this->_audio_closed && block){
        //                 this->_condvar->wait(lock);
        //             }else{
        //                 break;
        //             }
        //         }
        //     } while(toread == 0);
        //     if(toread == 0){
        //         break;
        //     }

        //     std::memcpy(dst, this->_audio_buffer.data() + cursor, sizeof(*dst) * toread);

        //     this->_audio_buffer_offset += toread;
        //     count -= toread;
        //     dst += toread;
        //     nread += toread;
        // }

//        std::cout << "AudioStreamFromMemory: unshift_audio: nread=" << nread << ", count=" << this->_audio_buffer_count << ", offset=" << this->_audio_buffer_offset << std::endl;
        return nread;
    }

    size_t
    AudioStreamFromMemory::push_audio(const char *src, size_t count, bool block)
    {
        UniqueLock lock(this->_mutex.get());

        size_t nwritten = 0;

//        this->_audio_buffer.insert(this->_audio_buffer.end(), src, src + count);
        for(size_t i = 0; i < count; i++){
            this->_audio_buffer.push_back(src[i]);
        }
        nwritten = count;

        // while(count > 0 && this->_audio_buffer_offset + this->_audio_buffer.size() >= this->_audio_buffer_count){
            
        //     size_t cursor = 0, towrite = 0;
        //     while(true) {
        //         cursor = this->_audio_buffer_count % this->_audio_buffer.size();
        //         towrite = std::min(std::min(count, this->_audio_buffer.size() - cursor), this->_audio_buffer.size() - (this->_audio_buffer_count - this->_audio_buffer_offset));
            
        //         if(towrite == 0){
        //             if(block){
        //                 this->_condvar->wait(lock);
                        
        //             }else{
        //                 throw RuntimeError("AudioStreamFromMemory: Audio buffer overflown.");
        //             }
        //         }else{
        //             break;
        //         }
        //     }
                              
        //     std::memcpy(this->_audio_buffer.data() + cursor, src, sizeof(*src) * towrite);

        //     this->_audio_buffer_count += towrite;
        //     count -= towrite;
        //     src   += towrite;
        //     nwritten += towrite;
        // }

//        std::cout << "AudioStreamFromMemory: push_audio: nwritten=" << nwritten << ", count=" << this->_audio_buffer_count << ", offset=" << this->_audio_buffer_offset << std::endl;
        this->_condvar->notify_one();
        return nwritten;
    }

    void
    AudioStreamFromMemory::close_audio()
    {
        this->_audio_closed = true;
    }

    bool
    AudioStreamFromMemory::eof() const
    {
        // return this->_audio_closed && this->_audio_buffer_count == this->_audio_buffer_offset;
        return this->_audio_closed && this->_audio_buffer.empty();
    }

    size_t
    AudioStreamFromMemory::audio_buffer_available() const
    {
        // return this->_audio_buffer_count - this->_audio_buffer_offset;
        return this->_audio_buffer.size();
    }

    bool
    AudioStreamFromMemory::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        if(count == 0){
            try{
                this->initialize(input);
            }catch(std::exception &ex){
                std::stringstream ss;
                ss << "AudioStreamFromMemory: Failed to initialize: " << ex.what();
                throw RuntimeError(ss.str());
            }
        }
        
        const int bytes_per_frame = this->_channels * this->_bytes_per_sample;
        auto &buffer  = this->_buffer;
        auto &fbuffer = this->_fbuffer;

        const size_t required_bytes = (buffer.empty() ? this->_length : this->_advance) * bytes_per_frame;
        const size_t buffer_offset  = (buffer.empty() ? 0 : this->_length - this->_advance) * bytes_per_frame;

        bool processed = false;
        if(this->audio_buffer_available() < required_bytes){
            //// バッファに十分な音声がたまっていない

            // 入力からバッファへデータを移動
            const std::vector<short int> &audio = input["INPUT"].last().cast<std::vector<short int>>();
            this->push_audio((const char *)audio.data(), audio.size() * sizeof(short int), false);

            // 入力を処理した旨返す
            processed = true;
        }

        data audioOutputData = data::create<hark::Matrix<float>>(this->_channels, this->_length);
        auto &audioOutput = audioOutputData.cast<hark::Matrix<float>>();
        if(this->audio_buffer_available() >= required_bytes){
            //// バッファに十分な音声がたまっている

            // 出力となる音声データを生成
            memmove(buffer.data(), buffer.data() + required_bytes, buffer_offset);
            this->unshift_audio(buffer.data() + buffer_offset, required_bytes);
            auto end = this->convert_bytes_to_float(buffer.begin(), buffer.end(), fbuffer.begin(), this->_bytes_per_sample);

            // 出力データとして格納
            audioOutput = hark::Matrix<float>::from_col_major_image(fbuffer.data(), this->_channels, this->_length);
            output["AUDIO"].push(audioOutputData);
            output["NOT_EOF"].push(data(true));

        }else{
            // （入力から音声を取得してバッファにため込んだが、それでも）足りない
            // →何もしない。processed = true により、再度コールされるはずである。
        }

        return processed;

//         if(input["INPUT"].count() < this->_last_input_count){
// //            std::cout << "not ready" << std::endl;
// //            return false;
//         }else{
//             while(input["INPUT"].count() >= this->_last_input_count){
// //            std::cout << "ready" << std::endl;
//                 const std::vector<short int> &audio = input["INPUT"].at(this->_last_input_count).cast<std::vector<short int>>();
//                 this->push_audio((const char *)audio.data(), audio.size() * sizeof(short int), false);
//                 this->_last_input_count++;
//             }
//         }
        
//         data audioOutputData = data::create<hark::Matrix<float>>(this->_channels, this->_length);
//         auto &audioOutput = audioOutputData.cast<hark::Matrix<float>>();
//         bool eofOutput = false;

//         bool to_process = false;
//         if(buffer.empty()){
//             buffer.resize(this->_length * bytes_per_frame);
//             if(this->audio_buffer_available() >= this->_length * bytes_per_frame){
//                 this->unshift_audio(buffer.data(), this->_length * bytes_per_frame);
//                 auto end = this->convert_bytes_to_float(buffer.begin(), buffer.end(), fbuffer.begin(), this->_bytes_per_sample);
//                 to_process = true;
//             }else{
// #if defined(VERBOSE_DEBUG)
//             {
//                 std::stringstream ss;
//                 ss << "AudioStreamFromMemory::operator(): avilable-data is not enough: size=" << this->audio_buffer_available() << ", required=" << this->_length * bytes_per_frame << std::endl;
//                 std::cout << ss.str();
//             }
// #endif
//             }
//                 // std::cout << "convert: " << (end - fbuffer.begin()) << " elements." << std::endl;
//         }else{
//             if(this->audio_buffer_available() >= this->_advance * bytes_per_frame){
//                 memmove(buffer.data(), buffer.data() + (this->_advance * bytes_per_frame), (this->_length - this->_advance) * bytes_per_frame);
//                 this->unshift_audio(buffer.data() + (this->_length - this->_advance) * bytes_per_frame, this->_advance * bytes_per_frame);
//                 auto end = this->convert_bytes_to_float(buffer.begin(), buffer.end(), fbuffer.begin(), this->_bytes_per_sample);
//                 to_process = true;
//             }else{
// #if defined(VERBOSE_DEBUG)
//             {
//                 std::stringstream ss;
//                 ss << "AudioStreamFromMemory::operator(): avilable-data is not enough: size=" << this->audio_buffer_available() << ", required=" << this->_advance * bytes_per_frame << std::endl;
//                 std::cout << ss.str();
//             }
// #endif
//             }
//         }
        
//         if(!to_process){
//             return false;
//         }
//         // audioOutput = Eigen::Map<hark::Matrix<float>>(fbuffer.data(), this->_channels, this->_length);
//         // fbuffer[0] = 1000 + count;
//         audioOutput = hark::Matrix<float>::from_col_major_image(fbuffer.data(), this->_channels, this->_length);

//         if(this->eof()){
// #if defined(VERBOSE_DEBUG)
//             {
//                 std::stringstream ss;
//                 ss << "AudioStreamFromMemory::operator(): eof" << std::endl;
//                 std::cout << ss.str();
//             }
// #endif
//             eofOutput = true;
//         }else{
// //             std::cout << "AudioStreamFromMemory: count=" << count << ", output=" << audioOutput << std::endl;
// #if defined(VERBOSE_DEBUG)
//             {
//                 std::stringstream ss;
//                 ss << "AudioStreamFromMemory::operator(): push-output: size=" << audioOutput.size() << std::endl;
//                 std::cout << ss.str();
//             }
// #endif
//             output["AUDIO"].push(audioOutputData);
//         }
//         output["NOT_EOF"].push(data(!eofOutput));

//         return true;
    }

    std::vector<float>::iterator
    AudioStreamFromMemory::convert_bytes_to_float(std::vector<std::istream::char_type>::iterator start,
                                                std::vector<std::istream::char_type>::iterator end,
                                                std::vector<float>::iterator into,
                                                size_t bytes_per_sample)
    {
        std::vector<std::istream::char_type>::iterator sit;
        std::vector<float>::iterator dit;

        switch(bytes_per_sample){
        case 3:
            for(sit = start, dit = into; sit != end; ){
                std::istream::char_type ch2 = *sit++;
                std::istream::char_type ch1 = *sit++;
                std::istream::char_type ch0 = *sit++;
                *dit++ = (float)(((int)ch0 << 16) + ((int)(unsigned char)ch1 << 8) + (int)(unsigned char)ch2);
            }
            break;

        case 2:
            for(sit = start, dit = into; sit != end; ){
                std::istream::char_type ch1 = *sit++;
                std::istream::char_type ch0 = *sit++;
                *dit = (float)(((int)ch0 << 8) + (int)(unsigned char)ch1);
                // std::cout << *dit << " = " << (int)ch0 << ", " << (int)(unsigned char)ch1 << std::endl;
                dit++;
            }
            break;

        }
        return dit;
    }

}
}

/* end of file */
