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

#if !defined(_HARKLIB_NODE_SPEECH_RECOGNITION_CLIENT_H_INCLUDED_)
#define _HARKLIB_NODE_SPEECH_RECOGNITION_CLIENT_H_INCLUDED_ 1

#include <cstring>
#include <cmath>
#include <vector>
#include <iomanip>
#include <random>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

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

namespace hark
{
namespace node
{
    class SpeechRecognitionClient: public NodeDef
    {
    public:
        SpeechRecognitionClient();
        ~SpeechRecognitionClient();

        void initialize(InputDataBundle &input);
        virtual void reset();
        virtual bool operator () (size_t count, InputDataBundle &input, OutputDataBundle &output);

    private:
        class MfcnetClient {
        public:
            MfcnetClient(): fd(-1), host(""), port(0) {
            }
            MfcnetClient(std::string &_host, unsigned int _port, bool disable=false):
                    host(_host), port(_port) {
                this->fd = -1;
                this->host = _host;
                this->port = _port;
                if(disable){ return; }
                hostent *connect_host;
                if((connect_host = gethostbyname(this->host.c_str())) == NULL){
                    std::stringstream ss;
                    ss << "bad hostname: \'" << this->host << "\'\n@MfcnetClient\n";
                    throw RuntimeError(ss.str());
                }
                sockaddr_in desthost;
                ::memset(reinterpret_cast<char*>(&desthost), 0, sizeof(desthost));
                desthost.sin_family = AF_INET;
                desthost.sin_port = htons(this->port);
                ::memmove(reinterpret_cast<char*>(&desthost.sin_addr), connect_host->h_addr, connect_host->h_length);
                if((this->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
                    this->fd = -1;
                    std::stringstream ss;
                    ss << "client socket failed.\n@MfcnetClient\n";
                    throw RuntimeError(ss.str());
                }
                if(::connect(this->fd, reinterpret_cast<sockaddr*>(&desthost), sizeof(desthost)) == -1){
                    ::close(this->fd);
                    this->fd = -1;
                    std::stringstream ss;
                    ss << "connect failed.\n@MfcnetClient";
                    throw RuntimeError(ss.str());
                }
                
            }
            ~MfcnetClient(){
                this->disconnect();
            }

            void connect(std::string &_host, unsigned short int _port, bool disable=false){
                if(this->fd >= 0){
                    if(this->host != _host || this->port != _port){ this->disconnect(); }
                    else{
                        std::cout << "already connected.\n@MfcnetClient\n";
                        return;
                    }
                }
                this->fd = -1;
                this->host = _host;
                this->port = _port;
                if(disable){ return; }
                hostent *connect_host;
                if((connect_host = gethostbyname(this->host.c_str())) == NULL){
                    std::stringstream ss;
                    ss << "bad hostname: \'" << this->host << "\'\n@MfcnetClient\n";
                    throw RuntimeError(ss.str());
                }
                sockaddr_in desthost;
                ::memset(reinterpret_cast<char*>(&desthost), 0, sizeof(desthost));
                desthost.sin_family = AF_INET;
                desthost.sin_port = htons(this->port);
                ::memmove(reinterpret_cast<char*>(&desthost.sin_addr), connect_host->h_addr, connect_host->h_length);
                if((this->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
                    this->fd = -1;
                    std::stringstream ss;
                    ss << "client socket failed.\n@MfcnetClient\n";
                    throw RuntimeError(ss.str());
                }
                if(::connect(this->fd, reinterpret_cast<sockaddr*>(&desthost), sizeof(desthost)) == -1){
                    ::close(this->fd);
                    this->fd = -1;
                    std::stringstream ss;
                    ss << "connect failed.\n@MfcnetClient";
                    throw RuntimeError(ss.str());
                }
            }

            void write(hark::SourceInfo &header){
                if(this->fd >= 0){
                    ssize_t write_size;
                    //int32_t header_size = sizeof(hark::SourceInfo_s);
                    //int32_t header_size = 28;
                    int32_t header_size = hark::SourceInfo::size;
                    write_size = ::write(this->fd, &header_size, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing SourceInfo header size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }

                    //write_size = ::write(this->fd, &header, header_size);
                    //if(write_size != header_size){
                    //    std::cout << "socket write error: The result when writing SourceInfo structure is (expected=" << header_size << ", actual=" << write_size << ").\n";
                    //}

                    write_size = ::write(this->fd, &header.source_id, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing SourceInfo.source_id size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.azimuth, sizeof(float));
                    if(write_size != sizeof(float)){
                        std::cout << "socket write error: The result when writing SourceInfo.azimuth size is (expected=" << sizeof(float) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.elevation, sizeof(float));
                    if(write_size != sizeof(float)){
                        std::cout << "socket write error: The result when writing SourceInfo.elevation size is (expected=" << sizeof(float) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.tv_sec, sizeof(int64_t));
                    if(write_size != sizeof(int64_t)){
                        std::cout << "socket write error: The result when writing SourceInfo.tv_sec size is (expected=" << sizeof(int64_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.tv_usec, sizeof(int64_t));
                    if(write_size != sizeof(int64_t)){
                        std::cout << "socket write error: The result when writing SourceInfo.tv_usec size is (expected=" << sizeof(int64_t) << ", actual=" << write_size << ").\n";
                    }
                }
            }

            void write(hark::UtteranceInfo &header){
                if(this->fd >= 0){
                    ssize_t write_size;
                    int32_t header_size = hark::UtteranceInfo::size;
                    write_size = ::write(this->fd, &header_size, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo header size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }

                    //write_size = ::write(this->fd, &header, header_size);
                    //if(write_size != header_size){
                    //    std::cout << "socket write error: The result when writing UtteranceInfo structure is (expected=" << header_size << ", actual=" << write_size << ").\n";
                    //}

                    write_size = ::write(this->fd, &header.client_id, sizeof(int64_t));
                    if(write_size != sizeof(int64_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.client_id size is (expected=" << sizeof(int64_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.mic_index, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.mic_index size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.source_id, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.source_id size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.sub_id, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.sub_id size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.azimuth, sizeof(float));
                    if(write_size != sizeof(float)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.azimuth size is (expected=" << sizeof(float) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.elevation, sizeof(float));
                    if(write_size != sizeof(float)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.elevation size is (expected=" << sizeof(float) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.tv_sec, sizeof(int64_t));
                    if(write_size != sizeof(int64_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.tv_sec size is (expected=" << sizeof(int64_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, &header.tv_usec, sizeof(int64_t));
                    if(write_size != sizeof(int64_t)){
                        std::cout << "socket write error: The result when writing UtteranceInfo.tv_usec size is (expected=" << sizeof(int64_t) << ", actual=" << write_size << ").\n";
                    }
                }
            }

            void write(std::vector<float> &features){
                if(this->fd >= 0){
                    ssize_t write_size;
                    int32_t features_size = sizeof(float) * features.size();
                    write_size = ::write(this->fd, &features_size, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing features size is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    write_size = ::write(this->fd, features.data(), features_size);
                    if(write_size != features_size){
                        std::cout << "socket write error: The result when writing features data is (expected=" << features_size << ", actual=" << write_size << ").\n";
                    }
                }
            }

            void disconnect(){
                if(this->fd >= 0){
                    ssize_t write_size;
                    int32_t terminate_symbol = 0;
                    write_size = ::write(this->fd, &terminate_symbol, sizeof(int32_t));
                    if(write_size != sizeof(int32_t)){
                        std::cout << "socket write error: The result when writing terminate symbol is (expected=" << sizeof(int32_t) << ", actual=" << write_size << ").\n";
                    }
                    ::close(this->fd);
                    this->fd = -1;
                }
            }

        private:
            int fd;
            std::string host;
            unsigned short int port;
        };

        std::map<int, MfcnetClient*> socket_map;
        bool mfm_enabled;
        bool socket_enabled;
        std::string hostname;
        unsigned short int port;
        int protocol_type;
        int64_t client_id; // for extended mfcnet.
    };
}
}

#endif /* #if !defined(_HARKLIB_NODE_SPEECH_RECOGNITION_CLIENT_H_INCLUDED_) */

/* end of file */
