/* OpenDoors Online Software Programming Toolkit
 * (C) Copyright 1991 - 1999 by Brian Pirie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *        File: ODList.c
 *
 * Description: Implements the od_list_files() function for displaying
 *              a FILES.BBS file.
 *
 *   Revisions: Date          Ver   Who  Change
 *              ---------------------------------------------------------------
 *              Oct 13, 1994  6.00  BP   New file header format.
 *              Oct 21, 1994  6.00  BP   Further isolated com routines.
 *              Dec 09, 1994  6.00  BP   Use new directory access functions.
 *              Dec 31, 1994  6.00  BP   Remove #ifndef USEINLINE DOS code.
 *              Aug 19, 1995  6.00  BP   32-bit portability.
 *              Nov 11, 1995  6.00  BP   Moved functions from odcore.c
 *              Nov 11, 1995  6.00  BP   Removed register keyword.
 *              Nov 16, 1995  6.00  BP   Removed oddoor.h, added odcore.h.
 *              Dec 12, 1995  6.00  BP   Added entry, exit and kernel macros.
 *              Dec 30, 1995  6.00  BP   Added ODCALL for calling convention.
 *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
 *              Mar 03, 1996  6.10  BP   Begin version 6.10.
 *              Aug 10, 2003  6.23  SH   *nix support
 */

#define BUILDING_OPENDOORS

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "OpenDoor.h"
#include "ODCore.h"
#include "ODGen.h"
#include "ODCom.h"
#include "ODPlat.h"
#include "ODKrnl.h"
#include "ODUtil.h"


/* Filename component identifies. */
#define WILDCARDS 0x01
#define EXTENSION 0x02
#define FILENAME  0x04
#define DIRECTORY 0x08
#define DRIVE     0x10


/* Local private helper function prototypes. */
static void ODListFilenameMerge(char *pszEntirePath, const char *pszDrive,
   const char *pszDir, const char *pszName, const char *pszExtension);
static char *ODListGetFirstWord(char *pszInStr, char *pszOutStr);
static char *ODListGetRemainingWords(char *pszInStr);
static INT ODListFilenameSplit(const char *pszEntirePath, char *pszDrive,
   char *pszDir, char *pszName, char *pszExtension);


/* ----------------------------------------------------------------------------
 * od_list_files()
 *
 * Displays a list of files available for download, using an extended version
 * of the standard FILES.BBS format index file.
 *
 * Parameters: pszFileSpec - Directory name where the FILES.BBS file can be
 *                           found, or full path and filename of a FILES.BBS
 *                           format index file.
 *
 *     Return: TRUE on success or FALSE on failure.
 */
ODAPIDEF BOOL ODCALL od_list_files(char *pszFileSpec)
{
   BYTE btLineCount = 2;
   BOOL bPausing;
   static char szLine[513];
   static char szFilename[80];
   static char szDrive[3];
   static char szDir[70];
   static char szTemp1[9];
   static char szTemp2[5];
   static char szBaseName[9];
   static char szExtension[5];
   static char szDirectory[100];
   INT nFilenameInfo;
   FILE *pfFilesBBS;
   static char *pszCurrent;
   BOOL bIsDir;
   BOOL bUseNextLine = TRUE;
   tODDirHandle hDir;
   tODDirEntry DirEntry;

   /* Log function entry if running in trace mode. */
   TRACE(TRACE_API, "od_list_files()");

   /* Initialize OpenDoors if it hasn't already been done. */
   if(!bODInitialized) od_init();

   OD_API_ENTRY();

   /* Check user's page pausing setting. */
   bPausing = od_control.od_page_pausing;

   if(od_control.od_extended_info) bPausing = od_control.user_attribute & 0x04;

   /* Parse directory parameter. */
   if(pszFileSpec == NULL)
   {
      strcpy(szODWorkString, ".");
      strcpy(szDirectory, "."DIRSEP_STR);
   }
   else if(*pszFileSpec == '\0')
   {
      strcpy(szODWorkString, ".");
      strcpy(szDirectory, "."DIRSEP_STR);
   }
   else
   {
      strcpy(szODWorkString, pszFileSpec);
      strcpy(szDirectory, pszFileSpec);
      if(szODWorkString[strlen(szODWorkString) - 1] == DIRSEP)
      {
         szODWorkString[strlen(szODWorkString) - 1] = '\0';
      }
   }

   /* Get directory information on path. */
   if(ODDirOpen(szODWorkString, DIR_ATTRIB_ARCH | DIR_ATTRIB_RDONLY
      | DIR_ATTRIB_DIREC, &hDir) != kODRCSuccess)
   {
      od_control.od_error = ERR_FILEOPEN;
      OD_API_EXIT();
      return(FALSE);
   }

   if(ODDirRead(hDir, &DirEntry) != kODRCSuccess)
   {
      ODDirClose(hDir);
      od_control.od_error = ERR_FILEOPEN;
      OD_API_EXIT();
      return(FALSE);
   }

   ODDirClose(hDir);

   /* If it is a directory. */
   if(DirEntry.wAttributes & DIR_ATTRIB_DIREC)
   {
      /* Append FILES.BBS to directory name & open. */
      bIsDir = TRUE;
      ODMakeFilename(szODWorkString, szODWorkString, "FILES.BBS",
         sizeof(szODWorkString));
      if((pfFilesBBS = fopen(szODWorkString, "r")) == NULL)
      {
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }
   }

   /* If it is not a directory. */
   else
   {
      bIsDir = FALSE;
      if((pfFilesBBS = fopen(szODWorkString,"r")) == NULL)
      {
         od_control.od_error = ERR_FILEOPEN;
         OD_API_EXIT();
         return(FALSE);
      }
   }


   /* Ignore previously pressed control keys. */
   chLastControlKey = 0;


   /* Loop until the end of the FILES.BBS file has been reached. */
   for(;;)
   {
      if(fgets(szLine, 512, pfFilesBBS) == NULL) break;

      if(!bUseNextLine)
      {
         if(szLine[strlen(szLine) - 1] == '\n')
         {
            bUseNextLine = TRUE;
         }
         continue;
      }

      if(szLine[strlen(szLine) - 1] == '\n')
      {
         szLine[strlen(szLine) - 1] = '\0';
      }
      else
      {
         bUseNextLine = FALSE;
      }
      if(szLine[strlen(szLine) - 1] == '\r')
      {
         szLine[strlen(szLine) - 1] = '\0';
      }

      if(chLastControlKey != 0)
      {
         switch(chLastControlKey)
         {
            case 's':
               if(od_control.od_list_stop)
               {
                  if(od_control.baud)
                  {
                     ODComClearOutbound(hSerialPort);
                  }
                  od_clear_keybuffer();
                  fclose(pfFilesBBS);
                  OD_API_EXIT();
                  return(TRUE);
               }
               break;

            case 'p':
               if(od_control.od_list_pause)
               {
                  od_clear_keybuffer();
                  od_get_key(TRUE);
               }
         }
         chLastControlKey = 0;
      }

      /* Determine whether or not this is a comment line. */
      if(szLine[0] == ' ' || strlen(szLine) == 0)

      {
         /* If so, display the line in comment color. */
         od_set_attrib(od_control.od_list_title_col);
         od_disp_str(szLine);
         od_disp_str("\n\r");
         ++btLineCount;
      }

      /* If the line is not a comment. */
      else                             
      {
         /* Extract the first word of the line, */
         ODListGetFirstWord(szLine, szFilename);

         /* And extract the filename. */
         nFilenameInfo = ODListFilenameSplit(szFilename, szDrive, szDir,
            szBaseName, szExtension);
         if(!((nFilenameInfo & DRIVE) || (nFilenameInfo & DIRECTORY)))
         {
            if(bIsDir)
            {
               ODMakeFilename(szDirectory, szDirectory, szFilename,
                  sizeof(szDirectory));
               strcpy(szFilename, szDirectory);
            }
            else
            {
               ODListFilenameSplit(szDirectory, szDrive, szDir, szTemp1,
                  szTemp2);
               ODListFilenameMerge(szFilename, szDrive, szDir, szBaseName,
                  szExtension);
            }
         }

         /* Search for the filespec in directory. */
         if(ODDirOpen(szFilename, DIR_ATTRIB_ARCH | DIR_ATTRIB_RDONLY, &hDir)
            == kODRCSuccess)
         {
            /* Display information on every file that matches. */
            while(ODDirRead(hDir, &DirEntry) == kODRCSuccess)
            {
               od_set_attrib(od_control.od_list_name_col);
               od_printf("%-12.12s  ", DirEntry.szFileName);
               od_set_attrib(od_control.od_list_size_col);
               od_printf("%-6ld   ", DirEntry.dwFileSize);
               od_set_attrib(od_control.od_list_comment_col);
               pszCurrent = ODListGetRemainingWords(szLine);
               if(strlen(pszCurrent) <= 56)
               {
                  od_disp_str(pszCurrent);
                  od_disp_str("\n\r");
               }
               else
               {
                  od_printf("%-56.56s\n\r", pszCurrent);
               }
               ++btLineCount;
            }

            ODDirClose(hDir);
         }

         /* Otherwise, indicate that the file is "Offline". */
         else
         {
            ODListFilenameMerge(szFilename, "", "", szBaseName, szExtension);
            od_set_attrib(od_control.od_list_name_col);
            od_printf("%-12.12s ", szFilename);
            od_set_attrib(od_control.od_list_offline_col);
            od_disp_str(od_control.od_offline);
            od_set_attrib(od_control.od_list_comment_col);

            od_printf("%-56.56s\n\r", ODListGetRemainingWords(szLine));
            ++btLineCount;
         }
      }

      /* Check for end of screen & page pausing. */
      if(btLineCount >= od_control.user_screen_length && bPausing)
      {
         /* Provide page pausing at end of each screen. */
         if(ODPagePrompt(&bPausing))
         {
            fclose(pfFilesBBS);
            OD_API_EXIT();
            return(TRUE);
         }

         /* Reset the line number counter. */
         btLineCount = 2;
      }
   }

   /* When finished, close the file. */
   fclose(pfFilesBBS);

   /*  Return with success. */
   OD_API_EXIT();
   return(TRUE);
}


/* ----------------------------------------------------------------------------
 * ODListFilenameMerge()                               *** PRIVATE FUNCTION ***
 *
 * Builds a fully-qualified path name from the provided path component
 * strings.
 *
 * Parameters: pszEntirePath - Pointer to the destination string where the
 *                             generated path should be stored.
 *
 *             pszDrive      - Pointer to the drive string.
 *
 *             pszDir        - Pointer to the directory string.
 *
 *             pszName       - Pointer to the base filename string.
 *
 *             pszExtension  - Pointer to the extension name string.
 *
 *     Return: void
 */
static void ODListFilenameMerge(char *pszEntirePath, const char *pszDrive,
   const char *pszDir, const char *pszName, const char *pszExtension)
{
   if(pszEntirePath == NULL) return;

   pszEntirePath[0] = '\0';

   if(pszDrive != NULL)
   {
      strcpy(pszEntirePath, pszDrive);
   }
   if(pszDir != NULL)
   {
      strcat(pszEntirePath, pszDir);
   }
   if(pszName != NULL)
   {
      strcat(pszEntirePath,pszName);
   }
   if(pszExtension != NULL)
   {
      strcat(pszEntirePath,pszExtension);
   }
}


/* ----------------------------------------------------------------------------
 * ODListFilenameSplit()                               *** PRIVATE FUNCTION ***
 *
 * Splits the provided path string into drive, directory name, file base name
 * and file extension components.
 *
 * Parameters: pszEntirePath - A string containing the path to split.
 *
 *             pszDrive      - A string where the drive letter should be stored.
 *
 *             pszDir        - A string where the directory name should be
 *                             stored.
 *
 *             pszName       - A string where the base filename should be
 *                             stored.
 *
 *             pszExtension  - A string where the filename extension should be
 *                             stored.
 *
 *     Return: One or more flags indicating which components where found in the
 *             provided path name.
 */
static INT ODListFilenameSplit(const char *pszEntirePath, char *pszDrive,
   char *pszDir, char *pszName, char *pszExtension)
{
   char *pchCurrentPos;
   char *pchStart;
   BYTE btSize;
   INT nToReturn;

   ASSERT(pszEntirePath != NULL);
   ASSERT(pszDrive != NULL);
   ASSERT(pszDir != NULL);
   ASSERT(pszName != NULL);
   ASSERT(pszExtension != NULL);

   pchStart = (char *)pszEntirePath;
   nToReturn = 0;

   if((pchCurrentPos = strrchr(pchStart,':')) == NULL)
   {
      pszDrive[0] = '\0';
   }
   else
   {
      btSize = (int)(pchCurrentPos - pchStart) + 1;
      if(btSize > 2) btSize = 2;
      strncpy(pszDrive, pchStart, btSize);
      pszDrive[btSize] = '\0';
      pchStart = pchCurrentPos + 1;
      nToReturn |= DRIVE;
   }

   if((pchCurrentPos = strrchr(pchStart, DIRSEP))==NULL)
   {
      pszDir[0] = '\0';
   }
   else
   {
      btSize = (int)(pchCurrentPos - pchStart) + 1;
      strncpy(pszDir,pchStart,btSize);
      pszDir[btSize] = '\0';
      pchStart = pchCurrentPos + 1;
      nToReturn |= DIRECTORY;
   }

   if(strchr(pchStart,'*') != NULL || strchr(pchStart, '?') != NULL)
   {
      nToReturn |= WILDCARDS;
   }

   if((pchCurrentPos = strrchr(pchStart, '.')) == NULL)
   {
      if(pchStart =='\0')
      {
         pszExtension[0] = '\0';
         pszName[0] = '\0';
      }
      else
      {
         pszExtension[0] = '\0';
         btSize = strlen(pchStart);
         if (btSize > 8) btSize = 0;
         strncpy(pszName, pchStart, btSize);
         pszName[btSize] = '\0';
         nToReturn |= FILENAME;
      }
   }
   else
   {
      nToReturn |= FILENAME;
      nToReturn |= EXTENSION;

      btSize = (int)(pchCurrentPos - pchStart);

      if(btSize > 8) btSize = 8;

      strncpy(pszName, pchStart, btSize);
      pszName[btSize] = '\0';

      btSize = strlen(pchCurrentPos);
      if(btSize > 4) btSize = 4;
      strncpy(pszExtension, pchCurrentPos, btSize);
      pszExtension[btSize]='\0';
   }

   return(nToReturn);
}


/* ----------------------------------------------------------------------------
 * ODListGetFirstWord()                                *** PRIVATE FUNCTION ***
 *
 * Returns the first word in a string containing a series of words separated by
 * one or more spaced.
 *
 * Parameters: pszInStr  - String to look in.
 *
 *             pszOutStr - Buffer to store result in. This buffer should be at
 *                         least as long as the pszInStr string.
 *
 *     Return: Pointer to the pszOutStr that was passed in.
 */
static char *ODListGetFirstWord(char *pszInStr, char *pszOutStr)
{
   char *pchOut = (char *)pszOutStr;

   ASSERT(pszInStr != NULL);
   ASSERT(pszOutStr != NULL);

   while(*pszInStr && *pszInStr != ' ')
   {
      *pchOut++ = *pszInStr++;
   }
   *pchOut = '\0';

   return(pszOutStr);
}


/* ----------------------------------------------------------------------------
 * ODListGetRemainingWords()                           *** PRIVATE FUNCTION ***
 *
 * Obtains the remaining words in a string, after the first word. This function
 * is a companion to ODListGetFirstWord(), which obtains just the first word
 * in a string of many words.
 *
 * Parameters: pszInStr  - String to look at.
 *
 *     Return: A pointer to the position in a string of the second word.
 */
static char *ODListGetRemainingWords(char *pszInStr)
{
   char *pchStartOfRemaining = (char *)pszInStr;

   /* Skip over the first word in the string. */
   while(*pchStartOfRemaining && *pchStartOfRemaining != ' ')
   {
      ++pchStartOfRemaining;
   }

   /* Skip over any spaces after the first word. */
   while(*pchStartOfRemaining && *pchStartOfRemaining == ' ')
   {
      ++pchStartOfRemaining;
   }

   /* Return pointer to the rest of the string. */
   return((char *)pchStartOfRemaining);
 }