/* Read, sort and compare two directories.  Used for GNU DIFF.
   Copyright (C) 1988, 1989 Free Software Foundation, Inc.
   Modified for DOS and OS/2 on 1991/09/14 by Kai Uwe Rommel
	<rommel@ars.muc.de>.

This file is part of GNU DIFF.

GNU DIFF 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 1, or (at your option)
any later version.

GNU DIFF 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 GNU DIFF; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "diff.h"

static int compare_names ();

/* Read the directory named DIRNAME and return a sorted vector
   of filenames for its contents.  NONEX nonzero means this directory is
   known to be nonexistent, so return zero files.  */

struct dirdata
{
  int length;			/* # elements in `files' */
  char **files;			/* Sorted names of files in the dir */
};

#if defined(MSDOS)
/*
** due to difference of opinion btw gnu and microsoft about what
** const means, const is defined away in diff.h, which causes warnings
** when compiling the headers.  This ugliness is avoided here.
*/
#ifdef const
#undef const
#endif
#include <string.h>
#include <ctype.h>
#ifdef OS2
#define strcmp stricmp
  /* stricmp() this is important for the HPFS because it is
  /* case-preserving but NOT case-sensitive */
#include <stdlib.h>
#define INCL_NOPM
#include <os2.h>
#define _A_NORMAL        0x00
#define _A_SUBDIR        0x10
#define _MAX_NAME        256
#else
#ifdef __TURBOC__
#include <dir.h>
#define _A_NORMAL        0x00
#define _A_SUBDIR        0x10
#endif
#include <dos.h>
#define _MAX_NAME        13
#endif

struct direct {
	char d_name[_MAX_NAME];
};

typedef struct _dir {
        int first;
#ifdef OS2
        FILEFINDBUF find;
#else
#ifdef __TURBOC__
        struct ffblk dta;
#else
        struct find_t dta;
#endif
#endif
	struct direct current;
} DIR;

#ifdef OS2
static HDIR hdir;
static USHORT count;
static BOOL lower;
#endif

#ifdef OS2
int IsFileSystemFAT(char *dir)
{
  USHORT nDrive;
  ULONG lMap;
  BYTE bData[64], bName[3];
  USHORT cbData;

  if ( _osmode == DOS_MODE )
    return TRUE;
  else
  {
    /* We separate FAT and HPFS file systems here.
     * Filenames read from a FAT system are converted to lower case
     * while the case of filenames read from a HPFS (and other future
     * file systems, like Unix-compatibles) is preserved.
     */

    if ( isalpha(dir[0]) && (dir[1] == ':') )
      nDrive = toupper(dir[0]) - '@';
    else
      DosQCurDisk(&nDrive, &lMap);

    bName[0] = (char) (nDrive + '@');
    bName[1] = ':';
    bName[2] = 0;

    cbData = sizeof(bData);

    if ( !DosQFSAttach(bName, 0U, 1U, bData, &cbData, 0L) )
      return !strcmp(bData + (*(USHORT *) (bData + 2) + 7), "FAT");
    else
      return FALSE;

    /* End of this ugly code */
  }
}
#endif

DIR *
opendir(char *name) {
	char localname[_MAX_NAME];
	DIR *rval = malloc(sizeof(DIR));
	strcpy(localname,name);
        strcat(localname,"/*.*");
#ifdef OS2
        lower = IsFileSystemFAT(name);
        hdir = count = 1;
#endif
	if(rval == NULL ||
#ifdef OS2
           DosFindFirst(localname, &hdir, _A_NORMAL|_A_SUBDIR, &rval->find,
                        sizeof(FILEFINDBUF), &count, 0L) != 0)
#else
#ifdef __TURBOC__
          findfirst(localname,&rval->dta,_A_NORMAL|_A_SUBDIR) != 0)
#else
          _dos_findfirst(localname,_A_NORMAL|_A_SUBDIR,&rval->dta) != 0)
#endif
#endif
                return NULL;
	rval->first = 1;
	return rval;
}

void
closedir(DIR *x) {
	free(x);
}

struct direct *
readdir(DIR *thisdir) {
	/*
	** first time through, we don't need to look for a file
	*/
        if(!thisdir->first) {
#ifdef OS2
                if(DosFindNext(hdir, &thisdir->find, sizeof(FILEFINDBUF),
                               &count) != 0)
#else
#ifdef __TURBOC__
                if(findnext(&thisdir->dta) != 0)
#else
                if(_dos_findnext(&thisdir->dta) != 0)
#endif
#endif
			return NULL;
	} else
                thisdir->first = 0;
#ifdef OS2
        strcpy(thisdir->current.d_name,thisdir->find.achName);
#else
#ifdef __BORLANDC__
        strcpy(thisdir->current.d_name,thisdir->dta.ff_name);
#else
        strcpy(thisdir->current.d_name,thisdir->dta.name);
#endif
#endif
	/* thisdir->current.d_name[13] = '\0'; */
#ifdef OS2
        if ( lower )
#endif
	  strlwr(thisdir->current.d_name);
	return &thisdir->current;
}

#endif /* MSDOS */

static struct dirdata
dir_sort (dirname, nonex)
     char *dirname;
     int nonex;
{
  register DIR *reading;
  register struct direct *next;
  struct dirdata dirdata;

  /* Address of block containing the files that are described.  */
  char **files;

  /* Length of block that `files' points to, measured in files.  */
  int nfiles;

  /* Index of first unused in `files'.  */
  int files_index;

  if (nonex)
    {
      dirdata.length = 0;
      dirdata.files = 0;
      return dirdata;
    }

  /* Open the directory and check for errors.  */
  reading = opendir (dirname);
  if (!reading)
    {
      perror_with_name (dirname);
      dirdata.length = -1;
      return dirdata;
    }

  /* Initialize the table of filenames.  */

  nfiles = 100;
  files = (char **) xmalloc (nfiles * sizeof (char *));
  files_index = 0;

  /* Read the directory entries, and insert the subfiles
     into the `files' table.  */

  while (next = readdir (reading))
    {
      /* Ignore the files `.' and `..' */
      if (next->d_name[0] == '.'
	  && (next->d_name[1] == 0
	      || (next->d_name[1] == '.'
		  && next->d_name[2] == 0)))
	continue;

      if (files_index == nfiles)
	{
	  nfiles *= 2;
	  files
	    = (char **) xrealloc (files, sizeof (char *) * nfiles);
	}
      files[files_index++] = concat (next->d_name, "", "");
    }

  closedir (reading);

  /* Sort the table.  */
  qsort (files, files_index, sizeof (char *), compare_names);

  /* Return a description of location and length of the table.  */
  dirdata.files = files;
  dirdata.length = files_index;

  return dirdata;
}

/* Sort the files now in the table.  */

static int
compare_names (file1, file2)
     char **file1, **file2;
{
  return strcmp (*file1, *file2);
}

/* Compare the contents of two directories named NAME1 and NAME2.
   This is a top-level routine; it does everything necessary for diff
   on two directories.

   NONEX1 nonzero says directory NAME1 doesn't exist, but pretend it is
   empty.  Likewise NONEX2.

   HANDLE_FILE is a caller-provided subroutine called to handle each file.
   It gets five operands: dir and name (rel to original working dir) of file
   in dir 1, dir and name pathname of file in dir 2, and the recursion depth.

   For a file that appears in only one of the dirs, one of the name-args
   to HANDLE_FILE is zero.

   DEPTH is the current depth in recursion.

   Returns the maximum of all the values returned by HANDLE_FILE,
   or 2 if trouble is encountered in opening files.  */

int
diff_dirs (name1, name2, handle_file, depth, nonex1, nonex2)
     char *name1, *name2;
#if !defined(MSDOS)
     int (*handle_file) ();
#else
      /* sorry, rms, I can't live with the assumption that
      ** sizeof(char *) == sizeof(int)
      */
      int (*handle_file)(char *,char *,
			 char *,char *,int);
#endif
     int depth, nonex1, nonex2;
{
  struct dirdata data1, data2;
  register int i1, i2;
  int val = 0;
  int v1;

  /* Get sorted contents of both dirs.  */
  data1 = dir_sort (name1, nonex1);
  data2 = dir_sort (name2, nonex2);
  if (data1.length == -1 || data2.length == -1)
    {
      if (data1.length >= 0)
	free (data1.files);
      if (data2.length >= 0)
	free (data2.files);
      return 2;
    }

  i1 = 0;
  i2 = 0;

  /* If -Sname was specified, and this is the topmost level of comparison,
     ignore all file names less than the specified starting name.  */

  if (dir_start_file && depth == 0)
    {
      while (i1 < data1.length && strcmp (data1.files[i1], dir_start_file) < 0)
	i1++;
      while (i2 < data2.length && strcmp (data2.files[i2], dir_start_file) < 0)
	i2++;
    }

  /* Loop while files remain in one or both dirs.  */
  while (i1 < data1.length || i2 < data2.length)
    {
      int nameorder;

      /* Compare next name in dir 1 with next name in dir 2.
	 At the end of a dir,
	 pretend the "next name" in that dir is very large.  */

      if (i1 == data1.length)
	nameorder = 1;
      else if (i2 == data2.length)
	nameorder = -1;
      else
	nameorder = strcmp (data1.files[i1], data2.files[i2]);

      if (nameorder == 0)
	{
	  /* We have found a file (or subdir) in common between both dirs.
	     Compare the two files.  */
	  v1 = (*handle_file) (name1, data1.files[i1], name2, data2.files[i2],
			       depth + 1);
	  i1++, i2++;
	}
      if (nameorder < 0)
	{
	  /* Next filename in dir 1 is less; that is a file in dir 1 only.  */
	  v1 = (*handle_file) (name1, data1.files[i1], name2, 0, depth + 1);
	  i1++;
	}
      if (nameorder > 0)
	{
	  /* Next filename in dir 2 is less; that is a file in dir 2 only.  */
	  v1 = (*handle_file) (name1, 0, name2, data2.files[i2], depth + 1);
	  i2++;
	}
      if (v1 > val)
	val = v1;
    }
  if (data1.files)
    free (data1.files);
  if (data2.files)
    free (data2.files);

  return val;
}
