#include <dht/context.hpp>

#include <util/bencode.hpp>
#include <dht/messages/findintro.hpp>
#include <dht/messages/findrouter.hpp>
#include <dht/messages/gotintro.hpp>
#include <dht/messages/gotrouter.hpp>
#include <dht/messages/pubintro.hpp>

namespace llarp
{
  namespace dht
  {
    struct MessageDecoder
    {
      const Key_t &From;
      IMessage::Ptr_t msg;
      bool firstKey = true;
      bool relayed  = false;

      MessageDecoder(const Key_t &from, bool wasRelayed)
          : From(from), relayed(wasRelayed)
      {
      }

      bool
      operator()(llarp_buffer_t *buffer, llarp_buffer_t *key)
      {
        llarp_buffer_t strbuf;
        // check for empty dict
        if(!key)
          return !firstKey;

        // first key
        if(firstKey)
        {
          if(!(*key == "A"))
            return false;
          if(!bencode_read_string(buffer, &strbuf))
            return false;
          // bad msg size?
          if(strbuf.sz != 1)
            return false;
          llarp::LogInfo("Handle DHT message ", *strbuf.base,
                         " relayed=", relayed);
          switch(*strbuf.base)
          {
            case 'F':
              msg.reset(new FindIntroMessage(From, relayed));
              break;
            case 'R':
              if(relayed)
                msg.reset(new RelayedFindRouterMessage(From));
              else
                msg.reset(new FindRouterMessage(From));
              break;
            case 'S':
              msg.reset(new GotRouterMessage(From, relayed));
              break;
            case 'I':
              msg.reset(new PublishIntroMessage());
              break;
            case 'G':
              if(relayed)
              {
                msg.reset(new RelayedGotIntroMessage());
                break;
              }
              else
              {
                msg.reset(new GotIntroMessage(From));
                break;
              }
            default:
              llarp::LogWarn("unknown dht message type: ", (char)*strbuf.base);
              // bad msg type
              return false;
          }
          firstKey = false;
          return msg != nullptr;
        }
        else
          return msg->DecodeKey(*key, buffer);
      }
    };

    IMessage::Ptr_t
    DecodeMesssage(const Key_t &from, llarp_buffer_t *buf, bool relayed)
    {
      MessageDecoder dec(from, relayed);
      if(!bencode_read_dict(dec, buf))
        return nullptr;

      return std::move(dec.msg);
    }

    struct ListDecoder
    {
      ListDecoder(bool hasRelayed, const Key_t &from,
                  std::vector< IMessage::Ptr_t > &list)
          : relayed(hasRelayed), From(from), l(list)
      {
      }

      bool relayed;
      const Key_t &From;
      std::vector< IMessage::Ptr_t > &l;

      bool
      operator()(llarp_buffer_t *buffer, bool has)
      {
        if(!has)
          return true;
        auto msg = DecodeMesssage(From, buffer, relayed);
        if(msg)
        {
          l.emplace_back(std::move(msg));
          return true;
        }
        else
          return false;
      }
    };

    bool
    DecodeMesssageList(Key_t from, llarp_buffer_t *buf,
                       std::vector< IMessage::Ptr_t > &list, bool relayed)
    {
      ListDecoder dec(relayed, from, list);
      return bencode_read_list(dec, buf);
    }
  }  // namespace dht
}  // namespace llarp
