#include <stdio.h>

#include "./lib/lib.c"

typedef struct {
  char *nickname;
  char *channel;
  bool_t do_compression;
  char *server_password;
  char *server_url;
  vector_t *cached_messages;
  int return_code;
} appdata_t;

typedef enum {
  ERROR_FAILED_TO_SERIALIZE_MESSAGE,
  ERROR_FAILED_TO_COMPRESS_MESSAGE,
  ERROR_FAILED_TO_DECOMPRESS_MESSAGE,
} error_t;

vector_t *rx_messages(char *server_url, char *server_password,
                      bool_t do_compression) {
  vector_t *parsed_messages = vector_create(1);
  defer(byte_sized_free) byte_sized_t *data = http_get(server_url);
  if (data == NULL) {
#ifdef DEBUG
    printf("<!> Failed to get data from server\n");
#endif
    return parsed_messages;
  }

#ifdef DEBUG
  printf("Received data from server: %zu\n", data->size);
#endif

  vector_t *received_messages = split_string((char *)data->data, ',');
  if (received_messages == NULL) {
#ifdef DEBUG
    printf("<!> Failed to split received data into messages\n");
#endif
    return parsed_messages;
  }

  for (size_t i = 0; i < received_messages->length; i++) {
    char *received_message = (char *)vector_get(received_messages, i);
    message_t *msg = message_from_transmittable(
        received_message, do_compression, server_password);
    if (msg == NULL) {
#ifdef DEBUG
      printf("<!> Failed to parse received message: %s\n", received_message);
#endif
      continue;
    }

#ifdef DEBUG
    printf("<@> Parsed message: %s\n", received_message);
#endif

    if (vector_push(parsed_messages, msg) != 0) {
#ifdef DEBUG
      printf("<!> Failed to push parsed message: %s\n", received_message);
#endif
    }
  }

  return parsed_messages;
}

// --- START - COMMANDS ---

typedef struct {
  char *command;
  char *arg1;
} command_t;

command_t *parse_command(char *input, size_t input_len) {
  command_t *cmd = malloc(sizeof(command_t));
  if (cmd == NULL) {
    return NULL;
  }
  cmd->command = NULL;
  cmd->arg1 = NULL;

  if (input_len < 2 || input[0] != '/') {
    free(cmd);
    return NULL;
  }

  size_t command_name_len = 0;
  size_t idx = 1;
  for (; idx < input_len; idx++) {
    if (input[idx] == ' ') {
      command_name_len = idx;
      break;
    }
  }
  command_name_len = idx;

  char *command_name = malloc(command_name_len);
  if (command_name == NULL) {
    free(cmd);
    return NULL;
  }
  memcpy(command_name, input + 1, command_name_len - 1);
  command_name[command_name_len] = '\0';
  cmd->command = command_name;

  size_t command_arg_1_len = 0;
  char *command_arg_1 = NULL;
  idx = command_name_len + 1;
  for (; idx < input_len; idx++) {
    if (input[idx] == ' ') {
      command_arg_1_len = idx;
      break;
    }
  }
  command_arg_1_len = idx;

  if (command_arg_1_len > command_name_len + 1) {
    command_arg_1 = malloc(command_arg_1_len - command_name_len);
    if (command_arg_1 == NULL) {
      free(cmd);
      free(command_name);
      return NULL;
    }
    memcpy(command_arg_1, input + command_name_len + 1,
           command_arg_1_len - command_name_len - 1);
    command_arg_1[command_arg_1_len - command_name_len - 1] = '\0';
  }
  cmd->arg1 = command_arg_1;

  return cmd;
}

void execute_command(appdata_t *appdata, char *input, size_t input_len) {
  command_t *cmd = parse_command(input, input_len);
  if (cmd == NULL) {
    return;
  }

  if (cmd->command == NULL) {
    printf("! Invalid command: %.*s\n", (int)input_len, input);
    goto execute_command_end;
  }

  if (strcmp(cmd->command, "join") == 0) {
    if (cmd->arg1 == NULL) {
      printf("! Usage: /join <channel>\n");
      goto execute_command_end;
    } else if (strlen(cmd->arg1) > MAX_CHANNEL_LEN) {
      printf("! Channel name too long: %s\n", cmd->arg1);
      goto execute_command_end;
    } else {
      printf("Joining channel: %s\n", cmd->arg1);
      strncpy(appdata->channel, cmd->arg1, MAX_CHANNEL_LEN);
    }
  } else if (strcmp(cmd->command, "nick") == 0) {
    if (cmd->arg1 == NULL) {
      printf("! Usage: /nick <new_nickname>\n");
      goto execute_command_end;
    } else if (strlen(cmd->arg1) > MAX_SENDER_LEN) {
      printf("! Nickname too long: %s\n", cmd->arg1);
      goto execute_command_end;
    } else {
      printf("Changing nickname to: %s\n", cmd->arg1);
      strncpy(appdata->nickname, cmd->arg1, MAX_SENDER_LEN);
    }
  } else if (strcmp(cmd->command, "exit") == 0) {
    printf("Exiting...\n");
    appdata->return_code = 0;
  } else {
    printf("! Unknown command: %s\n", cmd->command);
  }

execute_command_end:
  if (cmd->command != NULL) {
    free(cmd->command);
  }
  if (cmd->arg1 != NULL) {
    free(cmd->arg1);
  }
  free(cmd);
  return;
}

// ---  END  - COMMANDS ---

void update(appdata_t *appdata) {
  clear_screen();

  // Get messages
  vector_t *messages = rx_messages(
      appdata->server_url, appdata->server_password, appdata->do_compression);
  if (messages == NULL) {
    printf("<!> Failed to receive messages from server\n");
    return;
  }

#ifdef DEBUG
  printf("---\nReceived %zu messages\n", messages->length);
#endif
  for (size_t i = 0; i < messages->length; i++) {
    message_t *msg = (message_t *)vector_get(messages, i);
    if (msg != NULL) {
      message_print(msg);
      message_inner_free(msg);
    }
  }
  vector_destroy(messages);

  // Hud
  printf("---\n");
  printf("Nickname: %s\n", appdata->nickname);
  printf("Channel: %s\n", appdata->channel);
  printf("Enter a message (or command)\n");

  // Input
  printf("> ");
  char *input = NULL;
  size_t input_len = 0;
  ssize_t read = getline(&input, &input_len, stdin);
  if (read < 2) {
    free(input);
    return;
  }
  input[read - 1] = '\0';

  message_t *msg = message_create(appdata->nickname, appdata->channel, input);
  if (msg == NULL) {
    printf("<!> Invalid message\n");
    return;
  }

  // Execute
  if (input[0] == '/') {
    execute_command(appdata, input, input_len);
  } else {
    size_t serialized_msg_len;
    defer(free_checked) char *serialized_msg =
        message_to_transmittable(msg, appdata->do_compression,
                                 appdata->server_password, &serialized_msg_len);

    if (serialized_msg == NULL) {
      printf("<!> Failed to send message\n");
    }

    http_post(appdata->server_url, serialized_msg);
  }
  free(input);
}

int main() {
  allocator_storage_t a = allocator_storage_create(24);

  appdata_t appdata = {
      .nickname = malloc(MAX_SENDER_LEN),
      .channel = malloc(MAX_CHANNEL_LEN),
      .do_compression = false,
      .server_password = malloc(MAX_PASSWORD_LEN),
      .server_url = malloc(MAX_URL_LEN),
      .cached_messages = NULL,
      .return_code = -1,
  };
  ALLOCATOR_STORE_OR_RETURN(&a, appdata.nickname, 1);
  ALLOCATOR_STORE_OR_RETURN(&a, appdata.channel, 1);
  ALLOCATOR_STORE_OR_RETURN(&a, appdata.server_password, 1);
  ALLOCATOR_STORE_OR_RETURN(&a, appdata.server_url, 1);
  strncpy(appdata.nickname, "myst", MAX_SENDER_LEN);
  strncpy(appdata.channel, "root", MAX_CHANNEL_LEN);
  strncpy(appdata.server_password, "null", MAX_PASSWORD_LEN);
  strncpy(appdata.server_url, "http://localhost:13337", MAX_URL_LEN);

  while (appdata.return_code != 0) {
    update(&appdata);
  }

  allocator_storage_free_inner_pointers(&a);
  return 0;
}
