#include "readwrite.h"
#include "strerr.h"
#include "error.h"
#include "str.h"
#include "close.h"
#include "case.h"

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

struct client clients[MAX_CLIENTS];

void client_new(int fd) {
  int i, j;

  for(i = 0; i < MAX_CLIENTS; i++)
    if(!clients[i].active)
      break;

  if(i >= MAX_CLIENTS) {
    static const char msg[] = "ERROR :Too many connections.\r\n";
    write(fd, msg, sizeof(msg)-1);
    close(fd);
    return;
  }

  clients[i].active = 1;
  clients[i].fd = fd;

  clients[i].is_away = 0;
  clients[i].away_reason[0] = '\0';

  clients[i].in_len = 0;
  buffer_init(&clients[i].out, write, clients[i].fd,
      clients[i].out_space, sizeof(clients[i].out_space));

  clients[i].nick[0] = '\0';
  for(j = 0; j < MAX_USER_CHANNELS; j++)
    clients[i].channels[j].active = 0;

  tai_now(&clients[i].stamp);
}

void client_read(struct client* c) {
  if(!c->active)
    return;

  while(c->in_len < sizeof(c->in)) {
    int result;

    result = read(c->fd, c->in + c->in_len, sizeof(c->in) - c->in_len);
    if(result < 0) {
      if(errno == error_again)
        break;
      strerr_warn1("tinyircd: client read error: ", &strerr_sys);
      client_drop(c, "read error", -1);
      return;
    }

    if(result == 0) {
      client_drop(c, "Client closed connection", -1);
      return;
    }

    c->in_len += result;
  }

  command_parse_all(c);
}

void client_message_start(struct client* c, struct client* f, const char* s) {
  client_puts(c, ":");
  if(f) {
    client_puts(c, f->nick);
    client_puts(c, "!unknown@unknown");
  }
  else {
    client_puts(c, servername);
  }
  client_puts(c, " ");
  client_puts(c, s);
  client_puts(c, " ");
}

void client_reply_start(struct client* c, const char* s) {
  client_message_start(c, 0, s);
  if(c->nick[0]) {
    client_puts(c, c->nick);
    client_puts(c, " ");
  }
}

void client_reply_notregistered(struct client* c) {
  client_reply_start(c, "451");
  client_puts(c, ":You have not registered\r\n");
}

void client_puts(struct client* c, const char* buf) {
  client_put(c, buf, str_len(buf));
}

void client_put(struct client* c, const char* buf, unsigned int len) {
  if(!c->active)
    return;

  if(buffer_put(&c->out, buf, len) < 0) {
    if(errno != error_again) {
      strerr_warn1("tinyircd: write error to client", &strerr_sys);
      client_drop(c, "write error", -1);
    }
  }
}

void client_flush(struct client* c) {
  if(!c->active)
    return;

  if(buffer_flush(&c->out) < 0) {
    if(errno != error_again) {
      strerr_warn1("tinyircd: write error to client", &strerr_sys);
      client_drop(c, "write error", -1);
    }
  }
}

void client_drop(struct client* c, const char* reason, int len) {
  struct member_list listeners;

  if(!c->active)
    return;
  close(c->fd);

  client_listeners(c, &listeners);
  client_remove_from_list(c, &listeners);

  channel_message_start(&listeners, c, "QUIT");
  if(reason) {
    if (len == -1) len = str_len(reason);
    channel_puts(&listeners, ":");
    channel_put(&listeners, reason, len);
  }

  channel_puts(&listeners, "\r\n");

  c->active = 0;
}

static int nick_equals(const char* a, const char* b) {
  return case_equals(a, b);
}

struct client* client_find_by_nick(const char* s) {
  int i;

  for(i = 0; i < MAX_CLIENTS; i++)
    if(clients[i].active && nick_equals(clients[i].nick, s))
      return &clients[i];

  return 0;
}

/* !! see nick_equals */
static int channel_equals(const char* a, const char* b) {
  return str_equal(a, b);
}

int client_is_member(const struct client* c, const char* channel) {
  int i;

  for(i = 0; i < MAX_USER_CHANNELS; i++)
    if(c->channels[i].active && channel_equals(c->channels[i].s, channel))
      return 1;
  return 0;
}

int client_add_channel(struct client* c, const char* channel) {
  int i;

  for(i = 0; i < MAX_USER_CHANNELS; i++)
    if(!c->channels[i].active)
      break;

  if(i >= MAX_USER_CHANNELS)
    return -1;

  c->channels[i].active = 1;
  str_copyn(c->channels[i].s, MAX_CHANNEL, channel, -1);

  return 0;
}

void client_del_channel(struct client* c, const char* channel) {
  int i;

  for(i = 0; i < MAX_USER_CHANNELS; i++)
    if(c->channels[i].active && channel_equals(c->channels[i].s, channel))
      c->channels[i].active = 0;
}

static int is_listener(const struct client* a, const struct client* b) {
  int i, j;

  if(a == b)
    return 1;

  for(i = 0; i < MAX_USER_CHANNELS; i++) {
    if(!a->channels[i].active)
      continue;

    for(j = 0; j < MAX_USER_CHANNELS; j++) {
      if(!b->channels[j].active)
        continue;
      if(channel_equals(a->channels[i].s, b->channels[j].s))
        return 1;
    }
  }

  return 0;
}

void client_listeners(const struct client* c, struct member_list* m) {
  int i;

  m->n = 0;

  for(i = 0; i < MAX_CLIENTS; i++) {
    if(!clients[i].active)
      continue;

    if(is_listener(c, &clients[i]))
      m->v[m->n++] = &clients[i];
  }
}

void client_remove_from_list(const struct client* c, struct member_list* m) {
  int i;

  for(i = m->n - 1; i >= 0; --i) {
    if(m->v[i] == c) {
      m->v[i] = m->v[--m->n];
      return;
    }
  }
}
