#pragma once;


#include <string>
#include <filesystem>
#include <vector>
#include <unordered_map>
#include <iostream>
#include <array>

namespace Plotta
{
    namespace fs = std::filesystem;
    class plottastream : public std::stringstream { };


    struct Plotta
    {
    private:
        struct DataItem
        {
            std::string name;
            std::string dataStr;
        };

        const fs::path scriptFile;
        const fs::path dataFile;

        std::unordered_map<std::string, DataItem> data;


    public:
        Plotta(const fs::path& scriptFile, const fs::path& dataFile)
            : scriptFile(scriptFile), dataFile(dataFile)
        {

        }

        void frame()
        {
            // send data
            std::ofstream stream;
            stream.exceptions(stream.exceptions() | std::ios::failbit);

            stream.open(dataFile);

            std::time_t t = std::time(nullptr);
            std::tm tm = *std::localtime(&t);

            stream << "# genertated at " << std::put_time(&tm, "%d.%m.%Y %T") << "\n";
            stream << "import numpy as np" << "\n\n";

            for (const auto& item : data)
            {
                stream << item.second.dataStr << "\n";
            }

            stream.close();
        }

        template<typename T>
        void add(std::string name, const std::vector<T>& value)
        {
            plottastream stream;

            stream << name << " = ";
            stream << value;

            DataItem item;
            item.name = name;
            item.dataStr = stream.str();

            data.insert_or_assign(item.name, item);
        }

        template<typename T>
        void add(std::string name, const std::initializer_list<T>& value)
        {
            plottastream stream;

            stream << name << " = ";
            stream << value;

            DataItem item;
            item.name = name;
            item.dataStr = stream.str();

            data.insert_or_assign(item.name, item);
        }

    private:

        void start()
        {
            // TODO
            // start python script
            // connect
            assert(false);
        }
    };

    template<typename Iterator>
    static plottastream& writeNumpyArray(plottastream& stream, Iterator begin, Iterator end)
    {
        stream << "np.array([";

        for (Iterator it = begin; it != end; ++it)
        {
            stream << *it;
            if (it != end - 1)
            {
                stream << ", ";
            }
        }

        stream << "])";
        return stream;
    }

    template<typename T>
    plottastream& operator<<(plottastream& stream, const std::vector<T>& list)
    {
        return writeNumpyArray(stream, list.begin(), list.end());
    }

    template<typename T>
    plottastream& operator<<(plottastream& stream, const std::initializer_list<T>& list)
    {
        return writeNumpyArray(stream, list.begin(), list.end());
    }

    template<typename T, size_t S>
    plottastream& operator<<(plottastream& stream, const std::array<T, S>& list)
    {
        return writeNumpyArray(stream, list.begin(), list.end());
    }


    template<typename T>
    static plottastream& operator<<(plottastream& stream, const T& value)
    {
        // use std::strngstream conversion methods for any T
        static_cast<std::stringstream&>(stream) << value;
        return stream;
    }

    template<>
    inline plottastream& operator<<(plottastream& stream, const float& value)
    {
        return stream << (std::isnan(value) ? "float(\"nan\")" : std::to_string(value));
    }

    template<>
    inline plottastream& operator<<(plottastream& stream, const Point2& value)
    {
        return stream << std::initializer_list<float>{ value.x, value.y };
    }
}
