#include <dht/context.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) : From(from)
      {
      }

      static bool
      on_key(dict_reader *r, llarp_buffer_t *key)
      {
        llarp_buffer_t strbuf;
        MessageDecoder *dec = static_cast< MessageDecoder * >(r->user);
        // check for empty dict
        if(!key)
          return !dec->firstKey;

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

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

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

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

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

      static bool
      on_item(list_reader *r, bool has)
      {
        ListDecoder *dec = static_cast< ListDecoder * >(r->user);
        if(!has)
          return true;
        auto msg = DecodeMesssage(dec->From, r->buffer, dec->relayed);
        if(msg)
        {
          dec->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(from, list);
      dec.relayed = relayed;
      list_reader r;
      r.user    = &dec;
      r.on_item = &ListDecoder::on_item;
      return bencode_read_list(buf, &r);
    }
  }  // namespace dht
}  // namespace llarp
