/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: file_browser.c,v 1.1 2000/01/15 16:14:35 rich Exp $
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_SYS_TYPE_H
#include <sys/types.h>
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#include <GL/glut.h>

#include "xracer.h"
#include "xracer-mode.h"
#include "xracer-log.h"
#include "xracer-text.h"
#include "xracer-PATH_MAX.h"
#include "xracer-NAME_MAX.h"
#include "xracer-file-browser.h"

/* Program-level initializations. */
void
xrFileBrowserInit ()
{
}

void *
xrFileBrowserCreate (const char *pathname,
		     const struct xrFileBrowserCallbacks *callbacks)
{
  struct xrFileBrowserObject *obj;

  /* Create a new file browser object. */
  obj = xmalloc (sizeof (struct xrFileBrowserObject));

#ifdef HAVE_GETCWD
  getcwd (obj->cwd, sizeof obj->cwd);
#else
#error "require working getcwd(2) system call"
#endif
  obj->callbacks = callbacks;
  obj->contents.nr_entries = 0;
  obj->contents.entries = 0;

  if (xrFileBrowserChangeDirectory ((void *) obj, pathname) == -1)
    {
      free (obj);
      return 0;
    }

  return (void *) obj;
}

int
xrFileBrowserDisplay (void *file_browser, void *args)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;
  int i, index = 0;

  if (obj->callbacks->display == 0) return 0;

  /* Iterate over the contents of the directory. */
  for (i = 0; i < obj->contents.nr_entries; ++i)
    {
      if (obj->contents.entries[i].show)
	{
	  if (obj->callbacks->display (index++, obj->cwd,
				       obj->contents.entries[i].filename,
				       &obj->contents.entries[i].stat,
				       args) < 0)
	    {
	      return -1;
	    }
	}
    }

  return 0;
}

static int
compare_entries (const void *ventry0, const void *ventry1)
{
  const struct xrFileBrowserDirectoryEntry *entry0
    = (const struct xrFileBrowserDirectoryEntry *) ventry0;
  const struct xrFileBrowserDirectoryEntry *entry1
    = (const struct xrFileBrowserDirectoryEntry *) ventry1;

  return strcmp (entry0->filename, entry1->filename);
}

/* Change directory and load the contents of the new directory. */
int
xrFileBrowserChangeDirectory (void *file_browser,
			      const char *pathname)
{
#if defined(HAVE_STAT) && defined(HAVE_LSTAT) && defined (HAVE_READLINK) && defined(HAVE_OPENDIR)
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;
  char saved_cwd[PATH_MAX];
  DIR *dir;
  struct dirent *dentry;
  int i;

  /* Save real current directory. Switch to browser directory. */
#ifdef HAVE_GETCWD
  getcwd (saved_cwd, sizeof saved_cwd);
#else
#error "require working getcwd(2) system call"
#endif

  if (chdir (obj->cwd) == -1)
    {
      xrLogPerror ("chdir: %s", obj->cwd);
      return -1;
    }

  /* Change directory as required. */
  if (chdir (pathname) == -1)
    {
      xrLogPerror ("chdir: %s", pathname);
      /* Restore saved directory. */
      chdir (saved_cwd);
      return -1;
    }

  /* Store new current directory. */
#ifdef HAVE_GETCWD
  getcwd (obj->cwd, sizeof obj->cwd);
#else
#error "require working getcwd(2) system call"
#endif

  /* Free up existing directory contents. */
  if (obj->contents.entries != 0)
    {
      for (i = 0; i < obj->contents.nr_entries; ++i)
	free (obj->contents.entries[i].filename);
      free (obj->contents.entries);
      obj->contents.entries = 0;
      obj->contents.nr_entries = 0;
    }

  /* Load the directory contents into memory. */
  dir = opendir (".");
  if (dir == 0)
    {
      xrLogPerror ("opendir: %s", obj->cwd);
      /* Restore saved directory. */
      chdir (saved_cwd);
      return -1;
    }

  i = 0;
  while ((dentry = readdir (dir)) != 0)
    {
      obj->contents.entries
	= xrealloc (obj->contents.entries,
		    (i+1) * sizeof (struct xrFileBrowserDirectoryEntry));
      obj->contents.nr_entries = i+1;
      obj->contents.entries[i].filename
	= xmalloc (sizeof (char) * (strlen (dentry->d_name) + 1));
      strcpy (obj->contents.entries[i].filename, dentry->d_name);

      if (stat (dentry->d_name, &obj->contents.entries[i].stat) == -1)
	{
	  if (errno != ENOENT)
	    {
	      xrLogPerror ("stat: %s/%s", obj->cwd, dentry->d_name);
	      /* Restore saved directory. */
	      chdir (saved_cwd);
	      return -1;
	    }
	  else
	    {
	      /* This can happen if we stat a symlink which doesn't exist. */
	      obj->contents.nr_entries --;
	      continue;
	    }
	}

      obj->contents.entries[i].show = 
	!obj->callbacks->filter ||
	obj->callbacks->filter (obj->cwd, dentry->d_name,
				&obj->contents.entries[i].stat);

      i++;
    }

  /* Restore saved directory. */
  chdir (saved_cwd);

  /* Sort files into alphabetical order, directories first. */
  qsort (obj->contents.entries, obj->contents.nr_entries,
	 sizeof obj->contents.entries[0], compare_entries);

  xrLog (LOG_DEBUG, "changed to directory: %s", obj->cwd);

  return 0;

#else
#error "require working stat(2) and opendir(3) calls"
#endif
}

int
xrFileBrowserReload (void *file_browser)
{
  return xrFileBrowserChangeDirectory (file_browser, ".");
}

void
xrFileBrowserSetCallbacks (void *file_browser,
			   const struct xrFileBrowserCallbacks *callbacks)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;

  obj->callbacks = callbacks;

  xrFileBrowserChangeDirectory (file_browser, ".");
}

int
xrFileBrowserGetNrEntries (void *file_browser)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;
  int i, count = 0;

  for (i = 0; i < obj->contents.nr_entries; ++i)
    if (obj->contents.entries[i].show)
      count ++;
  return count;
}

const char *
xrFileBrowserGetDirectory (void *file_browser)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;

  return obj->cwd;
}

void
xrFileBrowserGetFileInfo (void *file_browser, int index,
			  const char **filename_rtn,
			  const struct stat **stat_rtn)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;
  int i;

  for (i = 0; i < obj->contents.nr_entries; ++i)
    if (obj->contents.entries[i].show)
      {
	if (index-- == 0)
	  {
	    if (filename_rtn)
	      *filename_rtn = obj->contents.entries[i].filename;
	    if (stat_rtn)
	      *stat_rtn = &obj->contents.entries[i].stat;
	    return;
	  }
      }
}

void
xrFileBrowserDelete (void *file_browser)
{
  struct xrFileBrowserObject *obj = (struct xrFileBrowserObject *)file_browser;
  int i;

  if (obj->contents.entries)
    {
      for (i = 0; i < obj->contents.nr_entries; ++i)
	free (obj->contents.entries[i].filename);
      free (obj->contents.entries);
    }
  free (obj);
}
