#include "byte.h"
#include "case.h"
#include "str.h"

#include "command.h"
#include "client.h"

void cmd_privmsg(struct client*, struct token*, int);
void cmd_names(struct client*, struct token*, int);
void cmd_whois(struct client*, struct token*, int);
void cmd_join(struct client*, struct token*, int);
void cmd_mode(struct client*, struct token*, int);
void cmd_part(struct client*, struct token*, int);
void cmd_nick(struct client*, struct token*, int);
void cmd_quit(struct client*, struct token*, int);
void cmd_user(struct client*, struct token*, int);
void cmd_away(struct client*, struct token*, int);
void cmd_ping(struct client*, struct token*, int);
void cmd_who(struct client*, struct token*, int);

static const char* skip_neq(const char* s, const char* e, char c) {
  while(s < e && *s != c)
    s++;
  return s;
}

void command_do(struct client* c, struct token cmd, struct token* p, int n) {
  struct {
    const char* s;
    const int active;
    void (*fun)(struct client*, struct token*, int);
  } commands[] = {
    { "privmsg", 1, cmd_privmsg },
    { "names", 0, cmd_names },
    { "whois", 1, cmd_whois },
    { "join", 1, cmd_join },
    { "mode", 1, cmd_mode },
    { "part", 1, cmd_part },
    { "nick", 1, cmd_nick },
    { "quit", 1, cmd_quit },
    { "user", 1, cmd_user },
    { "away", 1, cmd_away },
    { "ping", 0, cmd_ping },
    { "who", 0, cmd_who }
  };

  int i;

  for(i = 0; i < sizeof(commands) / sizeof(*commands); i++) {
    if(str_len(commands[i].s) == cmd.len && 
        !case_diffb(cmd.s, cmd.len, commands[i].s)) {
      commands[i].fun(c, p, n);
      if(commands[i].active)
        tai_now(&c->stamp);
      return;
    }
  }

  client_reply_start(c, "421");
  client_put(c, cmd.s, cmd.len);
  client_puts(c, " :Unknown command\r\n");
}

static void command_parse_one(struct client* c, const char* start, 
    const char* end) {
  struct token cmd;
  struct token params[15];
  int nparams = 0;

  /* skip leading prefix */
  if(*start == ':') {
    start = skip_neq(start, end, ' ');
    if(*start != ' ') {
      client_reply_start(c, "421");
      client_puts(c, "<unknown> :Unknown command\r\n");
      return;
    }
    start++;
  }

  cmd.s = start;
  start = skip_neq(start, end, ' ');
  cmd.len = start - cmd.s;
  if(*start != ' ' && start != end) {
    client_reply_start(c, "421");
    client_puts(c, "<unknown> :Unknown command\r\n");
    return;
  }
  start++;

  while(end - start > 0) {
    if(*start == ':') {
      start++;
      params[nparams].s = start;
      params[nparams].len = end - start;
      nparams++;
      break;
    }

    if(nparams == 14) {
      params[nparams].s = start;
      params[nparams].len = end - start;
      nparams++;
      break;
    }

    params[nparams].s = start;
    start = skip_neq(start, end, ' ');
    params[nparams].len = start - params[nparams].s;
    nparams++;
    if(*start != ' ' && start != end) {
      client_reply_start(c, "421");
      client_puts(c, "<unknown> :Unknown command\r\n");
      return;
    }
    start++;
  }

  command_do(c, cmd, params, nparams);
}

void command_parse_all(struct client* c) {
  const char* start;
  unsigned remaining;

  start = c->in;
  remaining = c->in_len;

  for(;;) {
    unsigned len; /* total len include CRLF */
    unsigned cmdlen; /* length of command without CRLF */

    len = byte_chr(start, remaining, '\n');
    if(len == remaining)
      break;

    /* add on for LF */
    len++;

    /* be forgiving about missing the CR */
    cmdlen = len - 1;
    if(cmdlen > 0 && start[cmdlen - 1] == '\r')
      cmdlen--;

    command_parse_one(c, start, start + cmdlen);

    start += len;
    remaining -= len;
  }

  byte_copy(c->in, c->in_len - remaining, c->in + remaining);
  c->in_len = remaining;
}
