/**
 * @file lfile.c
 *
 * @brief Handle read/write abstractions to the users directory.
 *
 * @todo Add support for windows and mac OS.
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#ifdef LINUX
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#endif

#include "lephisto.h"
#include "log.h"
#include "lfile.h"

static char lephisto_base[PATH_MAX] = "\0"; /**< Store Lephisto's base path. */
/**
 * @fn char* lfile_basePath(void)
 *
 * @brief Get Lephisto's base path (for saves etc..).
 *    @return The base path to Lephisto.
 */
char* lfile_basePath(void) {
  char* home;

  if(lephisto_base[0] == '\0') {
#ifdef LINUX
    home = getenv("HOME");
    snprintf(lephisto_base, PATH_MAX, "%s/.lephisto/", home);
#else
    FILE* f;

    /* Try to open the file, C89 compliant, but not as precise as stat. */
    f = fopen(file, 'r');
    if(f != NULL) {
      fclose(f);
      return 1;
    }
#endif
  }

  return lephisto_base;
}

/**
 * @fn int lfile_dirMakeExist(const char* path)
 *
 * @brief Create a directory if it doesn't exist.
 *
 * Uses relative path to basePath.
 *    @param path Path to create directory if it doesn't exist.
 *    @return 0 on success.
 */
int lfile_dirMakeExist(const char* path, ...) {
  char file[PATH_MAX];
  va_list ap;

  if(path == NULL) return -1;
  else { /* Get the message. */
    va_start(ap, path);
    vsnprintf(file, PATH_MAX, path, ap);
    va_end(ap);
  }

#ifdef LINUX
  struct stat buf;

  stat(file, &buf);
  if(!S_ISDIR(buf.st_mode))
    if(mkdir(file, S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
      WARN("Dir '%s' does not exist and unable to create", file);
      return -1;
    }
#else
#error "Needs implentation."
#endif

  return 0;
}

/**
 * @fn int lfile_fileExists(const char* path, ...)
 *
 * @brief Check to see if a file exists.
 *    @param path printf formatted string pointing to the file to check for existance.
 *    @return 1 if file exists, 0 if it doesn't or -1 on error.
 */
int lfile_fileExists(const char* path, ...) {
  char file[PATH_MAX];
  va_list ap;

  if(path == NULL) return -1;
  else { /* Get the message. */
    va_start(ap, path);
    vsnprintf(file, PATH_MAX, path, ap);
    va_end(ap);
  }

#ifdef LINUX
  struct stat buf;

  if(stat(file, &buf)==0) /* Stat worked, file must exist. */
    return 1;
#else
#error "Needs implementation."
#endif
  return 0;
}

/**
 * @fn char** lfile_readDir(int* lfiles, const char* path)
 *
 * @brief Lists all the visible files in a directory.
 *
 * Should also sort by last modified but thats up to the OS in question.
 * Paths are relative to base directory.
 *    @param[out] lfiles Returns how many files there are.
 *    @param path Directory to read.
 */
char** lfile_readDir(int* lfiles, const char* path, ...) {
  char file[PATH_MAX], base[PATH_MAX];
  char** files;
  va_list ap;

  if(path == NULL) {
    *lfiles = 0;
    return NULL;
  } else { /* Get the message. */
    va_start(ap, path);
    vsnprintf(base, PATH_MAX, path, ap);
    va_end(ap);
  }

#ifdef LINUX
  int i, j, k, n;
  DIR* d;
  struct dirent *dir;
  char* name;
  int mfiles;
  struct stat sb;
  time_t* tt, *ft;
  char** tfiles;

  (*lfiles) = 0;
  mfiles = 100;
  tfiles = malloc(sizeof(char*)*mfiles);
  tt = malloc(sizeof(time_t)*mfiles);

  d = opendir(base);
  if(d == NULL)
    return NULL;

  /* Get the file list. */
  while((dir = readdir(d)) != NULL) {
    name = dir->d_name;

    /* Skip hidden directories. */
    if(name[0] == '.')
      continue;

    /* Stat the file. */
    snprintf(file, PATH_MAX, "%s/%s", base, name);
    if(stat(file, &sb) == -1)
      continue; /* Unable to stat. */

    /* Enough memory? */
    if((*lfiles)+1 > mfiles) {
      mfiles += 100;
      tfiles = realloc(files, sizeof(char*) * mfiles);
      tt = realloc(tt, sizeof(time_t) * mfiles);
    }

    /* Write the information. */
    tfiles[(*lfiles)] = strdup(name);
    tt[(*lfiles)] = sb.st_mtime;
    (*lfiles)++;
  }

  closedir(d);

  /* Sort by last changed date. */
  if((*lfiles) > 0) {
    /* Need to allocate some stuff. */
    files = malloc(sizeof(char*)*(*lfiles));
    ft = malloc(sizeof(time_t)*(*lfiles));

    /* Fill the list. */
    for(i = 0; i < (*lfiles); i++) {
      n = -1;

      /* Get the next lowest. */
      for(j = 0; j < (*lfiles); j++) {
        /* Is lower? */
        if((n == -1) || (tt[j] > tt[n])) {
          /* Check if it's already there. */
          for(k = 0; k < i; k++)
            if(strcmp(files[k], tfiles[j])==0)
              break;

          /* New lowest. */
          if(k >= i)
            n = j;
        }
      }
      files[i] = tfiles[n];
      ft[i] = tt[n];
    }
    free(ft);
  } else
    files = NULL;

  /* Free temp stuff. */
  free(tfiles);
  free(tt);

#else
#error "Needs implementation."
#endif

  /* What if we find nothing? */
  if((*lfiles) == 0) {
    free(files);
    files = NULL;
  }

  return files;
}

/**
 * @brief Tries to create the file if it doesn't exist.
 *    @param path Path of the file to create.
 */
int lfile_touch(const char* path, ...) {
  char file[PATH_MAX];
  va_list ap;
  FILE* f;

  if(path == NULL) return -1;
  else { /* Get the message. */
    va_start(ap, path);
    vsnprintf(file, PATH_MAX, path, ap);
    va_end(ap);
  }

  /* Try to open the file, C89 cmpliant, but not as precise as stat. */
  f = fopen(file, "a+");
  if(f == NULL) {
    WARN("Unable to touch file '%s': %s", file, strerror(errno));
    return -1;
  }
  fclose(f);
  return 0;
}