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

#if !defined(_HARKLIB_NODE_FILE_STREAM_IO_H_INCLUDED_)
#define _HARKLIB_NODE_FILE_STREAM_IO_H_INCLUDED_ 1

#include <iostream>
#include <fstream>
#include <iomanip>
#include <limits>

#include <complex>
#include <vector>
#include <map>

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

#include "hark-lib/matrix.h"
#include "hark-lib/tensro.h"

#include "fmt/format.h"

namespace hark
{
namespace node
{
    class FileStreamIO
    {
    public:
        FileStreamIO();
        ~FileStreamIO();

        void reader_close();
        void writer_close();

        void reader_open(const std::string &filename);
        void writer_open(const std::string &filename);
        void writer_open(const std::string &filename, const std::string &type,
                         std::vector<size_t> &shape = std::vector<size_t>(),
                         const std::string &container = "");

        void set_modes(const std::string &output_mode, const std::string &text_mode);

        void _binary_write(const char *ptr, size_t &size);

        template <class T>
        void binary_write(T *ptr, size_t &size){
            this->_binary_write(reinterpret_cast<const char *>(ptr), sizeof(T) * size);
        }

        template <class T>
        void binary_write(std::vector<T> &vec){
            this->binary_write<T>(vec.data(), vec.size());
        }

        template <class T>
        void binary_write(hark::Matrix<T> &mat){
            this->binary_write<T>(mat.data(), mat.size());
        }

        template <class T>
        void binary_write(std::Tensor<T> &ten){
            this->binary_write<T>(ten.data.data(), ten.data.size());
        }

        template <class T>
        void _set_format(){
            if(is_complex<T>::value || std::if_floating_point<T>::value){
                if(this->tmode == TextMode::FIXED){
                    this->fout << std::fixed
                               << std::setprecision(std::numeric_limits<T>::max_digits10);
                }
                else if(this->tmode == TextMode::SCIENTIFIC){
                    this->fout << std::fixed
                               << std::setprecision(std::numeric_limits<T>::max_digits10);
                }
                else{
                    std::stringstream ss;
                    ss << "FileStreamIO: Unexpected TextMode.\n"
                       << __FILE__ << ", " << __LINE__;
                    throw RuntimeError(ss.str());
                }
            }
            else if(std::numeric_limits<T>::is_integer){
                this->fout << std::dec;
            }
            else{
                std::stringstream ss;
                ss << "FileStreamIO: Unexpected input type. Only support complex type, floating point type, integer type and container type.\n"
                   << __FILE__ << ", " << __LINE__;
                throw RuntimeError(ss.str());
            }

        }

        template <class T>
        void text_write(std::vector<T> &vec){
            if(is_complex<T>::value){
                for(size_t i = 0; i < vec.size(); i++){
                    this->fout << vec[i].real() << " " << vec[i].imag() << " ";
                }
                this->fout << std::endl;
            }
            else{
                for(size_t i = 0; i < vec.size(); i++){
                    this->fout << vec[i] << " ";
                }
                this->fout << std::endl;
            }
        }

        template <class T>
        void text_write(hark::Matrix<T> &mat){
            if(is_complex<T>::value){
                for(size_t i = 0; i < mat.rows(); i++){
                    for(size_t j = 0; j < mat.cols(); j++){
                        this->fout << mat(i, j).real() << " " << mat(i, j).imag() << " ";
                    }
                }
                this->fout << std::endl;
            }
            else{
                for(size_t i = 0; i < mat.rows(); i++){
                    for(size_t j = 0; j < mat.cols(); j++){
                        this->fout << mat(i, j) << " ";
                    }
                }
                this->fout << std::endl;
            }
        }

        template <class T>
        void text_write(hark::Tensor<T> &ten){
            if(is_complex<T>::value){
                for(size_t i = 0; i < ten.data.size(); i++){
                    this->fout << ten.data[i].real() << " " << ten.data[i].imag() << " ";
                }
                this->fout << std::endl;
            }
            else{
                for(size_t i = 0; i < ten.data.size(); i++){
                    this->fout << ten.data[i] << " ";
                }
                this->fout << std::endl;
            }
        }

        template <class T>
        void write(std::vector<T> &vec){
            if(this->mode == OutputMode::RAW){
                this->binary_write(vec);
            }
            else if(this->mode == OutputMode::TEXT){
                this->text_write(vec);
            }
            else{
                std::stringstream ss;
                ss << "FileStreamIO: Unexpected TextMode.\n"
                   << __FILE__ << ", " << __LINE__;
                throw RuntimeError(ss.str());
            }
        }

        template <class T>
        void write(hark::Matrix<T> &mat){
            if(this->mode == OutputMode::RAW){
                this->binary_write(mat);
            }
            else if(this->mode == OutputMode::TEXT){
                this->text_write(mat);
            }
            else{
                std::stringstream ss;
                ss << "FileStreamIO: Unexpected TextMode.\n"
                   << __FILE__ << ", " << __LINE__;
                throw RuntimeError(ss.str());
            }
        }

        template <class T>
        void write(hark::Tensor<T> &ten){
            if(this->mode == OutputMode::RAW){
                this->binary_write(ten);
            }
            else if(this->mode == OutputMode::TEXT){
                this->text_write(ten);
            }
            else{
                std::stringstream ss;
                ss << "FileStreamIO: Unexpected TextMode.\n"
                   << __FILE__ << ", " << __LINE__;
                throw RuntimeError(ss.str());
            }
        }


    private:
        enum class OutputMode : int64_t {
            TEXT = 0,
            RAW = 1
        };

        enum class TextMode : int64_t {
            FIXED = 0,
            SCIENTIFIC = 1
        };

        OutputMode mode;
        TextMode tmode;

        std::string rfilename;
        std::string wfilename;

        std::ifstream fin;
        std::ofstream fout;

    }


    class SaveVectorFrames: public NodeDef
    {
    public:
        SaveVectorFrames();
        ~SaveVectorFrames();

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

    private:
        template <class T>
        inline int type(data &)

        bool will_format_filename;
        std::ofstream fout;


        const float coef = 3.141592653589793238462643383279502884L * 2.0f;

        int length;
        float wn_level;
        float wn_factor;
        float wn_coef;

        std::mt19937 gen;
        std::uniform_real_distribution<float> rand_uniform;
    };
}
}

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

/* end of file */
