#include "amiga.h"
#include "dir_data.h"
#include <string.h>

/* opendir/readdir/etc ... emulation w/ stat support hack */

static void free_entries(iDIR *info)
{
  struct idirent *scan = info->files;
  
  while (scan)
    {
      struct idirent *next = scan->next;
      
      free(scan);
      scan = next;
    }
}

static int gobble_dir(DIR *dir)
{
  iDIR *info = (iDIR *)dir->dd_buf;
  long ioerr;
  struct idirent **last = &info->files;
  struct FileLock *dirlock;
  
  free_entries(info);
  last_dir = 0;
  info->files = 0;
  dir->dd_loc = 0;
  /* Find a value for st_dev of stat() calls */
  dirlock = BADDR(dir->dd_fd);
  info->task = dirlock->fl_Task;
  while (ExNext(dir->dd_fd, &info->fib))
    {
      u_short namlen = strlen(info->fib.fib_FileName);
      u_short reclen = namlen + 1 + offsetof(struct idirent, entry.d_name);
      struct idirent *newentry = (struct idirent *)malloc(reclen);
      struct dirent *entry;
      
      if (!newentry)
	{
	  errno = ENOMEM;
	  return 0;
	}
      newentry->next = 0;
      *last = newentry;
      last = &newentry->next;
      
      newentry->numblocks = info->fib.fib_NumBlocks;
      newentry->size = info->fib.fib_Size;
      newentry->date = info->fib.fib_Date;
      newentry->type = info->fib.fib_DirEntryType;
      newentry->protection = info->fib.fib_Protection;
      
      entry = &newentry->entry;
      entry->d_reclen = reclen;
      entry->d_namlen = namlen;
      entry->d_off = dir->dd_loc++;
      strcpy(entry->d_name, info->fib.fib_FileName);
      entry->d_ino = info->fib.fib_DiskKey;
    }
  info->pos = info->files;
  dir->dd_loc = 0;
  ioerr = IoErr();
  if (ioerr == ERROR_NO_MORE_ENTRIES) return 1;
  
  errno = convert_oserr(ioerr);
  return 0;
}

DIR *opendir(char *dirname)
{
  DIR *new = (DIR *)malloc(sizeof *new);
  iDIR *info = (iDIR *)malloc(sizeof *info);
  char *dircopy = malloc(strlen(dirname) + 1);

  chkabort();
  if (new && dircopy && info)
    {
      new->dd_buf = (char *)info;
      new->dd_size = sizeof *info;
      
      info->files = info->pos = 0;
      info->seeked = 0;
      info->dirname = dircopy;
      strcpy(dircopy, dirname);
      info->cdir = _get_cd();
      
      if ((new->dd_fd = Lock(dirname, ACCESS_READ)) &&
	  Examine(new->dd_fd, &info->fib))
	{
	  if (gobble_dir(new)) return new;
	}
      else errno = convert_oserr(IoErr());
      closedir(new);
      return 0;
    }
  
  errno = ENOMEM;
  if (new) free(new);
  if (dircopy) free(dircopy);
  if (info) free(info);
  
  return 0;
}

void closedir(DIR *dir)
{
  iDIR *info = (iDIR *)dir->dd_buf;
  
  chkabort();
  last_dir = 0;
  free_entries(info);
  free(info->dirname);
  if (dir->dd_fd) UnLock(dir->dd_fd);
  free(dir->dd_buf);
  free(dir);
}

struct dirent *readdir(DIR *dir)
{
  iDIR *info = (iDIR *)dir->dd_buf;
  struct dirent *entry = 0;
  
  chkabort();
  if (info->seeked)
    {
      long cloc = 0;
      struct idirent *pos;
      
      pos = info->files;
      
      while (cloc < dir->dd_loc && pos)
	{
	  cloc++; pos = pos->next;
	}
      /*if (cloc != dir->dd_loc) error ...
	This doesn't seem to be defined very precisely */
      info->pos = pos;
      info->seeked = 0;
    }
  if (info->pos)
    {
      entry = &info->pos->entry;
      
      last_dir = dir;
      last_entry = info->pos;
      
      info->pos = info->pos->next;
      dir->dd_loc++;
    }
  return entry;
}

long telldir(DIR *dir)
{
  chkabort();
  return dir->dd_loc;
}

void seekdir(DIR *dir, long loc)
{
  iDIR *info = (iDIR *)dir->dd_buf;
  
  chkabort();
  info->seeked = 1;
  dir->dd_loc = loc;
}

#if 0
void rewwinddir(DIR *dir)
{
  chkabort();
  gobble_dir(dir);
}
#endif
