#include <llarp/link/server.hpp>
#include "fs.hpp"

namespace llarp
{
  ILinkLayer::~ILinkLayer()
  {
  }

  bool
  ILinkLayer::HasSessionTo(const PubKey& pk)
  {
    Lock l(m_AuthedLinksMutex);
    return m_AuthedLinks.find(pk) != m_AuthedLinks.end();
  }

  bool
  ILinkLayer::Configure(llarp_ev_loop* loop, const std::string& ifname, int af,
                        uint16_t port)
  {
    m_udp.user     = this;
    m_udp.recvfrom = &ILinkLayer::udp_recv_from;
    m_udp.tick     = &ILinkLayer::udp_tick;
    if(ifname == "*")
    {
      if(!AllInterfaces(af, m_ourAddr))
        return false;
    }
    else if(!GetIFAddr(ifname, m_ourAddr, af))
      return false;
    m_ourAddr.port(port);
    return llarp_ev_add_udp(loop, &m_udp, m_ourAddr) != -1;
  }

  void
  ILinkLayer::Pump()
  {
    auto now = llarp_time_now_ms();
    {
      Lock lock(m_AuthedLinksMutex);
      auto itr = m_AuthedLinks.begin();
      while(itr != m_AuthedLinks.end())
      {
        if(!itr->second->TimedOut(now))
        {
          itr->second->Pump();
          ++itr;
        }
        else
          itr = m_AuthedLinks.erase(itr);
      }
    }
    {
      Lock lock(m_PendingMutex);

      auto itr = m_Pending.begin();
      while(itr != m_Pending.end())
      {
        if(!(*itr)->TimedOut(now))
        {
          (*itr)->Pump();
          ++itr;
        }
        else
          itr = m_Pending.erase(itr);
      }
    }
  }

  void
  ILinkLayer::MapAddr(const PubKey& pk, ILinkSession* s)
  {
    Lock l_authed(m_AuthedLinksMutex);
    Lock l_pending(m_PendingMutex);
    auto itr = m_Pending.begin();
    while(itr != m_Pending.end())
    {
      if(itr->get() == s)
      {
        m_AuthedLinks.insert(std::make_pair(pk, std::move(*itr)));
        itr = m_Pending.erase(itr);
        return;
      }
      else
        ++itr;
    }
  }

  bool
  ILinkLayer::PickAddress(const RouterContact& rc,
                          llarp::AddressInfo& picked) const
  {
    std::string OurDialect = Name();
    for(const auto& addr : rc.addrs)
    {
      if(addr.dialect == OurDialect)
      {
        picked = addr;
        return true;
      }
    }
    return false;
  }

  void
  ILinkLayer::TryEstablishTo(const RouterContact& rc)
  {
    llarp::AddressInfo to;
    if(!PickAddress(rc, to))
      return;
    llarp::Addr addr(to);
    auto s = NewOutboundSession(rc, to);
    s->Start();
    PutSession(s);
  }

  bool
  ILinkLayer::Start(llarp_logic* l)
  {
    m_Logic = l;
    ScheduleTick(100);
    return true;
  }

  void
  ILinkLayer::Stop()
  {
    if(m_Logic && tick_id)
      llarp_logic_remove_call(m_Logic, tick_id);
  }

  void
  ILinkLayer::CloseSessionTo(const PubKey& remote)
  {
    Lock l(m_AuthedLinksMutex);
    auto range = m_AuthedLinks.equal_range(remote);
    auto itr   = range.first;
    while(itr != range.second)
    {
      itr->second->SendClose();
      itr = m_AuthedLinks.erase(itr);
    }
  }

  void
  ILinkLayer::KeepAliveSessionTo(const PubKey& remote)
  {
    Lock l(m_AuthedLinksMutex);
    auto range = m_AuthedLinks.equal_range(remote);
    auto itr   = range.first;
    while(itr != range.second)
    {
      itr->second->SendKeepAlive();
      ++itr;
    }
  }

  bool
  ILinkLayer::SendTo(const PubKey& remote, llarp_buffer_t buf)
  {
    Lock l(m_AuthedLinksMutex);
    auto range = m_AuthedLinks.equal_range(remote);
    auto itr   = range.first;
    // TODO: random selection
    while(itr != range.second)
    {
      if(itr->second->SendMessageBuffer(buf))
        return true;
      ++itr;
    }
    return false;
  }

  bool
  ILinkLayer::GetOurAddressInfo(llarp::AddressInfo& addr) const
  {
    addr.dialect = Name();
    addr.pubkey  = TransportPubKey();
    addr.rank    = Rank();
    addr.port    = m_ourAddr.port();
    addr.ip      = *m_ourAddr.addr6();
    return true;
  }

  const byte_t*
  ILinkLayer::TransportPubKey() const
  {
    return llarp::seckey_topublic(TransportSecretKey());
  }

  const byte_t*
  ILinkLayer::TransportSecretKey() const
  {
    return m_SecretKey;
  }

  bool
  ILinkLayer::EnsureKeys(const char* f)
  {
    fs::path fpath(f);
    llarp::SecretKey keys;
    std::error_code ec;
    if(!fs::exists(fpath, ec))
    {
      if(!KeyGen(m_SecretKey))
        return false;
      // generated new keys
      if(!BEncodeWriteFile< decltype(keys), 128 >(f, m_SecretKey))
        return false;
    }
    // load keys
    if(!BDecodeReadFile(f, m_SecretKey))
    {
      llarp::LogError("Failed to load keyfile ", f);
      return false;
    }
    return true;
  }

  void
  ILinkLayer::PutSession(ILinkSession* s)
  {
    Lock lock(m_PendingMutex);
    m_Pending.emplace_back(s);
  }

  void
  ILinkLayer::OnTick(uint64_t interval, llarp_time_t now)
  {
    Tick(now);
    ScheduleTick(interval);
  }

  void
  ILinkLayer::ScheduleTick(uint64_t interval)
  {
    tick_id = llarp_logic_call_later(
        m_Logic, {interval, this, &ILinkLayer::on_timer_tick});
  }

}  // namespace llarp
