// Copyright 2022, DragonflyDB authors.  All rights reserved.
// See LICENSE for licensing terms.
//
#include "facade/memcache_parser.h"

#include <absl/container/flat_hash_map.h>
#include <absl/strings/ascii.h>
#include <absl/strings/numbers.h>

#include "base/stl_util.h"

namespace facade {
using namespace std;
using MP = MemcacheParser;

namespace {

MP::CmdType From(string_view token) {
  static absl::flat_hash_map<string_view, MP::CmdType> cmd_map{
      {"set", MP::SET},       {"add", MP::ADD},         {"replace", MP::REPLACE},
      {"append", MP::APPEND}, {"prepend", MP::PREPEND}, {"cas", MP::CAS},
      {"get", MP::GET},       {"gets", MP::GETS},       {"gat", MP::GAT},
      {"gats", MP::GATS},     {"stats", MP::STATS},     {"incr", MP::INCR},
      {"decr", MP::DECR},     {"delete", MP::DELETE},   {"flush_all", MP::FLUSHALL},
      {"quit", MP::QUIT},     {"version", MP::VERSION},
  };

  auto it = cmd_map.find(token);
  if (it == cmd_map.end())
    return MP::INVALID;

  return it->second;
}

MP::Result ParseStore(const std::string_view* tokens, unsigned num_tokens, MP::Command* res) {
  unsigned opt_pos = 3;
  if (res->type == MP::CAS) {
    if (num_tokens <= opt_pos)
      return MP::PARSE_ERROR;
    ++opt_pos;
  }

  uint32_t flags;
  if (!absl::SimpleAtoi(tokens[0], &flags) || !absl::SimpleAtoi(tokens[1], &res->expire_ts) ||
      !absl::SimpleAtoi(tokens[2], &res->bytes_len))
    return MP::BAD_INT;

  if (res->type == MP::CAS && !absl::SimpleAtoi(tokens[3], &res->cas_unique)) {
    return MP::BAD_INT;
  }

  res->flags = flags;
  if (num_tokens == opt_pos + 1) {
    if (tokens[opt_pos] == "noreply") {
      res->no_reply = true;
    } else {
      return MP::PARSE_ERROR;
    }
  } else if (num_tokens > opt_pos + 1) {
    return MP::PARSE_ERROR;
  }

  return MP::OK;
}

MP::Result ParseValueless(const std::string_view* tokens, unsigned num_tokens, MP::Command* res) {
  unsigned key_pos = 0;
  if (res->type == MP::GAT || res->type == MP::GATS) {
    if (!absl::SimpleAtoi(tokens[0], &res->expire_ts)) {
      return MP::BAD_INT;
    }
    ++key_pos;
  }
  res->key = tokens[key_pos++];

  if (key_pos < num_tokens && base::_in(res->type, {MP::STATS, MP::FLUSHALL}))
    return MP::PARSE_ERROR;  // we do not support additional arguments for now.

  if (res->type == MP::INCR || res->type == MP::DECR) {
    if (key_pos == num_tokens)
      return MP::PARSE_ERROR;

    if (!absl::SimpleAtoi(tokens[key_pos], &res->delta))
      return MP::BAD_DELTA;
    ++key_pos;
  }

  while (key_pos < num_tokens) {
    res->keys_ext.push_back(tokens[key_pos++]);
  }

  if (res->type >= MP::DELETE) {  // write commands
    if (!res->keys_ext.empty() && res->keys_ext.back() == "noreply") {
      res->no_reply = true;
      res->keys_ext.pop_back();
    }
  }

  return MP::OK;
}

}  // namespace

auto MP::Parse(string_view str, uint32_t* consumed, Command* cmd) -> Result {
  cmd->no_reply = false;  // re-initialize
  auto pos = str.find('\n');
  *consumed = 0;
  if (pos == string_view::npos) {
    // TODO: it's over simplified since we may process GET/GAT command that is not limited to
    // 300 characters.
    return str.size() > 300 ? PARSE_ERROR : INPUT_PENDING;
  }

  if (pos == 0) {
    return PARSE_ERROR;
  }
  *consumed = pos + 1;

  // cas <key> <flags> <exptime> <bytes> <cas unique> [noreply]\r\n
  // get <key>*\r\n
  string_view tokens[8];
  unsigned num_tokens = 0;
  uint32_t cur = 0;

  while (cur < pos && str[cur] == ' ')
    ++cur;

  uint32_t s = cur;
  for (; cur <= pos; ++cur) {
    if (absl::ascii_isspace(str[cur])) {
      if (cur != s) {
        tokens[num_tokens++] = str.substr(s, cur - s);
        if (num_tokens == ABSL_ARRAYSIZE(tokens)) {
          ++cur;
          s = cur;
          break;
        }
      }
      s = cur + 1;
    }
  }

  if (num_tokens == 0)
    return PARSE_ERROR;

  while (cur < pos - 1) {
    if (str[cur] != ' ')
      return PARSE_ERROR;
    ++cur;
  }

  cmd->type = From(tokens[0]);
  if (cmd->type == INVALID) {
    return UNKNOWN_CMD;
  }

  if (cmd->type <= CAS) {                            // Store command
    if (num_tokens < 5 || tokens[1].size() > 250) {  // key length limit
      return MP::PARSE_ERROR;
    }

    // memcpy(single_key_, tokens[0].data(), tokens[0].size());  // we copy the key
    cmd->key = string_view{tokens[1].data(), tokens[1].size()};

    return ParseStore(tokens + 2, num_tokens - 2, cmd);
  }

  if (num_tokens == 1) {
    if (base::_in(cmd->type, {MP::STATS, MP::FLUSHALL, MP::QUIT, MP::VERSION}))
      return MP::OK;
    return MP::PARSE_ERROR;
  }

  return ParseValueless(tokens + 1, num_tokens - 1, cmd);
};

}  // namespace facade
