#include "hark-lib/node.h"
#include "hark-lib/network.h"
#include "hark-lib/datastream.h"
#include "hark-lib/input.h"

#include "hark-lib/node/Constant.h"
#include "hark-lib/node/InputStream.h"
#include "hark-lib/node/LocalizeMUSIC.h"
#include "hark-lib/node/SourceTracker.h"
#include "hark-lib/node/DisplayLocalization.h"
#include "hark-lib/node/AudioStreamFromWave.h"
#include "hark-lib/node/MultiFFT.h"

#include <stdlib.h>
#include <exception>
#include <iostream>
#include <fstream>

#include <Eigen/Dense>

using namespace hark;

// class Network: hark::Network
// {
// public:
//     Network(){
//         node_const = Const();
//         node_print = Print();
//     }
//
//     object calculate(){
//         auto x = node_const();
//         auto y = node_print(x);
//     }
//
// private:
//     Const *node_const;
//     Print *node_print;
// };
//
// //--------
//
// class MyNode: hark::NaseNode
// {
// public:
//     void
//     calculate(hark::DataStream &input, hark::DataStream &output) {
//         auto value = input("INPUT").cast<int>();
//
//         std::stringstream ss;
//         ss << input.count << ":" << value;
//
//         output["OUTPUT"] = ss.str();
//     }
// };
//
// class MyNode: hark::NodeDef
// {
// public:
//     void
//     calculate(hark::DataStream &input, hark::DataStream &output) {
//
//         int sum = 0;
//         for(int i = -window / 2; i < window / 2; i++){
//             auto value = input("INPUT", i).cast<int>();
//             sum += value;
//         }
//
//         output["OUTPUT"] = sum / window;
//     }
// };

class Practice1_Loop: public hark::NetworkDef
{
public:
    void
    build(hark::Network &network, hark::DataSourceMap &input, hark::DataSinkMap &output)
    {
        auto &stream = input;

        // 陽にパラメータを指定する例
        // パラメータとデータ入力を区別していない
        auto &audio = network.create<hark::Node, hark::node::AudioStreamFromWave, dispatch::TriggeredRepeat>()
                      << hark::Input("INPUT", input["INPUT"]) // データ入力
                      << hark::Input("USE_WAIT", "true") // 静的パラメータ
                      << hark::Input("LENGTH", 512) // 静的パラメータ
                      << hark::Input("ADVANCE", 160); // 静的パラメータ

        // 上流のパラメータを参照する例
        auto &spec = network.create<hark::Node, hark::node::MultiFFT>()
                     << hark::Input("INPUT", audio["AUDIO"])
                     << hark::Input("LENGTH", 512)
                     << hark::Input("WINDOW", "CONJ")
                     << hark::Input("WINDOW_LENGTH", 512);

        // // 適宜伝達関数を計算する何かがいるとして：
        // auto noisecm = hark::Node<...>()
        //     << hark::Input("INPUT", spec);

        // 別途計算されたパラメータを参照する例
        auto &loc = network.create<hark::Node, hark::node::LocalizeMUSIC>()
                    << hark::Input("INPUT", spec["OUTPUT"])
                    // << hark::AsyncInput("NOISECM", noisecm)
                    << hark::Input("MUSIC_ALGORITHM", "SEVD");
        // << hark::Input("A_MATRIX", ...);

        auto &srcs = network.create<hark::Node, hark::node::SourceTracker>()
                     << hark::Input("INPUT", loc["OUTPUT"]);

        auto &disp = network.create<hark::Node, hark::node::DisplayLocalization>()
                     << hark::Input("INPUT", srcs["OUTPUT"]);

        output["OUTPUT"] << disp["OUTPUT"];
        output["CONDITION"] << audio["NOT_EOF"];
    }
};

class Practice1: public hark::NetworkDef
{
public:
    void
    build(hark::Network &network, hark::DataSourceMap &input, hark::DataSinkMap &output){

        auto &filename = network.create<hark::Node, hark::node::Constant, hark::dispatch::OneShot>()
                         << hark::Input("VALUE", "practice1.wav");

        // std::string n = "OUTPUT";
        // auto &x = filename.data_source_from_name(n);
        //
        auto &stream = network.create<hark::Node, hark::node::InputStream>()
                       << hark::Input("INPUT", filename["OUTPUT"]);

        // 実装はともかく、ユーザには Iterator として扱えた方が便利なのは確か
        auto &loop = network.create<hark::Network, Practice1_Loop, hark::dispatch::TriggeredRepeat>("Practice1_Loop")
                     << hark::Input("INPUT", stream["OUTPUT"]);

        output["OUTPUT"] << loop["OUTPUT"];
    }

};
//
// //--------
//
// class Practice1_Loop: hark::IteratorNetwork
// {
// private:
//     hark::Node<hark::node::AudioStreamFromWave> audioStreamFromWave;
//     hark::Node<hark::node::MultiFFT> multiFFT;
//     hark::Node<hark::node::LocalizeMUSIC> localizeMUSIC;
//     hark::Node<hark::node::SourceRracker> sourceTracker;
//     hark::Node<hark::node::DisplayLocalization> displayLocalization;
//
// public:
//     Practice1_Loop() {
//         this->add_terminal("INPUT", hark::terminal::input, hark::terminal::required);
//         this->add_terminal("OUTPUT", hark::terminal::output, hark::terminal::required);
//         this->add_terminal("CONDITION", hark::terminal::condition, hark::terminal::required);
//     }
//
//     void
//     build(hark::DataStream &input, hark::DataStream &output) {
//         this->audioStreamFromWave.create();
//         this->multiFFT.create();
//         this->localizeMUSIC.create();
//         this->sourceTracker.crrate();
//         this->displayLocalization.create();
//
//         auto stream = input["INPUT"];
//         auto audio  = this->audioStreamFromWave(stream);
//         auto spec   = this->multiFFT(audio["AUDIO"]);
//         auto loc    = this->localizeMUSIC(spec);
//         auto srcs   = this->sourceTracker(loc);
//         auto output = this->displayLocalization(srcs);
//
//         output["OUTPUT"] = output;
//         output["CONDITION"] = audio["NOT_EOF"];
//     }
// };
//
// class Practice1: hark::network
// {
// private:
//     hark::Node<hark::node::InputStream> inputStream;
//     hark::Node<hark::node::Constant>    constant;
//     hark::Node<Practice1_Loop>          loop;
//
// public:
//     Practice1() {
//         this->add_terminal("OUTPUT", hark::terminal::output, hark::terminal::required);
//     }
//
//     hark::DataStream
//     build(hark::DataStream &input) {
//         this->inputStream.create();
//         this->constant.create();
//         this->loop.create();
//
//         auto filename = this->constant();
//         auto stream   = this->inputStream(filename);
//         auto output   = this->loop(stream);
//
//         return output;
//     }
//
// };

// --------

// class C
// {
// public:
//     C() { std::cout << "ctor" << std::endl; }
//
//     C(C&){ std::cout << "ctor::copy::C&" << std::endl; }
//     C(const C&){ std::cout << "ctor::copy::const C&" << std::endl; }
//     C(C &&){ std::cout << "ctor::move::C&&" << std::endl; }
//
//     template<typename T>
//     C(T &&){ std::cout << "ctor::copy::T&&" << std::endl; }
//
// };

int
process()
{
    // C c1;
    // C c2(1);
    // C c3(c1);
    std::shared_ptr<Network>network(hark::Network::create<Practice1>("Practice1"));
    // auto app = hark::Application();
    //
    // app.run(network);

    // hark::DataBundle input, output;

    std::ofstream os("graph.dot");
    network->dump_graph(os, true);
    os.close();

    network->execute(); // input, output);

    return 0;
}
//
// struct Moge
// {
// public:
//     Moge(): values(100000, 100000)
//     {
//         std::cout << "----Moge::ctor" << std::endl;
//     }
//
//     Moge(const Moge &) = default;
//     Moge(Moge &&) = default;
//     Moge &operator = (const Moge &) = default;
//     Moge &operator = (Moge &&) = default;
//
// public:
//     // std::vector<int> values;
//     Eigen::MatrixXf values;
//
// };
//
// std::ostream &operator << (std::ostream &os, const Moge &moge)
// {
//     os << "moge.ptr=" << (void *)moge.values.data() << ", rows=" << moge.values.rows() << ", cols=" << moge.values.cols();
// }
//
// struct Hoge
// {
// public:
//     Hoge(): r() { std::cout << "Hoge::ctor: r=" << r << ", " << (void *)this << std::endl; }
//     ~Hoge() { std::cout << "Hoge::dtor: r=" << r << ", " << (void *)this << std::endl; }
//
//     Hoge(const Hoge &src): r(src.r) { std::cout << "Hoge::ctor: r=" << r << ", " << (void *)this << " < " << (void *)&src << std::endl; }
//     Hoge(Hoge &&src): r(std::move(src.r)) { std::cout << "Hoge::ctor: r=" << r << ", " << (void *)this << " << " << (void *)&src << std::endl; }
//
//     Hoge &operator = (const Hoge &src) { r = src.r; std::cout << "Hoge::operator =: r=" << r << ", " << (void *)this << " < " << (void *)&src << std::endl; return *this; }
//     Hoge &operator = (Hoge &&src) { r = std::move(src.r); std::cout << "Hoge::operator =: r=" << r << ", " << (void *)this << " << " << (void *)&src << std::endl; return *this;}
//
//     Moge r;
// };
//
// std::ostream &operator << (std::ostream &os, const Hoge &hoge){
//     os << "Hoge::dump: r=" << hoge.r << ", " << (void *)&hoge;
//     return os;
// }
//
// std::ostream &operator << (std::ostream &os, const std::map<int, std::string> &m){
//     // os << "{";
//     // for(auto it: m){
//     //     if(it != m.begin()){
//     //         os << ", ";
//     //     }
//     //     os << it.first << ": " << it.second;
//     // }
//     // os << "}";
//     return os;
// }

int
main(int argc, char *argv[])
{
    // hark::data d0 = Hoge();
    // hark::data d1 = d0;
    //
    // return 0;
    // hark::data d0, d1, d2, d3, d4;
    //
    // hark::data dx;
    // {
    //     hark:data dx0 = std::string("ujauja");
    //     d1 = hark::data(dx0);
    //     dx = dx0;
    // }
    // std::cout << dx.cast<std::string>() << std::endl;
    //
    // DataPacket p;
    // {
    //     std::cout << "1" << std::endl;
    //     hark::data d = 123; //std::string ("moge");
    //     std::cout << "2" << std::endl;
    //     int x0 = d.cast<int>();
    //     std::cout << "3" << std::endl;
    //     std::cout << x0 << std::endl;
    //     std::cout << "4" << std::endl;
    //
    //     DataPacket pz(d);
    //     std::cout << pz.as<int>() << std::endl;
    //     DataPacket p0, p1, p2;
    //     std::cout << "5" << std::endl;
    //     p0 = DataPacket(d);
    //     std::cout << p0.as<int>() << std::endl;
    //     std::cout << "6" << std::endl;
    //     p1 = DataPacket(p0);
    //     std::cout << p1.as<int>() << std::endl;
    //     std::cout << "7" << std::endl;
    //     p = p1;
    //     std::cout << "8" << std::endl;
    //     std::cout << p.as<int>() << std::endl;
    //     std::cout << "9" << std::endl;
    // }
    // std::cout << "10" << std::endl;
    // std::cout << p.as<int>() << std::endl;
    //
    // d1 = 100;
    // int x1 = d1.cast<int>();
    // std::cout << x1 << std::endl;
    //
    // std::cout << "----" << std::endl;
    //
    // d2 = Hoge();
    // auto x2 = d2.cast<Hoge>();
    // std::cout << x2 << std::endl;
    //
    // std::cout << "----" << std::endl;
    //
    // d3 = new Hoge();
    // auto x3 = d3.cast<Hoge *>();
    // std::cout << *x3 << std::endl;
    //
    // std::cout << "----" << std::endl;
    //
    // d4 = d3;
    // auto x4 = d4.cast<Hoge *>();
    // std::cout << *x4 << std::endl;
    //
    // std::cout << "----" << std::endl;
    // std::cout << p.as<int>() << std::endl;
    //
    // return 0;

    try{
        process();
    }catch(std::exception &ex){
        std::cerr << "Error in process: " << ex.what() << std::endl;
        return EXIT_FAILURE;
    }catch(...){
        std::cerr << "Error in process: " << "not std::exception type" << std::endl;
        throw;
    }
    return EXIT_SUCCESS;
}
