#include <iostream>
#include <fstream>
#include <deque>
#include "StreamParser.h"
#include <chrono>
#include <mutex>

using namespace slsp;

std::mutex DequeMutex;
int main()
{
    try
    {
        // Define stream callback
        std::deque<std::unique_ptr<RtcDataPackage>> ReadPackages;
        const StreamCallback MyCallback = [&ReadPackages](RtcDataStream* StreamBuffer)
        {
            // Copy first and process later
            while (StreamBuffer->size() > 0)
            {
                std::lock_guard<std::mutex> guard(DequeMutex);
                ReadPackages.push_back(StreamBuffer->pop());
            }
        };

        std::cout << "Init stream client ..." << std::endl;

        // Init stream client (lifetime of StreamParser object == duration of stream)
        std::unique_ptr<StreamParser> Client = createStreamParser("127.0.0.1", MyCallback);
        Client->connect();

        // Check for the StreamParser to be connected
        while (!(Client->isConnected()))
        {
            Client->rethrow();
            const uint32_t PeriodMilliSeconds = 50;
            std::this_thread::sleep_for(std::chrono::milliseconds(PeriodMilliSeconds));
        }
        std::cout << "Connection established. Start streaming ..." << std::endl;

        // Process stream data in extra thread
        std::ofstream LogFile("SimulationLog_CPP.txt");
        LogFile << "Channel 1; Channel 2; Channel 3; Channel 4 " << std::endl;
        std::atomic<bool> StopProcessing = false;
        auto ProcessStreamData =
            std::async(std::launch::async,
                       [&ReadPackages, &LogFile, &StopProcessing]()
                       {
                           while (!StopProcessing || !ReadPackages.empty())
                           {
                               if (ReadPackages.empty())
                               {
                                   std::this_thread::sleep_for(std::chrono::milliseconds(15));
                                   continue;
                               }
                               std::lock_guard<std::mutex> guard(DequeMutex);
                               size_t Size = ReadPackages.size();
                               for (size_t Index = 0; Index < Size; ++Index)
                               {
                                   const auto& Package = ReadPackages.front();
                                   for (size_t Element = 0; Element < Package->accessWaveformData(0).size(); ++Element)
                                   {
                                       for (size_t Channel = 0; Channel < Package->getChannelCount(); ++Channel)
                                       {
                                           LogFile << Package->accessWaveformData(Channel)[Element] << "; ";
                                       }
                                       LogFile << "\n";
                                   }
                                   ReadPackages.pop_front();
                               }
                           }
                       });

        auto StreamState = Client->getState();

        while (StreamState != StreamParser::State::RECEIVING)
        {
            Client->rethrow();
            const uint32_t PeriodMilliSeconds = 500;
            std::this_thread::sleep_for(std::chrono::milliseconds(PeriodMilliSeconds));
            StreamState = Client->getState();
        }

        // Stream as long as data is received
        while (Client->getState() == StreamParser::State::RECEIVING)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }

        // Disconnect
        Client->disconnect();

        // Stop processing of data when finished
        StopProcessing = true;

        // Propagate any thrown exceptions
        Client->rethrow();
        ProcessStreamData.get();

        // Delete client (== stop stream)
        std::cout << "Delete client (stop stream) ..." << std::endl;
        Client.reset();

        return 0;
    }
    catch (const StreamParserException& Exception)
    {
        std::cout << "StreamParserException: "
                  << ": " << Exception.what() << std::endl;
        return static_cast<std::underlying_type<StreamParserException::ErrorCode>::type>(Exception.getErrorCode());
    }
    catch (const std::exception& Exception)
    {
        std::cout << "General Exception: " << Exception.what() << std::endl;
        return -1;
    }
}
