#include <messages/link_message_parser.hpp>

#include <messages/dht_immediate.hpp>
#include <messages/discard.hpp>
#include <messages/link_intro.hpp>
#include <messages/link_message.hpp>
#include <messages/relay_commit.hpp>
#include <messages/relay.hpp>
#include <router_contact.hpp>
#include <util/buffer.hpp>
#include <util/logger.hpp>
#include <util/metrics.hpp>

#include <memory>

namespace llarp
{
  struct InboundMessageParser::msg_holder_t
  {
    LinkIntroMessage i;
    RelayDownstreamMessage d;
    RelayUpstreamMessage u;
    DHTImmediateMessage m;
    LR_CommitMessage c;
    DiscardMessage x;

    msg_holder_t() = default;
  };

  InboundMessageParser::InboundMessageParser(AbstractRouter* _router)
      : router(_router), from(nullptr), msg(nullptr), holder(new msg_holder_t())
  {
  }

  InboundMessageParser::~InboundMessageParser()
  {
    delete holder;
  }

  bool
  InboundMessageParser::operator()(llarp_buffer_t* buffer, llarp_buffer_t* key)
  {
    // we are reading the first key
    if(firstkey)
    {
      llarp_buffer_t strbuf;
      // check for empty dict
      if(!key)
        return false;
      // we are expecting the first key to be 'a'
      if(!(*key == "a"))
      {
        llarp::LogWarn("message has no message type");
        return false;
      }

      if(!bencode_read_string(buffer, &strbuf))
      {
        llarp::LogWarn("could not read value of message type");
        return false;
      }
      // bad key size
      if(strbuf.sz != 1)
      {
        llarp::LogWarn("bad mesage type size: ", strbuf.sz);
        return false;
      }
      // create the message to parse based off message type
      llarp::LogDebug("inbound message ", *strbuf.cur);
      bool isLIM = false;
      switch(*strbuf.cur)
      {
        case 'i':
          msg   = &holder->i;
          isLIM = true;
          break;
        case 'd':
          msg = &holder->d;
          break;
        case 'u':
          msg = &holder->u;
          break;
        case 'm':
          msg = &holder->m;
          break;
        case 'c':
          msg = &holder->c;
          break;
        case 'x':
          msg = &holder->x;
          break;
        default:
          return false;
      }

      if(!isLIM)
      {
        const std::string host = "RX_" + RouterID(from->GetPubKey()).ToString();
        METRICS_DYNAMIC_INCREMENT(msg->Name(), host.c_str());
      }

      msg->session = from;
      firstkey     = false;
      return true;
    }
    // check for last element
    if(!key)
      return MessageDone();

    return msg->DecodeKey(*key, buffer);
  }

  bool
  InboundMessageParser::MessageDone()
  {
    bool result = false;
    if(msg)
    {
      result = msg->HandleMessage(router);
    }
    Reset();
    return result;
  }

  bool
  InboundMessageParser::ProcessFrom(ILinkSession* src,
                                    const llarp_buffer_t& buf)
  {
    if(!src)
    {
      llarp::LogWarn("no link session");
      return false;
    }

    from     = src;
    firstkey = true;
    ManagedBuffer copy(buf);
    return bencode_read_dict(*this, &copy.underlying);
  }

  void
  InboundMessageParser::Reset()
  {
    if(msg)
      msg->Clear();
    msg = nullptr;
  }
}  // namespace llarp
