#include <Orders/MoveOrder.hpp>
#include <SimCore/Identifier.hpp>
#include "DirectCommunicationServer.hpp"
#include "GeographicLib/Geodesic.hpp"
#include "SimControl/Tracklist.hpp"
#include "SimCore/Messages/Control.hpp"
#include "SimCore/Messages/SimTrack.hpp"
#include "SimCore/SimCore.hpp"
#include "SimCore/UtilFunctions.hpp"
#include "WHISPER/InternalUDPListener.hpp"
#include "WHISPER/Messages/Message.hpp"
#include "crossguid/guid.hpp"
#include "ixwebsocket/IXConnectionState.h"
#include "ixwebsocket/IXWebSocket.h"
#include "kubecontrol/KubePod.hpp"
#include "kubecontrol/PodController.hpp"
#include "nlohmann/json_fwd.hpp"
#include <SimControl/SimControl.hpp>



#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <loguru.hpp>
#include <memory>
#include <string>
#include <thread>
#include <utility>



namespace SimControl {


  SimControl::SimControl(bool online,std::string CommandPort,std::string GroundTruthAddr, ushort GroundTruthPort,std::string Namespace):
  online_(online),CommandPort_(CommandPort),GroundTruthAddr_(GroundTruthAddr),GroundTruthPort_(GroundTruthPort),Namespace_(Namespace),ID_(SimCore::Identifier(xg::newGuid()))
  {
    // BroadcastListener_ = std::make_shared<WHISPER::InternalUDPListener>("239.0.0.1",10000);
    BroadcastListener_ = std::make_shared<WHISPER::InternalUDPListener>(GroundTruthAddr_,GroundTruthPort_);
  
    BroadcastListener_->registerMessageCallback(std::bind(&SimControl::HandleBroadcastMessage,this,std::placeholders::_1));
    BroadcastListener_->connect();
    BroadcastListener_->subscribe(WHISPER::MsgTopics::TRACK);
    
    PodController_ = std::make_unique<kubecontrol::PodController>("docs/config");
    ExternalTCPServer_  = std::make_shared<DirectCommunication::DirectCommunicationServer>(5000);
    ExternalTCPServer_->registerMessageCallback(std::bind(&SimControl::HandleTCPMessage,this,std::placeholders::_1));
    // TCPClient_ = std::make_unique<DirectCommunication::DirectCommunicationClient>(30200,"192.168.252.6");   
    // TCPClient_ = std::make_unique<DirectCommunication::DirectCommunicationClient>(5555,"127.0.0.1");    
 
    // TCPClient_->registerMessageCallback(std::bind(&SimControl::HandleMessage,this,std::placeholders::_1));
    // TCPClient_->sendMessage("Hello Server");



    WebsocketServer_ = std::make_shared<ix::WebSocketServer>(port,host);
    WebsocketServer_->setOnClientMessageCallback( [this](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket &websocket, const ix::WebSocketMessagePtr &msg)
    {
      HandleWebsocketMessages( connectionState, websocket,  msg);
    });


    Tracklist_ = std::make_unique<TrackList>();



    this->stopMainThread_ = false;
    MainThread_ = std::thread(&SimControl::MainFunction_,this);

    this->stopWebsocketThreadThread_ = false;
    WebsocketThreadThread_ = std::thread(&SimControl::startWebsocketServer,this);




  }

  SimControl::~SimControl()
  {
    stop();

    stopMainThread_ = true;
    while (!MainThread_.joinable()) {
      // LOG_S(INFO)<< "waiting";
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    MainThread_.join();

    WebsocketServer_->stop();
    stopWebsocketThreadThread_ = true;
    while (!WebsocketThreadThread_.joinable()) {
      // LOG_S(INFO)<< "waiting";
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    WebsocketThreadThread_.join();

    TCPClient_.reset();


  }

  void SimControl::stop()
  {

    PodController_->stopAllPods();    
  }
  
  void SimControl::HandleMessage(std::string msg)
  {
  // LOG_S(INFO)<<msg;
  auto simtrack = SimCore::SimTrack::unpack(msg);
  if (simtrack != nullptr) 
  {
    LOG_S(INFO)<< "own ship data received";
    auto OwnShipPos = std::make_shared<SimCore::Position>(simtrack->getPosition());  
    LOG_S(INFO)<< "Name: " << simtrack->Name.getValue() ;

    LOG_S(INFO)<< " pos: Lat= "<< OwnShipPos->getGeodesicPos().x() << "Lon= "<< OwnShipPos->getGeodesicPos().y();
  }
  std::this_thread::sleep_for(std::chrono::milliseconds(500));


  }


  void SimControl::HandleTCPMessage(std::string msg)
  {

    LOG_S(INFO)<<msg;
  }

  void SimControl::HandleBroadcastMessage(std::string msg)
  {
    // LOG_S(INFO)<<msg;

    auto track = SimCore::SimTrack::unpack(msg);
    if (track != nullptr) 
    {
      // LOG_S(INFO)<<"Track received";
      Tracklist_->addTrack(std::move(track));
    }


  }

  void SimControl::startWebsocketServer()
  {
    auto res = WebsocketServer_->listen();
    if (!res.first)
    {
        // Error handling
        return ;
    }
    WebsocketServer_->start();

    // Block until server.stop() is called.
    WebsocketServer_->wait();

    while (!stopWebsocketThreadThread_) {
          std::this_thread::sleep_for(std::chrono::milliseconds(100));

    }

  }
  void SimControl::HandleWebsocketMessages(std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket &websocket, const ix::WebSocketMessagePtr &msg)
  {
    // LOG_S(INFO) << "Remote ip: " << connectionState->getRemoteIp();
    
    if (msg->type == ix::WebSocketMessageType::Open)
    {
         LOG_S(INFO)<< "New connection" ;

        // A connection state object is available, and has a default id
        // You can subclass ConnectionState and pass an alternate factory
        // to override it. It is useful if you want to store custom
        // attributes per connection (authenticated bool flag, attributes, etc...)
        LOG_S(INFO) << "id: " << connectionState->getId();

        // The uri the client did connect to.
        LOG_S(INFO) << "Uri: " << msg->openInfo.uri ;

        // LOG_S(INFO) << "Headers:" ;
        // for (auto it : msg->openInfo.headers)
        // {
        //     LOG_S(INFO)<< "\t" << it.first << ": " << it.second;
        // }
    }
    else if (msg->type == ix::WebSocketMessageType::Message)
    {
        // For an echo server, we just send back to the client whatever was received by the server
        // All connected clients are available in an std::set. See the broadcast cpp example.
        // Second parameter tells whether we are sending the message in binary or text mode.
        // Here we send it in the same mode as it was received.
        // LOG_S(INFO)<< "Received: " << msg->str << std::endl;
        try {
          nlohmann::json j = nlohmann::json::parse(msg->str);
          // LOG_S(INFO)<< "Received: " << j["Data"].get<std::string>() << std::endl;

          if (j["Data"] == "COP") 
          {
            if (j["Type"] == "Request") {
              nlohmann::json response;
              response["Data"] = "COP";
              Tracklist_->getJsonTRackList(response);
              // response.merge_patch(Tracklist_->getJsonTRackList());
              // LOG_S(INFO)<<response;
              websocket.send(response.dump());
            }
          }else if (j["Data"] == "Entity") 
          {
            
            if(j["Type"] == "Update")
            {
              updateShip(j);
            }else if(j["Type"] == "New")
            {

              startNewShip( j["Name"],  j["Position"][0],  j["Position"][1], j["Height"] ,  j["Course"],  j["Speed"]);

            }else if(j["Type"] == "Delete")
            {
              deleteShip(j);
            }
            
          
              
          }else if (j["Data"] == "Scenario") 
          {
            if(j["Type"] == "Save")
            {

            }else if(j["Type"] == "Load")
            {

            }else if(j["Type"] == "Delete")
            {

            }else if(j["Type"] == "AutoRaster")
            {
              this->generateSampleScneario();
            }   


          }else if (j["Data"] == "TEST") 
          {
              nlohmann::json response;
              response["Message"] = "Hello world";
              websocket.send(response.dump());

          }



        
        } catch (const std::exception e) {
          LOG_S(ERROR)<< e.what();
        }


        // websocket.send(msg->str, msg->binary);
    }

  }



  



  void SimControl::startShip(std::string name)
  {
    std::string uuid = name;
    kubecontrol::KubePod ShipPod1("controller",uuid,"ship","ship:latest",Namespace_);
    ShipPod1.setEnvironmentVar("ENTITY_ID", uuid);
    ShipPod1.setEnvironmentVar("ENTITY_NAME", "hamburg");
    ShipPod1.setEnvironmentVar("POS_LAT", "\"55\"");
    ShipPod1.setEnvironmentVar("POS_LONG", "\"8\"");
    ShipPod1.setEnvironmentVar("POS_HEIGHT", "\"0\"");
    ShipPod1.setEnvironmentVar("GROUNDTRUTH_PORT", "\""+std::to_string(this->GroundTruthPort_)+"\"");
    ShipPod1.setEnvironmentVar("GROUNDTRUTH_ADDR", "\""+this->GroundTruthAddr_+"\"");
    ShipPod1.setEnvironmentVar("COMMAND_PORT", "\""+CommandPort_+"\"");

    ShipPod1.createYAML();
    PodController_->startPod(ShipPod1);

  }


  void SimControl::startNewShip(std::string Name, std::string lat, std::string lon, std::string height , std::string course, std::string speed)
  {
    LOG_S(INFO)<< "start New Ship";

    std::string uuid = xg::newGuid().str();
    kubecontrol::KubePod ShipPod1("controller",uuid,"ship","ship:latest",Namespace_);

    nlohmann::json vars;
    vars["ENTITY_ID"] = uuid;
    vars["ENTITY_NAME"] = Name;
    vars["POSITION"]["LAT"] = lat;
    vars["POSITION"]["LON"] = lon;
    vars["POSITION"]["Height"] = height;
    vars["COURSE"] = course;
    vars["SPEED"] = speed;
    vars["GROUNDTRUTH_PORT"] = std::to_string(this->GroundTruthPort_);
    vars["GROUNDTRUTH_ADDR"] = this->GroundTruthAddr_;
    vars["COMMAND_PORT"] = CommandPort_;

    ShipPod1.setEnvironmentVar("ENTITY_ID", uuid);
    ShipPod1.setEnvironmentVar("ENTITY_NAME", Name);
    ShipPod1.setEnvironmentVar("POS_LAT", "\""+ lat + "\"");
    ShipPod1.setEnvironmentVar("POS_LONG", "\""+ lon + "\"");
    ShipPod1.setEnvironmentVar("POS_HEIGHT", "\""+ height + "\"");
    ShipPod1.setEnvironmentVar("GROUNDTRUTH_PORT", "\""+std::to_string(this->GroundTruthPort_)+"\"");
    ShipPod1.setEnvironmentVar("GROUNDTRUTH_ADDR", "\""+this->GroundTruthAddr_+"\"");
    ShipPod1.setEnvironmentVar("COMMAND_PORT", "\""+CommandPort_+"\"");
    ShipPod1.setEnvironmentVar("CONFIG", vars.dump());

    ShipPod1.createYAML();
    PodController_->startPod(ShipPod1);


  }


  void SimControl::updateShip(nlohmann::json request)
  {
      LOG_S(INFO)<< "Update Ship: "<< request["ID"];

     // if (j["Type"] == ) {
            // statements
            // }
      LOG_S(INFO)<< "Entity values:" << request;
      auto track = Tracklist_->getTrackBySringID(request["ID"].get<std::string>());
      Orders::MoveOrder moveOrder(track->getIdentifier());
      if(track != nullptr)
      {
        LOG_S(INFO)<<request["Speed"].get<std::string>();
        LOG_S(INFO)<<request["Course"].get<std::string>();

        moveOrder.Speed.setValue(std::stod(request["Speed"].get<std::string>()));
        moveOrder.Course.setValue(std::stod(request["Course"].get<std::string>()));
      }
      
      if(request["SetPosition"].get<bool>() == true) 
      {
      LOG_S(INFO)<<"setting new position";
      SimCore::Position pos;
      LOG_S(INFO)<<"LAT: "<<request["Position"][0].get<std::string>();
      LOG_S(INFO)<<"LON: "<<request["Position"][1].get<std::string>();

      pos.setGeodesicPos(std::stod(request["Position"][0].get<std::string>()),std::stod(request["Position"][1].get<std::string>()), std::stod(request["Height"].get<std::string>()));
      moveOrder.setPosition(pos);

      }

      auto info = PodController_->getPodsInfo(request["ID"].get<std::string>());
      if(info != nullptr)
      {
        LOG_S(INFO)<<"PODS Info: " <<info->ToJson();
        DirectCommunication::DirectCommunicationClient client(std::stoi(CommandPort_),info->Ip);
        client.sendMessage(moveOrder.buildMessage().serialize());
        // client.disconnect();
      }

  }

  void SimControl::deleteShip(nlohmann::json request)
  {
    LOG_S(INFO)<<"REQUEST: "<<request;
    PodController_->checkPodsHierarchy();

      auto info = PodController_->getPodsInfo(request["ID"].get<std::string>());
      if(info != nullptr)
      {
        LOG_S(INFO)<<"PODS Info: " <<info->ToJson();
        DirectCommunication::DirectCommunicationClient client(std::stoi(CommandPort_),info->Ip);
        SimCore::Control control(this->ID_,SimCore::ControlType::SHUT_DOWN,"Bye");
        client.sendMessage(control.buildMessage().serialize());
        client.disconnect();
      }  

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));

    PodController_->stopPod(request["ID"]);
    Tracklist_->deleteTrack(request["ID"]);
  }

    void SimControl::startWebApp()
    {

    kubecontrol::KubePod Webapp("controller","webapp","webapp","webapp:latest",Namespace_);
    Webapp.setEnvironmentVar("REACT_APP_WEBAPP_WS_URL", SimCore::UtilFunctions::getOwnIP());

    Webapp.createYAML();

    PodController_->startPod(Webapp);

    }


    void SimControl::saveScenario()
    {

    }

    void SimControl::loadScenario(std::string ID)
    {

    }

    void SimControl::deleteScenario(std::string ID)
    {

    }

    void SimControl::generateSampleScneario()
    {
     GeographicLib::Geodesic geod(GeographicLib::Constants::WGS84_a(), GeographicLib::Constants::WGS84_f());   
      double lat = 54, lon = 1;
      int counter  = 0;
      double distance = 10000;

      int rasterSize = 10;

      for (int i = 0; i < rasterSize; i++ ) 
      {
        double lonTmp = lon;
        for (int a = 0; a < rasterSize; a++) 
        {
        
        std::string name = "test";
        name += std::to_string(counter);
        double lat2, lon2;
        geod.Direct(lat, lonTmp, 90, distance, lat2, lon2);    

        SimControl::startNewShip(name, std::to_string(lat2), std::to_string(lon2), "0", "0", "0");
        lonTmp = lon2;
        counter ++;
        }
        double lat2, lon2;

        geod.Direct(lat, lon, 0, distance, lat2, lon2);    

        lat = lat2;

      }


    }




  void SimControl::MainFunction_()
  {   
      // std::string uuid = xg::newGuid().str();
    if (online_ != false) {
    startShip("hamburg");
    startWebApp();
    
    }

    // LOG_S(INFO)<<PodController_->getInfoForPod("hamburg");

    // auto IP =  PodController_->getPodsInfo("hamburg")->Ip;
    // TCPClient_ = std::make_shared<DirectCommunication::DirectCommunicationClient>(5555,IP);
    // //   Orders::MoveOrder order(this->ID_);
    //   order.Speed.setValue(0);
    //   TCPClient_->sendMessage(order.buildMessage().serialize());


    // std::this_thread::sleep_for(std::chrono::milliseconds(10000));
    // SimCore::Control shutdown(this->ID_,SimCore::SHUT_DOWN,"");
    //   TCPClient_->sendMessage(shutdown.buildMessage().serialize());
    //   // TCPClient_->disconnect();
    //   // TCPClient_.reset();

   

    
    while (!stopMainThread_) 
    {
      std::this_thread::sleep_for(std::chrono::milliseconds(100));


    }
   

    LOG_S(INFO)<<"main func stopped";
  }





}