/*
 * Copyright 2001-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

#include "cs_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>

#include "neo_misc.h"
#include "neo_err.h"
#include "neo_files.h"
#include "wildmat.h"

NEOERR *ne_mkdirs (const char *path, mode_t mode)
{
  char mypath[_POSIX_PATH_MAX];
  int x;
  int r;

  strncpy (mypath, path, sizeof(mypath));
  x = strlen(mypath);
  if ((x < sizeof(mypath)) && (mypath[x-1] != '/'))
  {
    mypath[x] = '/';
    mypath[x+1] = '\0';
  }

  for (x = 1; mypath[x]; x++)
  {
    if (mypath[x] == '/')
    {
      mypath[x] = '\0';
#ifdef __MINGW32__
      /* Braindead MINGW32 doesn't just have a dummy argument for mode */
      r = mkdir (mypath);
#else
      r = mkdir (mypath, mode);
#endif

      if (r == -1 && errno != EEXIST)
      {
	return nerr_raise_errno(NERR_SYSTEM, "ne_mkdirs: mkdir(%s, %x) failed", mypath, mode);
      }
      mypath[x] = '/';
    }
  }
  return STATUS_OK;
}

NEOERR *ne_load_file_len (const char *path, char **str, int *out_len)
{
  struct stat s;
  int fd;
  int len;
  int bytes_read;

  *str = NULL;
  if (out_len) *out_len = 0;

  if (stat(path, &s) == -1)
  {
    if (errno == ENOENT)
      return nerr_raise (NERR_NOT_FOUND, "File %s not found", path);
    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
  }

  fd = open (path, O_RDONLY);
  if (fd == -1)
  {
    return nerr_raise_errno (NERR_SYSTEM, "Unable to open file %s", path);
  }
  len = s.st_size;
  *str = (char *) malloc (len + 1);

  if (*str == NULL)
  {
    close(fd);
    return nerr_raise (NERR_NOMEM, 
	"Unable to allocate memory (%d) to load file %s", s.st_size, path);
  }
  if ((bytes_read = read (fd, *str, len)) == -1)
  {
    close(fd);
    free(*str);
    return nerr_raise_errno (NERR_SYSTEM, "Unable to read file %s", path);
  }

  (*str)[bytes_read] = '\0';
  close(fd);
  if (out_len) *out_len = bytes_read;

  return STATUS_OK;
}

NEOERR *ne_load_file (const char *path, char **str) {
  return ne_load_file_len (path, str, NULL);
}

NEOERR *ne_save_file (const char *path, char *str)
{
  NEOERR *err;
  int fd;
  int w, l;

  fd = open (path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP );
  if (fd == -1)
  {
    return nerr_raise_errno (NERR_IO, "Unable to create file %s", path);
  }
  l = strlen(str);
  w = write (fd, str, l);
  if (w != l)
  {
    err = nerr_raise_errno (NERR_IO, "Unable to write file %s", path);
    close (fd);
    return err;
  }
  close (fd);

  return STATUS_OK;
}

NEOERR *ne_remove_dir (const char *path)
{
  NEOERR *err;
  DIR *dp;
  struct stat s;
  struct dirent *de;
  char npath[_POSIX_PATH_MAX];

  if (stat(path, &s) == -1)
  {
    if (errno == ENOENT) return STATUS_OK;
    return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", path);
  }
  if (!S_ISDIR(s.st_mode))
  {
    return nerr_raise (NERR_ASSERT, "Path %s is not a directory", path);
  }
  dp = opendir(path);
  if (dp == NULL)
    return nerr_raise_errno (NERR_IO, "Unable to open directory %s", path);
  while ((de = readdir (dp)) != NULL)
  {
    if (strcmp(de->d_name, ".") && strcmp(de->d_name, ".."))
    {
      snprintf (npath, sizeof(npath), "%s/%s", path, de->d_name);
      if (stat(npath, &s) == -1)
      {
	if (errno == ENOENT) continue;
	closedir(dp);
	return nerr_raise_errno (NERR_SYSTEM, "Unable to stat file %s", npath);
      }
      if (S_ISDIR(s.st_mode))
      {
	err = ne_remove_dir(npath);
	if (err) break;
      }
      else
      {
	if (unlink(npath) == -1)
	{
	  if (errno == ENOENT) continue;
	  closedir(dp);
	  return nerr_raise_errno (NERR_SYSTEM, "Unable to unlink file %s", 
	      npath);
	}
      }
    }
  }
  closedir(dp);
  if (rmdir(path) == -1)
  {
    return nerr_raise_errno (NERR_SYSTEM, "Unable to rmdir %s", path);
  }
  return STATUS_OK;
}

NEOERR *ne_listdir(const char *path, ULIST **files)
{
  return nerr_pass(ne_listdir_fmatch(path, files, NULL, NULL));
}

static int _glob_match(void *rock, const char *filename)
{
  return wildmat(filename, rock);
}

NEOERR *ne_listdir_match(const char *path, ULIST **files, const char *match)
{
  return nerr_pass(ne_listdir_fmatch(path, files, _glob_match, (void *)match));
}

NEOERR *ne_listdir_fmatch(const char *path, ULIST **files, MATCH_FUNC fmatch, 
                          void *rock)
{
  DIR *dp;
  struct dirent *de;
  ULIST *myfiles = NULL;
  NEOERR *err = STATUS_OK;

  if (files == NULL) 
    return nerr_raise(NERR_ASSERT, "Invalid call to ne_listdir_fmatch");

  if (*files == NULL)
  {
    err = uListInit(&myfiles, 10, 0);
    if (err) return nerr_pass(err);
  }
  else
  {
    myfiles = *files;
  }

  if ((dp = opendir (path)) == NULL)
  {
    return nerr_raise_errno(NERR_IO, "Unable to opendir %s", path);
  }
  while ((de = readdir (dp)) != NULL)
  {
    if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
      continue;

    if (fmatch != NULL && !fmatch(rock, de->d_name))
      continue;

    err = uListAppend(myfiles, strdup(de->d_name));
    if (err) break;
  }
  closedir(dp);
  if (err && *files == NULL)
  {
    uListDestroy(&myfiles, ULIST_FREE);
  }
  else if (*files == NULL)
  {
    *files = myfiles;
  }
  return nerr_pass(err);
}
