#pragma once

#include "LibLsp/lsp/ProtocolJsonHandler.h"
#include "LibLsp/lsp/AbsolutePath.h"

#include "LibLsp/JsonRpc/Endpoint.h"
#include "LibLsp/JsonRpc/TcpServer.h"
#include "LibLsp/JsonRpc/Condition.h"

// header for requests
#include "LibLsp/lsp/textDocument/hover.h"
#include "LibLsp/lsp/general/initialize.h"
#include "LibLsp/lsp/general/shutdown.h"
#include "LibLsp/lsp/textDocument/declaration_definition.h"
#include "LibLsp/lsp/textDocument/colorPresentation.h"

//header for notifs
#include "LibLsp/lsp/general/exit.h"
#include "LibLsp/lsp/general/initialized.h"
#include "LibLsp/lsp/textDocument/did_open.h"
#include "LibLsp/lsp/textDocument/did_change.h"
#include "LibLsp/lsp/textDocument/did_save.h"
#include "LibLsp/lsp/textDocument/did_close.h"
#include "LibLsp/JsonRpc/stream.h"


#include "common.h"
#include "symbolmaps.h"

//everything else
#include <functional>
#include <cctype>
#include <unordered_map>

namespace absyntax
{
 class block;
}

namespace AsymptoteLsp
{
 class istream : public lsp::base_istream<std::istream>
 {
 public:
   istream(std::istream& ist) : lsp::base_istream<std::istream>(ist)
   {
   }

   std::string what() override
   {
     return "AsymptoteLSP_istream";
   }
 };

 class ostream : public lsp::base_ostream<std::ostream>
 {
 public:
   ostream(std::ostream& ost) : lsp::base_ostream<std::ostream>(ost)
   {
   }

   std::string what() override
   {
     return "AsymptoteLSP_ostream";
   }
 };

 template<typename TLeft, typename TRight>
 inline optional<std::pair<optional<TLeft>, optional<TRight>>> opt_left(TLeft const& opt)
 {
   return make_optional(std::make_pair(optional<TLeft>(opt), optional<TRight>()));
 }

 template<typename TLeft, typename TRight>
 inline optional<std::pair<optional<TLeft>, optional<TRight>>> opt_right(TRight const& opt)
 {
   return make_optional(std::make_pair(optional<TLeft>(), optional<TRight>(opt)));
 }

 TextDocumentHover::Either fromString(std::string const &str);
 TextDocumentHover::Either fromMarkedStr(lsMarkedString const& markedString);
 TextDocumentHover::Either fromMarkedStr(std::string const& str, std::string const& language="asymptote");
 TextDocumentHover::Either fromMarkedStr(std::vector<std::string> const& stringList,
                                         std::string const& language="asymptote");

#if defined(LINUX_SYSTEM)
 // these functions have no use for apple or windows builds
 std::string wslDos2Unix(std::string const& dosPath);
 std::string wslUnix2Dos(std::string const& unixPath);
#endif
 std::string getDocIdentifierRawPath(lsTextDocumentIdentifier const&);

 typedef std::unordered_map<std::string, std::unique_ptr<SymbolContext>> SymContextFilemap;

 class LspLog: public lsp::Log
 {
 public:
   void log(Level level, std::string&& msg) override;
   void log(Level level, const std::string& msg) override;
   void log(Level level, std::wstring&& msg) override;
   void log(Level level, const std::wstring& msg) override;
 };


 class AsymptoteLspServer
 {
 public:
   AsymptoteLspServer(shared_ptr<lsp::ProtocolJsonHandler> const& jsonHandler,
                      shared_ptr<GenericEndpoint> const& endpoint, LspLog& log);
   AsymptoteLspServer(RemoteEndPoint* remoteEndPt,
                      shared_ptr<lsp::ProtocolJsonHandler> const& jsonHandler,
                      shared_ptr<GenericEndpoint> const& endpoint, LspLog& log);
   virtual ~AsymptoteLspServer();

   // copy constructors + copy assignment op
   AsymptoteLspServer(AsymptoteLspServer& sv) = delete;
   AsymptoteLspServer& operator=(AsymptoteLspServer const& sv) = delete;

   // move constructors and move assignment op
   AsymptoteLspServer(AsymptoteLspServer&& sv) = delete;
   AsymptoteLspServer& operator=(AsymptoteLspServer&& sv) = delete;

   virtual void start();
   void startIO(std::istream& in=cin, std::ostream& out=cout);

 protected:
   td_hover::response handleHoverRequest(td_hover::request const&);
   virtual td_initialize::response handleInitailizeRequest(td_initialize::request const&);
   virtual td_shutdown::response handleShutdownRequest(td_shutdown::request const&);
   td_definition::response handleDefnRequest(td_definition::request const&);
   td_documentColor::response handleDocColorRequest(td_documentColor::request const&);
   td_colorPresentation::response handleColorPresRequest(td_colorPresentation::request const&);


   virtual void onInitialized(Notify_InitializedNotification::notify& notify);
   virtual void onExit(Notify_Exit::notify& notify);
   void onChange(Notify_TextDocumentDidChange::notify& notify);
   void onOpen(Notify_TextDocumentDidOpen::notify& notify);
   void onSave(Notify_TextDocumentDidSave::notify& notify);
   void onClose(Notify_TextDocumentDidClose::notify& notify);

   void generateMissingTrees(std::string const& inputFile);

   void initializeRequestFn();
   void initializeNotifyFn();

   void reloadFile(std::string const&);
   void updateFileContentsTable(std::string const& filename);
   void updateFileContentsTable(std::string const& filename, std::istream& in);

   // logging functions
   void log(lsp::Log::Level const& level, std::string const& message);
   void logInfo(std::string const& message);
   void logWarning(std::string const& message);
   void logError(std::string const& message);

   SymbolContext* reloadFileRaw(std::string const&, bool const& fillTree=true);
   SymbolContext* fromRawPath(lsTextDocumentIdentifier const& identifier);
   SymbolContext* reloadFileRaw(absyntax::block* blk, std::string const& rawPath, bool const& fillTree=true);
   virtual void clearVariables();
   Condition<bool> serverClosed;

 private:
   unique_ptr<RemoteEndPoint> internalREP;
 protected:
   // [owned, ptr]
   RemoteEndPoint* const remoteEndPoint;

 private:
   shared_ptr<lsp::ProtocolJsonHandler> pjh;
   shared_ptr<GenericEndpoint> ep;
   SymbolContext* plainCtx=NULL;
   LspLog& _log;

   unique_ptr<SymContextFilemap> symmapContextsPtr;
   unique_ptr<unordered_map<std::string, std::vector<std::string>>> fileContentsPtr;
   std::string plainFile;
 };

 class TCPAsymptoteLSPServer : public lsp::TcpServer, public AsymptoteLspServer
 {
 public:
   TCPAsymptoteLSPServer(
           std::string const& addr, std::string const& port,
           shared_ptr<lsp::ProtocolJsonHandler> const& jsonHandler,
           shared_ptr<GenericEndpoint> const& endpoint, LspLog& log);
   ~TCPAsymptoteLSPServer() override;

 protected:
   void start() override;
   Condition<bool> serverInitialized;
 };
}