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

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

#if HARKLIB_MOSQUITTO_AVAILABLE

#include "hark-lib/matrix.h"
#include <string>
#include <fstream>

namespace hark
{
namespace node
{

    SubscriberFLAC::SubscriberFLAC():
        NodeDef("SubscriberFLAC")
    {
        *this
            << Terminal::in("ENABLE_SAVE_SUBSCRIBED").def(false)
            << Terminal::in("SAVE_SUBSCRIBED_FILENAME_PREFIX").def("subscribed_")
            << Terminal::in("ENABLE_SAVE_DECODED").def(false)
            << Terminal::in("SAVE_DECODED_FILENAME_PREFIX").def("decoded_")
            << Terminal::in("LENGTH").def(512)
            << Terminal::in("ADVANCE").def(160)
            << Terminal::in("MQTT_HOST").def("localhost")
            << Terminal::in("MQTT_PORT").def(1883)
            << Terminal::in("MQTT_TOPIC").def("hark/data/flac")
            << Terminal::in("MQTT_USERNAME").def("")
            << Terminal::in("MQTT_PASSWORD").def("")
            << Terminal::in("MQTT_CAFILE").def("")
            << Terminal::in("MQTT_TIMEOUT").def(60)
            << Terminal::in("DEBUG").def(false)
            >> Terminal::out("OUTPUT")
            >> Terminal::out("CONDITION");
    }

    void
    SubscriberFLAC::reset()
    {
        this->param_b_enable_save_subscribed = false;
        this->param_s_subscribed_filename_prefix.clear();
        this->param_b_enable_save_decoded = false;
        this->param_s_decoded_filename_prefix.clear();
        this->param_i_length = 0;
        this->param_i_advance = 0;
        this->param_i_keepalive = 0;
        this->param_i_timeout = 0;
        this->isDebug = false;
        delete this->mqttobj;
        this->mqttobj = 0;
        delete this->decoder_p;
        this->decoder_p = 0;
        this->recv_size = 0;
        this->decode_size = 0;
        this->recv_completed = 0;
        this->exit_flag = false;
        this->last_recieved = 0;
        this->started = 0;
        delete this->state_publisher;
        this->state_publisher = 0;
        this->total_channels = 0;
        this->frame_buffer.clear();
    }

    void
    SubscriberFLAC::saveSubscribedData(std::vector<unsigned char>vec)
    {
        if(!param_b_enable_save_subscribed) return;

        std::ostringstream filename_str;
        filename_str << param_s_subscribed_filename_prefix << ".flac";

        std::ofstream ofs(filename_str.str(), std::ios::out | std::ios::binary | std::ios::app);
        if(ofs){
            ofs.write((char *)&vec[0], vec.size());
            ofs.close();
        }
    }

    void
    SubscriberFLAC::saveDecodedData(std::vector<float>pcm_vec, bool isLast)
    {
        return;
    }

    inline bool starts_with(const std::string &s, const std::string &prefix){
        if(s.size() < prefix.size()){return false;}
        return std::equal(prefix.begin(), prefix.end(), s.begin());
    }

    inline bool ends_with(const std::string &s, const std::string &suffix){
        if(s.size() < suffix.size()){return false;}
        return std::equal(s.begin() + s.size() - suffix.size(), s.end(), suffix.begin());
    }

    bool
    SubscriberFLAC::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        data outputMatrixData = data::create<Matrix<float>>();
        Matrix<float> &outputMatrix = outputMatrixData.cast<Matrix<float>>();

        while(true){
            // If the session is over (if STOP_STREAM is received), the reception process is skipped.
            if(!recv_completed){
                if(!mqttobj->is_connected()){
                    throw RuntimeError("MQTT connection failed.");
                }
                // The check cycle to start reception is 100ms, and it will time out
                // if there is no reception within the period specified by MQTT_TIMEOUT parameter
                // from the start of the process.
                if(!recv_size){
                    if(param_i_timeout < time(NULL) - started){
                        throw RuntimeError("MQTT no data available.");
                    }

                    usleep(100 * 1000);
                    state_publisher->send_message("", "READY", 3, 2, 0);
                }

                std::vector<unsigned char>tmpvec;
                do {
                    std::string received_data;
                    received_data = mqttobj->receive_message();
                    if(isDebug){
                        std::cerr << "receive size:" << std::dec << received_data.size() << std::endl;
                    }
                    if(received_data.size() == 0){continue;}
                    // If STOP_STREAM has been received, the remaining FLAC stream is passed to
                    // the FLAC decoder along with the end notification of the data stream.
                    if(ends_with(received_data, "STOP_STREAM")){
                        std::cerr << "STOP_STREAM message receved:: received data size:" << std::dec << recv_size << std::endl;

                        if(received_data.size() >= 11){
                            received_data.resize(received_data.size() - 11);
                        }
                        recv_size += received_data.size();
                        tmpvec.assign(received_data.begin(), received_data.end());

                        decoder_p->requst_decode(tmpvec, tmpvec.size(), true);
                        saveSubscribedData(tmpvec);
                        tmpvec.clear();

                        recv_completed = true;
                        // decoder_p->stop(); // I decided to stop the decoder instance in destructor.
                        last_recieved = 0; // 0 is substituted to simplify the judgment of 'if' syntax.

                        // No data has been sent but a STOP_STREAM has been received.
                        if(!recv_size){
                            throw RuntimeError("MQTT no data available.");
                        }
                        break;
                    }

                    recv_size += received_data.size();
                    tmpvec.assign(received_data.begin(), received_data.end());

                    decoder_p->requst_decode(tmpvec, tmpvec.size(), false);
                    saveSubscribedData(tmpvec);
                    tmpvec.clear();

                    last_recieved = time(NULL);
                } while(mqttobj->has_data());

                // If the MQTT session has not ended, TIMEOUT will be confirmed.
                // It is judged as TIMEOUT if there is no data has been received for more than
                // the period specified by the MQTT_TIMEOUT parameter since the last data received.
                if(last_recieved && param_i_timeout < time(NULL) - last_recieved){
                    std::cerr << "TIMEOUT:: reveived data size:" << std::dec << recv_size << std::endl;
                    recv_completed = true;
                    // decoder_p->stop(); // I decided to stop the decoder instance in destructor.
                    last_recieved = 0; // 0 is substituted to simplify the judgment of 'if' syntax.

                    if(!recv_size){
                        throw RuntimeError("MQTT no data available.");
                    }
                }
            }

            if(!total_channels){
                total_channels = decoder_p->get_channels();
                // std::cout << "[DEBUG] total_channels: " << total_channels << std::endl;
                if(!total_channels){
                    if(recv_completed){
                        throw RuntimeError("MQTT no data available.");
                    }
                    usleep(1000);
                    continue;
                }
            }

            int read_size = total_channels * param_i_length;
            int del_size = total_channels * param_i_advance;
            bool isLast = false;

            std::vector<float>pcm_vec;
            do {
                pcm_vec = decoder_p->get_decoded_pcm(read_size, del_size);

                if(pcm_vec.size() == 0){
                    if(recv_completed){
                        // This code is unnecessary because confirmation of the incomplete FLAC frame
                        // is performed on the HARK_FLAC side. It should be ...
                        // int retry = 100;
                        // do{
                        //  pcm_vec = decoder_p->get_decoded_pcm(read_size, del_size);
                        //  if(pcm_vec.size() != 0){
                        //    pcm_vec.resize(read_size, 0.0f);
                        //    frame_buffer.push_back(pcm_vec);
                        //  }
                        //  if(retry-- <= 0){ break; }
                        //  usleep(1000);
                        // } while(true);
                        isLast = true;
                        pcm_vec = decoder_p->get_decoded_pcm(read_size, del_size, isLast);
                        if(pcm_vec.size() != 0 && pcm_vec.size() < read_size){
                            pcm_vec.resize(read_size, 0.0f);
                            frame_buffer.push_back(pcm_vec);
                        }
                    }
                    break;
                }

                frame_buffer.push_back(pcm_vec);
                usleep(1000);
            } while(true);


            if(frame_buffer.size() != 0){
                // std::cout << "[DEBUG] frame_buffer.size(): " << frame_buffer.size() << std::endl;
                pcm_vec = frame_buffer.front();

                // output decoded pcm to OUTPUT
                // RCPtr<Matrix<float> > output_matrix(new Matrix<float>(total_channels, param_i_length));
                // (*(outputs[outputID].buffer))[count] = output_matrix;
                // Matrix<float>& output = *output_matrix;
                outputMatrix = Matrix<float>(total_channels, param_i_length);

                decode_size += isLast? del_size: pcm_vec.size();

                int pos = 0;
                for(int l = 0; l < param_i_length; l++){
                    for(int c = 0; c < total_channels; c++){
                        outputMatrix(c, l) = pcm_vec[pos++];
                    }
                }

                saveDecodedData(pcm_vec, isLast);

                frame_buffer.pop_front();

                break;
            }
            else if(recv_completed){
                // output decoded pcm to OUTPUT
                // RCPtr<Matrix<float> > output_matrix(new Matrix<float>(total_channels, param_i_length));
                // (*(outputs[outputID].buffer))[count] = output_matrix;
                // Matrix<float>& output = *output_matrix;
                outputMatrix = Matrix<float>(total_channels, param_i_length);

                for(int l = 0; l < param_i_length; l++){
                    for(int c = 0; c < total_channels; c++){
                        outputMatrix(c, l) = 0.0f;
                    }
                }
                exit_flag = true;

                break;
            }

            usleep(1000); // Process is too fast, cannot debugging.
        }
        // std::cout << "[DEBUG] count: " << count << std::endl;

        output["CONDITION"].push(data(!exit_flag));
        // Buffer &output_conditionBuffer = *(outputs[conditionID].buffer);
        // output_conditionBuffer[count] = (exit_flag ? FalseObject : TrueObject);
        return true;
    }
}
}

#endif /* #if HARKLIB_MOSQUITTO_AVAILABLE */

/* end of file */
