#ifndef LLARP_PATH_HPP
#define LLARP_PATH_HPP
#include <llarp/path.h>
#include <llarp/router.h>
#include <llarp/time.h>
#include <llarp/aligned.hpp>
#include <llarp/crypto.hpp>
#include <llarp/dht.hpp>
#include <llarp/endpoint.hpp>
#include <llarp/messages/relay.hpp>
#include <llarp/messages/relay_commit.hpp>
#include <llarp/path_types.hpp>
#include <llarp/pathset.hpp>
#include <llarp/router_id.hpp>
#include <llarp/routing/handler.hpp>
#include <llarp/routing/message.hpp>
#include <llarp/service/Intro.hpp>

#include <functional>
#include <list>
#include <map>
#include <mutex>
#include <unordered_map>
#include <vector>

namespace llarp
{
  namespace path
  {
    struct TransitHopInfo
    {
      TransitHopInfo() = default;
      TransitHopInfo(const TransitHopInfo& other);
      TransitHopInfo(const RouterID& down, const LR_CommitRecord& record);

      PathID_t txID, rxID;
      RouterID upstream;
      RouterID downstream;

      friend std::ostream&
      operator<<(std::ostream& out, const TransitHopInfo& info)
      {
        out << "<tx=" << info.txID << " rx=" << info.rxID;
        out << " upstream=" << info.upstream
            << " downstream=" << info.downstream;
        return out << ">";
      }

      bool
      operator==(const TransitHopInfo& other) const
      {
        return txID == other.txID && rxID == other.rxID
            && upstream == other.upstream && downstream == other.downstream;
      }

      bool
      operator!=(const TransitHopInfo& other) const
      {
        return !(*this == other);
      }

      bool
      operator<(const TransitHopInfo& other) const
      {
        return txID < other.txID || rxID < other.rxID
            || upstream < other.upstream || downstream < other.downstream;
      }

      struct Hash
      {
        std::size_t
        operator()(TransitHopInfo const& a) const
        {
          std::size_t idx0, idx1, idx2, idx3;
          memcpy(&idx0, a.upstream, sizeof(std::size_t));
          memcpy(&idx1, a.downstream, sizeof(std::size_t));
          memcpy(&idx2, a.txID, sizeof(std::size_t));
          memcpy(&idx3, a.rxID, sizeof(std::size_t));
          return idx0 ^ idx1 ^ idx2;
        }
      };
    };

    struct PathIDHash
    {
      std::size_t
      operator()(const PathID_t& a) const
      {
        std::size_t idx0;
        memcpy(&idx0, a, sizeof(std::size_t));
        return idx0;
      }
    };

    struct IHopHandler
    {
      virtual ~IHopHandler(){};

      virtual bool
      Expired(llarp_time_t now) const = 0;

      /// send routing message and increment sequence number
      virtual bool
      SendRoutingMessage(llarp::routing::IMessage* msg, llarp_router* r) = 0;

      // handle data in upstream direction
      virtual bool
      HandleUpstream(llarp_buffer_t X, const TunnelNonce& Y,
                     llarp_router* r) = 0;

      // handle data in downstream direction
      virtual bool
      HandleDownstream(llarp_buffer_t X, const TunnelNonce& Y,
                       llarp_router* r) = 0;

     protected:
      uint64_t m_SequenceNum = 0;
    };

    struct TransitHop : public IHopHandler,
                        public llarp::routing::IMessageHandler
    {
      TransitHop();

      TransitHop(const TransitHop& other);

      TransitHopInfo info;
      SharedSecret pathKey;
      llarp_time_t started = 0;
      // 10 minutes default
      llarp_time_t lifetime = DEFAULT_PATH_LIFETIME;
      llarp_proto_version_t version;

      llarp::routing::InboundMessageParser m_MessageParser;

      friend std::ostream&
      operator<<(std::ostream& out, const TransitHop& h)
      {
        return out << "[TransitHop " << h.info << " started=" << h.started
                   << " lifetime=" << h.lifetime << "]";
      }

      bool
      Expired(llarp_time_t now) const;

      // send routing message when end of path
      bool
      SendRoutingMessage(llarp::routing::IMessage* msg, llarp_router* r);

      // handle routing message when end of path
      bool
      HandleRoutingMessage(const llarp::routing::IMessage* msg,
                           llarp_router* r);

      bool
      HandlePathConfirmMessage(const llarp::routing::PathConfirmMessage* msg,
                               llarp_router* r);
      bool
      HandlePathTransferMessage(const llarp::routing::PathTransferMessage* msg,
                                llarp_router* r);
      bool
      HandlePathLatencyMessage(const llarp::routing::PathLatencyMessage* msg,
                               llarp_router* r);

      bool
      HandleGotIntroMessage(const llarp::dht::GotIntroMessage* msg);

      bool
      HandleDHTMessage(const llarp::dht::IMessage* msg, llarp_router* r);

      // handle data in upstream direction
      bool
      HandleUpstream(llarp_buffer_t X, const TunnelNonce& Y, llarp_router* r);

      // handle data in downstream direction
      bool
      HandleDownstream(llarp_buffer_t X, const TunnelNonce& Y, llarp_router* r);
    };

    /// configuration for a single hop when building a path
    struct PathHopConfig
    {
      /// path id
      PathID_t txID, rxID;
      // router contact of router
      llarp_rc router;
      // temp public encryption key
      SecretKey commkey;
      /// shared secret at this hop
      SharedSecret shared;
      /// next hop's router id
      RouterID upstream;
      /// nonce for key exchange
      TunnelNonce nonce;
      // lifetime
      llarp_time_t lifetime = DEFAULT_PATH_LIFETIME;

      ~PathHopConfig();
      PathHopConfig();
    };

    /// A path we made
    struct Path : public IHopHandler, public llarp::routing::IMessageHandler
    {
      typedef std::function< void(Path*) > BuildResultHookFunc;
      typedef std::vector< PathHopConfig > HopList;
      HopList hops;

      llarp::service::Introduction intro;

      llarp_time_t buildStarted;
      PathStatus status;

      Path(llarp_path_hops* path);

      void
      SetBuildResultHook(BuildResultHookFunc func);

      bool
      Expired(llarp_time_t now) const;

      void
      Tick(llarp_time_t now, llarp_router* r);

      bool
      SendRoutingMessage(llarp::routing::IMessage* msg, llarp_router* r);

      bool
      HandlePathConfirmMessage(const llarp::routing::PathConfirmMessage* msg,
                               llarp_router* r);

      bool
      HandlePathLatencyMessage(const llarp::routing::PathLatencyMessage* msg,
                               llarp_router* r);

      bool
      HandlePathTransferMessage(const llarp::routing::PathTransferMessage* msg,
                                llarp_router* r);

      bool
      HandleGotIntroMessage(const llarp::dht::GotIntroMessage* msg);

      bool
      HandleDHTMessage(const llarp::dht::IMessage* msg, llarp_router* r);

      bool
      HandleRoutingMessage(llarp_buffer_t buf, llarp_router* r);

      // handle data in upstream direction
      bool
      HandleUpstream(llarp_buffer_t X, const TunnelNonce& Y, llarp_router* r);

      // handle data in downstream direction
      bool
      HandleDownstream(llarp_buffer_t X, const TunnelNonce& Y, llarp_router* r);

      bool
      IsReady() const;

      // Is this deprecated?
      // nope not deprecated :^DDDD
      const PathID_t&
      TXID() const;

      RouterID
      Endpoint() const;

      const PathID_t&
      RXID() const;

      RouterID
      Upstream() const;

     protected:
      llarp::routing::InboundMessageParser m_InboundMessageParser;

     private:
      BuildResultHookFunc m_BuiltHook;
      llarp_time_t m_LastLatencyTestTime = 0;
      uint64_t m_LastLatencyTestID       = 0;
    };

    enum PathBuildStatus
    {
      ePathBuildSuccess,
      ePathBuildTimeout,
      ePathBuildReject
    };

    struct PathContext
    {
      PathContext(llarp_router* router);
      ~PathContext();

      /// called from router tick function
      void
      ExpirePaths();

      /// called from router tick function
      /// builds all paths we need to build at current tick
      void
      BuildPaths();

      /// called from router tick function
      void
      TickPaths();

      ///  track a path builder with this context
      void
      AddPathBuilder(llarp_pathbuilder_context* set);

      void
      AllowTransit();
      void
      RejectTransit();

      bool
      AllowingTransit() const;

      bool
      HasTransitHop(const TransitHopInfo& info);

      bool
      HandleRelayCommit(const LR_CommitMessage* msg);

      void
      PutTransitHop(TransitHop* hop);

      IHopHandler*
      GetByUpstream(const RouterID& id, const PathID_t& path);

      IHopHandler*
      GetByDownstream(const RouterID& id, const PathID_t& path);

      PathSet*
      GetLocalPathSet(const PathID_t& id);

      bool
      ForwardLRCM(const RouterID& nextHop,
                  std::deque< EncryptedFrame >& frames);

      bool
      HopIsUs(const PubKey& k) const;

      bool
      HandleLRUM(const RelayUpstreamMessage* msg);

      bool
      HandleLRDM(const RelayDownstreamMessage* msg);

      void
      AddOwnPath(PathSet* set, Path* p);

      typedef std::multimap< PathID_t, TransitHop* > TransitHopsMap_t;

      typedef std::pair< std::mutex, TransitHopsMap_t > SyncTransitMap_t;

      // maps path id -> pathset owner of path
      typedef std::map< PathID_t, PathSet* > OwnedPathsMap_t;

      typedef std::pair< std::mutex, OwnedPathsMap_t > SyncOwnedPathsMap_t;

      llarp_threadpool*
      Worker();

      llarp_crypto*
      Crypto();

      llarp_logic*
      Logic();

      llarp_router*
      Router();

      byte_t*
      EncryptionSecretKey();

      const byte_t*
      OurRouterID() const;

     private:
      llarp_router* m_Router;
      SyncTransitMap_t m_TransitPaths;
      SyncTransitMap_t m_Paths;
      SyncOwnedPathsMap_t m_OurPaths;
      std::list< llarp_pathbuilder_context* > m_PathBuilders;
      bool m_AllowTransit;
    };
  }  // namespace path
}  // namespace llarp

#endif
