#include <Orders/MoveOrder.hpp>
#include <SimCore/Identifier.hpp>
#include "DirectCommunicationServer.hpp"
#include "GeographicLib/Geodesic.hpp"
#include "Orders/TracklistRequest.hpp"
#include "SimControl/Tracklist.hpp"
#include "SimCore/Messages/Control.hpp"
#include "SimCore/Messages/SimTrack.hpp"
#include "SimCore/Messages/TracklistUpdate.hpp"
#include "SimCore/SimCore.hpp"
#include "SimCore/UtilFunctions.hpp"
#include "WHISPER/InternalUDPListener.hpp"
#include "WHISPER/Messages/Message.hpp"
#include "WHISPER/Messages/stringData.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 <SimControl/TrackItem.hpp>



#include <cstddef>
#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <iterator>
#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);
    
    if(online)
    {
      PodController_ = std::make_unique<kubecontrol::PodController>("docs/config");
    }

   
    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()
  {
    if(PodController_ != nullptr)
    {
      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) 
    {
      auto item = std::make_shared<TrackItem>(*track);
      
      // LOG_S(INFO)<<"Track received";
      Tracklist_->addTrack(item);
      LOG_S(INFO)<<Tracklist_->getTracklistSize();
    }


  }

  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)
    {
        try {
          nlohmann::json j = nlohmann::json::parse(msg->str);

          if (j["Data"] == "COP") 
          {
            if (j["Type"] == "Request") {
              nlohmann::json response;
              response["Data"] = "COP";
              Tracklist_->getJsonTRackList(response);
         
              websocket.send(response.dump());
            }else if(j["Type"] == "EntityTrackList")
            {
                            
              if(j.contains("ID"))
              {
                nlohmann::json response;
                 this->getTrackListForEntity(j["ID"].get<std::string>(),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")
            {
              LOG_S(INFO)<< j;
              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";
                  LOG_S(INFO)<<response;

              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_);

    nlohmann::json vars;
    vars["ENTITY_ID"] = uuid;
    vars["ENTITY_NAME"] = "FGS Hamburg";
    vars["ENTITY_SIDE"] = "Neutral";
    vars["POSITION"]["LAT"] = "55";
    vars["POSITION"]["LON"] = "8";
    vars["POSITION"]["Height"] = "0";
    vars["COURSE"] = "0";
    vars["SPEED"] = "0";
    vars["GROUNDTRUTH_PORT"] = std::to_string(this->GroundTruthPort_);
    vars["GROUNDTRUTH_ADDR"] = this->GroundTruthAddr_;
    vars["COMMAND_PORT"] = CommandPort_;
    vars["ENTITY_RCS"] = std::to_string(850);

    vars["ENTITY_SENSORS"].push_back("radar:latest");
    ShipPod1.setEnvironmentVar("CONFIG", vars.dump());


    // 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_+"\"");

    if(PodController_ != nullptr)
    {
      PodController_->startPod(ShipPod1);
    }

  }


  void SimControl::startNewShip(std::string Name, std::string lat, std::string lon, std::string height , std::string course, std::string speed,bool WaitTillRunning )
  {
    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["ENTITY_SIDE"] = "Neutral";
    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_;
    vars["ENTITY_RCS"] = std::to_string(850);

    vars["ENTITY_SENSORS"].push_back("radar:latest");

    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());

    if(PodController_ != nullptr)
    {
      PodController_->startPod(ShipPod1,WaitTillRunning);
    }


  }

   std::string SimControl::sendRecvMessageToClient(std::string ip, std::string port,WHISPER::Message msg, bool waitForResponse)
   {


    DirectCommunication::DirectCommunicationClient client(std::stoi(port),ip,this->ID_.getUUID());
    LOG_S(INFO)<<"send message";
    client.sendMessage("hello");
    client.sendMessage(msg);

    if(waitForResponse == true)
    {
      std::string message = client.getLatestMessage();
      LOG_S(INFO)<<"message received " << message;
      return message;
    }
    client.disconnect();
    return "";

   }


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

     
      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>()));
        LOG_S(INFO)<<"Course as double"<< std::stod(request["Course"].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);

      }

      if(PodController_ == nullptr)
      {
        return;
      }
      auto info = PodController_->getInfoForPod(request["ID"].get<std::string>());
      
      
        LOG_S(INFO)<<"PODS Info: " <<info.Component;
        DirectCommunication::DirectCommunicationClient client(std::stoi(CommandPort_),info.IP,ID_.getUUID());
        client.sendMessage(moveOrder.buildMessage());
        // client.disconnect();
      

  }

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

    if(PodController_ == nullptr)
    {
      return;
    }
      auto info = PodController_->getInfoForPod(request["ID"].get<std::string>());
      
        LOG_S(INFO)<<"PODS Info: " <<info.Component;
        if(!std::empty(info.IP))
        {
          DirectCommunication::DirectCommunicationClient client(std::stoi(CommandPort_),info.IP,ID_.getUUID());
          SimCore::Control control(this->ID_,SimCore::ControlType::SHUT_DOWN,"Bye");
          client.sendMessage(control.buildMessage());
          client.disconnect();
        }
      
    // std::this_thread::sleep_for(std::chrono::milliseconds(1000));

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

    // std::this_thread::sleep_for(std::chrono::milliseconds(4000));


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

 void SimControl::getTrackListForEntity(std::string id,nlohmann::json &j)
  {
    if(PodController_ == nullptr)
    {
        return;
    }

    // LOG_S(INFO)<<"REQUEST: "<<request;
    std::string response;
      auto info = PodController_->getInfoForPod(id);
      std::unique_ptr<kubecontrol::PodChild> cmsPod = nullptr;
      bool hasCMS = false;
        std::string IP =  info.IP;

      for (auto child: info.Childs) {
        if (child.Component == "cms") 
        {
          cmsPod = std::make_unique<kubecontrol::PodChild>(child);
          hasCMS = true;
        }
      } 

      if (cmsPod == nullptr && hasCMS == true) 
      {
        return;
      }
         IP = cmsPod->IP;


        LOG_S(INFO)<<"PODS Info: " <<cmsPod->IP;
        
        DirectCommunication::DirectCommunicationClient client(std::stoi(CommandPort_),IP,ID_.getUUID());
        Orders::TracklistRequest TrackListRequest(ID_,SimCore::Identifier(id),true);
        // SimCore::Control control(this->ID_,SimCore::ControlType::GET_TRACKLIST,"Tracklist");
        client.sendMessage(TrackListRequest.buildMessage());
        response = client.getLatestMessage();
        client.disconnect();
      if (response != "NULL")
      {
        try
        {
        LOG_S(INFO)<<"raw data"<< response;
        auto message = WHISPER::StringData(response);
        LOG_S(INFO)<<"message from cms: "<< message.data_;
        j["Tracks"] =  nlohmann::json::parse(message.data_);  

      //  auto update = SimCore::TracklistUpdate::unpack(WHISPER::Message(response));
      //  if(update == nullptr)
      //  {
      //   LOG_S(INFO)<<"no update received";
      //   return;
      //  }

      //     nlohmann::json Jsontracks;
      //     auto tracks = update->getTracks();
      //     LOG_S(INFO)<<"Tracklist Update Size: " <<tracks.size();
      //     for(auto item : tracks)
      //     {
      //       Jsontracks.push_back(item.getsTrackListItemAsJSON());
      //     }
          //  j["Tracks"] = Jsontracks;

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

      }
  }   


    void SimControl::startWebApp()
    {
    if(PodController_ == nullptr)
    {
      return;
    }    
    kubecontrol::KubePod Webapp("controller","webapp","webapp","webapp:latest",Namespace_);
    Webapp.setEnvironmentVar("REACT_APP_WEBAPP_WS_URL", SimCore::UtilFunctions::getOwnIP());

    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 = 5;

      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",false);
        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) {
    startWebApp();
    startShip("hamburg");
    startNewShip("Test", "55.16131", "7.86004", "0", "0", "0");
    
    }

  

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


    }
   

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




}