/*
 *  mklist.c, a replacement for James Jessiman's makelist
 *  Copyright (C) 1999  Lars C. Hassing
 *
 *  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.
                                                                             */

/*****************************************************************************
  Please do not edit this file. In stead contact Lars C. Hassing (lch@cci.dk)
  to get your changes integrated in a future release.
******************************************************************************/

/*
990214 lch Release v1.0
990316 lch Release v1.1: added options + minor changes to sort algorithms
990518 lch Release v1.2: Skip "~Moved to xxx" by default
20000703 lch Release v1.3: Added -i and -o
20030625 dmh Release v1.4: Ported to gcc.
200????? ??? Release v1.5: ??? Lost track of this one...
20090928 dmh Release v1.6beta1: Added -f -q -v -8 and allow 25 chars in filenames.
20100706 dmh Release v1.6: Added -l and made long format the default.
                                                                             */

/* Compile with Borland Turbo C 2.0: tcc -mc -d -f -k- -N -v- -y- -wrvl -wstv
-wucp -wnod -wpro -wuse -wsig -ncmdsrel -M src\mklist.c                      */


#include "stdio.h"
#include "stdlib.h"
#include "ctype.h"
#include "string.h"
#include "alloc.h"
#include "dir.h"
#include "conio.h"

char   *ProgVer = "mklist  v1.6 20100706  (C) 1999-2010 Lars C. Hassing  lch@ccieurope.com";

int	forceit = 0;
int	quiet = 0;

/*****************************************************************/
/* Filename length compatibility stuff */
/*****************************************************************/
#ifndef _MAX_PATH
#define _MAX_PATH 256
#endif
char    shortfilepath[MAX_PATH];
char    shortfilename[MAX_PATH];


#ifdef __BORLANDC__
#define LACKS_BASENAME 1
int     numlines = 1000; /* Use small chunks for DOS */
int 	namelen = 12; /* Set to 12 for 8.3 DOS compatibility. */
#else
#ifdef __TURBOC__
#define LACKS_BASENAME 1
int     numlines = 1000; /* Use small chunks for DOS */
int 	namelen = 12; /* Set to 12 for 8.3 DOS compatibility. */
#else
int     numlines = 16384;  /* Allocate lines in bigger chunks if not DOS. */
int 	namelen = 25; /* LSC specs allow 25 chars in filename.   */
#endif
#endif


#ifdef _WIN32
#include <windows.h>
#define LACKS_BASENAME 1

#else
int GetShortPathName(char *longpath, char * shortpath, int psize)
{
    strncpy(shortpath, longpath, psize);
    return(strlen(shortpath);
}
#endif


#if LACKS_BASENAME
char *basename( const char *filepath )
{
  char *tmpstr, *ptr;

  if (filepath == NULL)
  {
    return NULL;
  }
  if ( (ptr = strrchr(filepath, '\\')) || (ptr = strrchr(filepath, '/')) )
  {
    /* If there isn't anything after the last separator, the result is a 0-length string */
    tmpstr = strdup(ptr+1);
  }
  else
  {
    /* dup the string, so caller can safely free whatever we return */
    tmpstr = strdup(filepath);
  }
  return tmpstr;
}
#endif

/*****************************************************************/
int                  CmpNumber(const void *p1, const void *p2)
{
   char                *s1 = *((char **) p1);
   char                *s2 = *((char **) p2);
   long                 l1;
   long                 l2;

   if (isdigit(*s1) && isdigit(*s2))
   {
      l1 = atol(s1);
      l2 = atol(s2);
      if (l1 != l2)
         return (l1 < l2 ? -1 : 1);
      /* Numbers are equal. Be sure to make 3005.dat come before 3005-1.dat */
      do
      {
         l1 = (unsigned char) *s1++;
         if ('A' <= l1 && l1 <= 'Z')
            l1 -= ('A' - 'a');
         else if (l1 == '.')
            l1 = '\0';                    /* Sort dot very first             */
         l2 = (unsigned char) *s2++;
         if ('A' <= l2 && l2 <= 'Z')
            l2 -= ('A' - 'a');
         else if (l2 == '.')
            l2 = '\0';                    /* Sort dot very first             */
      } while (l1 && (l1 == l2));

      return ((int) (l1 - l2));
   }
   return (stricmp(s1, s2));
}

/*****************************************************************/
int                  CmpDescription(const void *p1, const void *p2)
{
   char                *s1 = *((char **) p1);
   char                *s2 = *((char **) p2);
   int                  Res;

#if 0
   Res = stricmp(s1 + (namelen+2), s2 + (namelen+2));
#else
   s1 += strcspn(s1, " \t"); /* Find the beginning of whitespace. */
   s1 += strspn(s1, " \t");  /* Skip to end of whitespace (start of description). */
   s2 += strcspn(s2, " \t"); /* Find the beginning of whitespace. */
   s2 += strspn(s2, " \t");  /* Skip to end of whitespace (start of description). */
   Res = stricmp(s1, s2);
#endif

   return (Res ? Res : CmpNumber(p1, p2));
}

/*****************************************************************/
void                 PressAnyKey(void)
{
   if (forceit)
      return;
   if (quiet)
      return;
   printf("  Press any key to continue");
   getch();
   printf("\n");
}

/*****************************************************************/
void                 PrintUsage(void)
{
  printf("Options:\n");
  printf("  -h        You already figured this one out :-)\n");
  printf("  -n        Sort by Number\n");
  printf("  -d        Sort by Description\n");
  printf("  -c        Check for duplicate descriptions. \"parts.lst\" unchanged.\n");
  printf("  -m        Don't skip parts with \"~Moved to xxx\" description\n");
  printf("  -~        Skip parts with ~ description, e.g. \"~Winch  2 x  4 x  2 Top\"\n");
  printf("  -i <dir>  input directory, default is \"PARTS\" in current directory\n");
  printf("  -o <file> output filename, default is \"parts.lst\" in current directory\n");
  printf("  -f        Force it to finish.  No prompts.\n");
  printf("  -q        Quiet mode.  No warnings, and no prompts.\n");
  printf("  -8        Use 8.3 names for compatibility.\n");
  printf("  -t        Truncating descriptions to fit in an 80 char terminal window.\n");
  printf("  -r        Ragged filename column.  Size it to fit short filenames.\n");
  printf("  -l        Truncate Long descriptions at 64 chars.\n");
  printf("  -v        Print verbose info.  Useful for debugging.\n");
}

/*****************************************************************/
int                  main(int argc, char **argv)
{
   int                  CheckDuplicateDescriptions;
   int                  SortBy;
   int                  SkipTilde;
   int                  SkipMovedto;
   char                *arg;
   int                  c;
   int                  i;
   int                  j;
   FILE                *fp;
   struct ffblk         ffb;
   int                  done;
   int                  Len;
   char               **Lines;
   int                  maxLines;
   int                  nLines;
   int                  pathlen;
   char                 Line[200];
   char                 Dirname[MAX_PATH];
   char                 Filename[MAX_PATH];
   char                 OutFilename[MAX_PATH];
   char                *s;
   char                *Description;
   char                *FormattedLine;
   unsigned long        farcoreleftStart;
   unsigned long        farcoreleftEnd;
   long                 FileSize;
   struct stat		statbuf;
   int 			verbose;
   int 			ragged;
   int 			terminalsized;
   int 			longDescriptions;

   printf("%s\n", ProgVer);
   printf("Replacement for James Jessiman's makelist\n");
   printf("Call with -h to see a list of options.\n\n");

   strcpy(Dirname, "PARTS"); /* Default input directory path */
   strcpy(OutFilename, "parts.lst"); /* Default output filename */

   verbose = 0;
   ragged = 0; /* Steve wanted it ragged (1) by default */
   terminalsized = 0; /* Steve wanted it (1) to fit in 80 chars by default */
   longDescriptions = 1; /* Do not truncate descriptions at 64 chars by default */

   CheckDuplicateDescriptions = 0;
   SortBy = 0;
   SkipTilde = 0;
   SkipMovedto = 1;
   while (--argc > 0)
   {
      arg = *++argv;
      if (arg[0] == '-')
      {
         switch (arg[1])
         {
            case '?':
            case 'h':
               PrintUsage();
               exit(1);
               break;
            case '8':
               namelen = 12;
               break;
            case 'c':
               CheckDuplicateDescriptions = 1;
               break;
            case 'n':
            case 'd':
               SortBy = arg[1];
               break;
            case 'm':
               SkipMovedto = 0;
               break;
            case '~':
               SkipTilde = 1;
               break;
            case 'i':
	       if (--argc > 0)
		 strcpy(Dirname, *++argv);
	       else
	       {
		 PrintUsage();
		 printf("*** input directory expected as next argument after -i.\n");
		 exit(1);
	       }
               break;
            case 'o':
	       if (--argc > 0)
		 strcpy(OutFilename, *++argv);
	       else
	       {
		 PrintUsage();
		 printf("*** output filename expected as next argument after -o.\n");
		 exit(1);
	       }
               break;
            case 'q':
               quiet = 1;
            case 'f':
               forceit = 1;
               break;
            case 'r':
               ragged = ragged ^ 1;
               break;
            case 't':
               terminalsized = terminalsized ^ 1;
               break;
            case 'l':
               longDescriptions = longDescriptions ^ 1;
               break;
            case 'v':
               verbose = 1;
               break;
            default:
               PrintUsage();
               printf("*** Unknown option '%s'.\n", arg);
               exit(1);
               break;
         }
      }
      else
      {
         PrintUsage();
         exit(1);
      }
   }

   /* Do a stat to see if Dirname exists and is a directory. */
   if (stat(Dirname, &statbuf) < 0)
   {
     printf("*** Could not stat input directory \"%s\".\n", Dirname);
     exit(1);
   }

   if ((statbuf.st_mode & S_IFDIR) == 0) 
   {
     printf("*** Input directory \"%s\" is not a directory.\n", Dirname);
     exit(1);
   }

   if (CheckDuplicateDescriptions)
      SortBy = 'd';
   if (!SortBy)
   {
      if (forceit)
        SortBy = 'd';
      if (!quiet)
         printf("Sorting by [D]escription.\n");
   }

   if (!SortBy)
   {
      printf("Sort by [N]umber or [D]escription: ");
      c = getch();
      printf("%c\n", c);

      if (c == 'N' || c == 'n')
         SortBy = 'n';
      else if (c == 'D' || c == 'd')
         SortBy = 'd';
      else
      {
         printf("Nothing done.\n");
         exit(0);
      }
   }

   farcoreleftStart = farcoreleft();

   nLines = 0;
   maxLines = numlines;
   Lines = farmalloc(maxLines * sizeof(char *));
   if (!Lines)
   {
      printf("Out of memory after %d parts\n", nLines);
      printf("Memory available at beginning: %ld kBytes\n",
             (farcoreleftStart + 1023) / 1024);
      exit(1);
   }
   strcpy(Filename, Dirname);
   strcat(Filename, "\\");
   pathlen = strlen(Filename);
   strcat(Filename, "*.*");
   for (done = findfirst(Filename, &ffb, 0); !done; done = findnext(&ffb))
   {
      if (verbose)
      {
         printf("Processing file: \"%s\"\n", ffb.ff_name);
      }
      strcpy(Filename + pathlen, ffb.ff_name);
      fp = fopen(Filename, "rt");
      if (!fp)
      {
         if (!quiet) printf("Cannot open \"%s\"", ffb.ff_name);
         PressAnyKey();
         continue;
      }
      fgets(Line, sizeof(Line), fp);
      fclose(fp);

      s = Line + strlen(Line) - 1;
      while (s >= Line && (*s == '\n' || *s == '\r' || *s == '\t' || *s == ' '))
         *s-- = '\0';                     /* clear newline and trailing tabs
                                             and spaces                      */
      s = Line;
      while (*s == '\t' || *s == ' ')
         *s++;
      if (*s++ != '0')
      {
         if (!quiet)          printf("Line type 0 expected in \"%s\", skipping...", ffb.ff_name);
         PressAnyKey();
         continue;
      }
      while (*s == '\t' || *s == ' ')
         *s++;
      Description = s;
      if (SkipTilde && Description[0] == '~')
         continue;
      if (SkipMovedto && strncmp(Description, "~Moved to", 9) == 0)
         continue;
      Len = strlen(Description);
      if (Len == 0)
      {
         if (!quiet)          printf("Empty description in \"%s\"", ffb.ff_name);
         PressAnyKey();
      }
      if ((Len > 64) && !longDescriptions)
      {
         /* Original makelist truncates to 64 characters. */
         if (!quiet) 
         {
            printf("Description in \"%s\" will be truncated to 64 characters:\n",
                   ffb.ff_name);
            printf("Before: \"%s\"\n", Description);
            printf("After:  \"%-64.64s\"\n", Description);
         }
         PressAnyKey();
      }
      if (namelen == 12)
         FormattedLine = farmalloc(79);
      else
         FormattedLine = farmalloc(256);
      if (!FormattedLine)
      {
         printf("Out of memory after %d parts\n", nLines);
         printf("Memory available at beginning: %ld kBytes\n",
                (farcoreleftStart + 1023) / 1024);
         exit(1);
      }


      if (namelen > 12) 
         strcpy(shortfilename, ffb.ff_name);
      else
      {
         GetShortPathName(Filename, shortfilepath, MAX_PATH);
         s = basename(shortfilepath);
         strcpy(shortfilename, s);    
         if (s != NULL)
            free(s);
         if (strcmp(ffb.ff_name, shortfilename))
         {
            if (!quiet) 
               printf("Filename \"%s\" will be shortened to %s\n", 
                      ffb.ff_name, shortfilename);
            PressAnyKey();
         }
      }

      Len = strlen(shortfilename); 
      if (Len > namelen)
      {
         if (!quiet)          
            printf("Filename \"%s\" will be truncated to %d characters.\n",
                   shortfilename, namelen);
         PressAnyKey();
      }
      shortfilename[namelen] = 0;
      if (namelen == 12)
         sprintf(FormattedLine, "%-12s  %-64.64s", shortfilename, Description);
      else if (ragged && terminalsized)
      {
         if (Len > 14) /* Squeeze every last char out of the 80. */
            sprintf(FormattedLine, "%s %-64.64s", shortfilename, Description);
         else
            sprintf(FormattedLine, "%-14s  %-64.64s", shortfilename, Description);
      }
      else if (ragged && longDescriptions)
      {
         if (Len > 12)
            sprintf(FormattedLine, "%s %s", shortfilename, Description);
         else
            sprintf(FormattedLine, "%-12s  %s", shortfilename, Description);
      }
      else if (ragged)
      {
         if (Len > 12)
            sprintf(FormattedLine, "%s %-64.64s", shortfilename, Description);
         else
            sprintf(FormattedLine, "%-12s  %-64.64s", shortfilename, Description);
      }
      else if (longDescriptions)
         sprintf(FormattedLine, "%-25s  %s", shortfilename, Description);
      else
         sprintf(FormattedLine, "%-25s  %s", shortfilename, Description);
    
      if (terminalsized)
      {
         if (namelen == 12)
            FormattedLine[78] = 0;
         else
            FormattedLine[80] = 0;
      }

      if (verbose)
      {
         printf("%d:\t%s\n", nLines, FormattedLine);
      }
      if (nLines >= maxLines)
      {
         /* Let's have another 1000 pointers */
         maxLines += numlines;
         Lines = farrealloc(Lines, maxLines * sizeof(char *));
         if (!Lines)
         {
            printf("Out of memory after %d parts\n", nLines);
            printf("Memory available at beginning: %ld kBytes\n",
                   (farcoreleftStart + 1023) / 1024);
            exit(1);
         }
      }
      Lines[nLines++] = FormattedLine;
      if (nLines % 100 == 0)
         printf("%d parts so far...\r", nLines);
   }
   printf("%d parts found in %s.\n", nLines, Dirname);
   if (nLines == 0)
   {
      printf("No parts found, nothing done.\n");
      exit(0);
   }

   printf("Sorting...\n");
   qsort(Lines, nLines, sizeof(Lines[0]),
         (SortBy == 'n') ? CmpNumber : CmpDescription);

   if (CheckDuplicateDescriptions)
   {
      printf("Checking for duplicate descriptions. \"%s\" unchanged.\n", OutFilename);
      for (i = 0; i < nLines; i += j)
      {
         for (j = 1; i + j < nLines; j++)
         {
            if (stricmp(Lines[i] + (namelen+2), Lines[i + j] + (namelen+2)) != 0)
               break;                     /* OK to break, lines are sorted   */
            if (j == 1)                   /* First duplicate                 */
               printf("%s\n", Lines[i]);
            printf("%s\n", Lines[i + j]);
         }
         if (j > 1)                       /* Duplicates found                */
            PressAnyKey();
      }
   }
   else
   {
      fp = fopen(OutFilename, "wt");
      if (!fp)
      {
         printf("Cannot open \"%s\" for writing.\n", OutFilename);
         exit(1);
      }
      for (i = 0; i < nLines; i++)
         fprintf(fp, "%s\n", Lines[i]);
      FileSize = ftell(fp);
      fclose(fp);
      printf("\"%s\" successfully written, %ld kBytes\n", OutFilename,
             (FileSize + 1023) / 1024);
   }

   if (numlines > 1000) /* if not Borland DOS compiler then skip the mem msg. */
   {
       return (0);
   }

   farcoreleftEnd = farcoreleft();

   printf("Maximum memory usage: %ld kBytes of %ld kBytes available\n",
          (farcoreleftStart - farcoreleftEnd + 1023) / 1024,
          (farcoreleftStart + 1023) / 1024);

   return (0);
}
