/*
 * apilist.c
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "mrdb.h"
#include "mrdberror.h"
#include "apilist.h"
#include "mruby/compile.h"
#include "mruby/irep.h"
#include "mruby/debug.h"

#define LINE_BUF_SIZE MAX_COMMAND_LINE

typedef struct source_file {
  char *path;
  uint16_t lineno;
  FILE *fp;
} source_file;

static void
source_file_free(mrb_state *mrb, source_file *file)
{
  if (file != NULL) {
    if (file->path != NULL) {
      mrb_free(mrb, file->path);
    }
    if (file->fp != NULL) {
      fclose(file->fp);
      file->fp = NULL;
    }
    mrb_free(mrb, file);
  }
}

static char*
build_path(mrb_state *mrb, const char *dir, const char *base)
{
  int len;
  char *path = NULL;

  len = strlen(base) + 1;

  if (strcmp(dir, ".")) {
    len += strlen(dir) + strlen("/");
  }

  if ((path = mrb_malloc(mrb, len)) != NULL) {
    memset(path, 0, len);

    if (strcmp(dir, ".")) {
      strcat(path, dir);
      strcat(path, "/");
    }
    strcat(path, base);
  }
  return path;
}

static char*
dirname(mrb_state *mrb, const char *path)
{
  size_t len;
  char *p, *dir;

  if (path == NULL) {
    return NULL;
  }

  p = strrchr(path, '/');
  len = p != NULL ? p - path : strlen(path);

  if ((dir = mrb_malloc(mrb, len + 1)) != NULL) {
    strncpy(dir, path, len);
    dir[len] = '\0';
  }
  return dir;
}

static source_file*
source_file_new(mrb_state *mrb, mrb_debug_context *dbg, char *filename)
{
  source_file *file = NULL;

  if ((file = mrb_malloc(mrb, sizeof(source_file))) == NULL) {
    return NULL;
  }

  memset(file, '\0', sizeof(source_file));
  file->fp = fopen(filename, "rb");

  if (file->fp == NULL) {
    source_file_free(mrb, file);
    return NULL;
  }

  file->lineno = 1;
  file->path = mrb_malloc(mrb, strlen(filename) + 1);
  strcpy(file->path, filename);
  return file;
}

static mrb_bool
remove_newlines(char *s, FILE *fp)
{
  int c;
  char *p;
  size_t len;

  if ((len = strlen(s)) == 0) {
    return FALSE;
  }

  p = s + len - 1;

  if (*p != '\r' && *p != '\n') {
    return FALSE;
  }

  if (*p == '\r') {
    /* peek the next character and skip '\n' */
    if ((c = fgetc(fp)) != '\n') {
      ungetc(c, fp);
    }
  }

  /* remove trailing newline characters */
  while (s <= p && (*p == '\r' || *p == '\n')) {
    *p-- = '\0';
  }

  return TRUE;
}

static void
show_lines(source_file *file, uint16_t line_min, uint16_t line_max)
{
  char buf[LINE_BUF_SIZE];
  int show_lineno = 1, found_newline = 0, is_printed = 0;

  if (file->fp == NULL) {
    return;
  }

  while (fgets(buf, sizeof(buf), file->fp) != NULL) {
    found_newline = remove_newlines(buf, file->fp);

    if (line_min <= file->lineno) {
      if (show_lineno) {
        printf("%-8d", file->lineno);
      }
      show_lineno = found_newline;
      printf(found_newline ? "%s\n" : "%s", buf);
      is_printed = 1;
    }

    if (found_newline) {
      if (line_max < ++file->lineno) {
        break;
      }
    }
  }

  if (is_printed && !found_newline) {
    printf("\n");
  }
}

char*
mrb_debug_get_source(mrb_state *mrb, mrdb_state *mrdb, const char *srcpath, const char *filename)
{
  int i;
  FILE *fp;
  const char *search_path[3];
  char *path = NULL;

  search_path[0] = srcpath;
  search_path[1] = dirname(mrb, mrb_debug_get_filename(mrdb->dbg->root_irep, 0));
  search_path[2] = ".";

  for (i = 0; i < 3; i++) {
    if (search_path[i] == NULL) {
      continue;
    }

    if ((path = build_path(mrb, search_path[i], filename)) == NULL) {
      continue;
    }

    if ((fp = fopen(path, "rb")) == NULL) {
      mrb_free(mrb, path);
      path = NULL;
      continue;
    }
    fclose(fp);
    break;
  }

  mrb_free(mrb, (void *)search_path[1]);

  return path;
}

int32_t
mrb_debug_list(mrb_state *mrb, mrb_debug_context *dbg, char *filename, uint16_t line_min, uint16_t line_max)
{
  char *ext;
  source_file *file;

  if (mrb == NULL || dbg == NULL || filename == NULL) {
    return MRB_DEBUG_INVALID_ARGUMENT;
  }

  ext = strrchr(filename, '.');

  if (ext == NULL || strcmp(ext, ".rb")) {
    printf("List command only supports .rb file.\n");
    return MRB_DEBUG_INVALID_ARGUMENT;
  }

  if (line_min > line_max) {
    return MRB_DEBUG_INVALID_ARGUMENT;
  }

  if ((file = source_file_new(mrb, dbg, filename)) != NULL) {
    show_lines(file, line_min, line_max);
    source_file_free(mrb, file);
    return MRB_DEBUG_OK;
  }
  else {
    printf("Invalid source file named %s.\n", filename);
    return MRB_DEBUG_INVALID_ARGUMENT;
  }
}
