/* fileview.c - File viewing door that demonstrates the use of the    */
/*              PagedViewer() function. This door can be setup to     */
/*              to display a single text file, or any file from an    */
/*              entire directory of text files. The program accepts   */
/*              a single command-line argument, which if present,     */
/*              specifies the filename or wildcard of the files to    */
/*              display. If this argument is not present, all files   */
/*              in the current directory will be available for        */
/*              viewing. If there is more than one possible file to   */
/*              be displayed, this program will display a list of     */
/*              files that the user can choose from to display. If    */
/*              there is only one possible file, that file is         */
/*              is displayed. This program uses PagedViewer() for two */
/*              seperate uses - the list of available files, and for  */
/*              viewing the file itself.                              */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "bpfind.h"
#include "opendoor.h"
#include "pageview.h"


/* Configurable constants. */
#define FILENAME_SIZE        75
#define PATH_CHARS           (FILENAME_SIZE - 13)
#define LINE_SIZE            80
#define ARRAY_GROW_SIZE      20


/* Global variables. */
int nTotalFiles = 0;
int nFileArraySize = 0;
char *paszFileArray = NULL;

FILE *pfCurrentFile;
int nTotalLines = 0;
int nLineArraySize = 0;
long *palLineOffset = NULL;


/* Function prototypes. */
void AddFilesMatching(char *pszFileSpec);
char *GetFilename(int nIndex);
int AddFilename(char *pszFilename);
void GetDirOnly(char *pszOutDirName, const char *pszInPathName);
int DirExists(const char *pszDirName);
void BuildPath(char *pszOut, char *pszPath, char *pszFilename);
void FreeFileList(void);
void DisplayFileName(int nLine, void *pCallbackData);
void DisplayFile(char *pszFilename);
void DisplayFileLine(int nLine, void *pCallbackData);
int AddOffsetToArray(long lOffset);
void FreeLineArray(void);


/* Program execution begins here. */
int main(int nArgCount, char *papszArgument[])
{
   int nArg;
   int nChoice;

   od_init();

   /* Get file specifiction from command-line, if any. */
   if(nArgCount >= 2)
   {
      for(nArg = 1; nArg < nArgCount; ++nArg)
      {
         AddFilesMatching(papszArgument[nArg]);
      }
   }

   /* If there are no command-line parameters, use *.* */
   else
   {
      AddFilesMatching("*.*");
   }

   /* If there are no matching files, display error. */
   if(nTotalFiles == 0)
   {
      od_printf("No files were found.\n\r\n\r");
      od_printf("Press [Enter] to continue.\n\r");
      od_get_answer("\n\r");
      return(0);
   }

   /* If only one file was found, then display it. */
   else if(nTotalFiles == 1)
   {
      DisplayFile(GetFilename(0));
   }

   /* If more than one file was found, allow user to choose file */
   /* to display.                                                */
   else
   {
      /* Loop until user chooses to quit. */
      nChoice = 0;
      for(;;)
      {
         /* Get user's selection. */
         nChoice = PagedViewer(nChoice, nTotalFiles, DisplayFileName,
            NULL, TRUE, "Choose A File To Display", 19);

         /* If user chose to quit, then exit door. */
         if(nChoice == NO_LINE) break;

         /* Otherwise, display the file that the user chose. */
         DisplayFile(GetFilename(nChoice));
      }
   }

   FreeFileList();

   return(0);
}


void AddFilesMatching(char *pszFileSpec)
{
   struct ffblk DirEntry;
   int bNoMoreFiles;
   char szDirName[PATH_CHARS + 1];
   char szFileName[FILENAME_SIZE];

   /* Check that file specification is not too long. */
   if(strlen(pszFileSpec) > PATH_CHARS)
   {
      return;
   }

   /* Get directory name from path. */
   GetDirOnly(szDirName, pszFileSpec);

   bNoMoreFiles = findfirst(pszFileSpec, &DirEntry, FA_RDONLY);
   while(!bNoMoreFiles)
   {
      BuildPath(szFileName, szDirName, DirEntry.ff_name);

      AddFilename(szFileName);

      bNoMoreFiles = findnext(&DirEntry);
   }
}


void GetDirOnly(char *pszOutDirName, const char *pszInPathName)
{
   char *pchBackslashChar;

   /* Default dir name is entire path. */
   strcpy(pszOutDirName, pszInPathName);

   /* If there is a backslash in the string. */
   pchBackslashChar = strrchr(pszOutDirName, '\\');
   if(pchBackslashChar != NULL)
   {
      /* Remove all character beginning at last backslash from path. */
      *pchBackslashChar = '\0';
   }
   else
   {
      /* If there is no backslash in the filename, then the dir name */
      /* is empty.                                                   */
      pszOutDirName[0] = '\0';
   }
}


void BuildPath(char *pszOut, char *pszPath, char *pszFilename)
{
   /* Copy path to output filename. */
   strcpy(pszOut, pszPath);

   /* Ensure there is a trailing backslash. */
   if(strlen(pszOut) > 0 && pszOut[strlen(pszOut) - 1] != '\\')
   {
      strcat(pszOut, "\\");
   }

   /* Append base filename. */
   strcat(pszOut, pszFilename);
}


char *GetFilename(int nIndex)
{
   return(paszFileArray + (nIndex * FILENAME_SIZE));
}


int AddFilename(char *pszFilename)
{
   int nNewArraySize;
   char *paszNewArray;
   char *pszNewString;

   /* If array is full, then try to grow it. */
   if(nTotalFiles == nFileArraySize)
   {
      nNewArraySize = nFileArraySize + ARRAY_GROW_SIZE;
      if((paszNewArray =
         realloc(paszFileArray, nNewArraySize * FILENAME_SIZE)) == NULL)
      {
         return(FALSE);
      }
      nFileArraySize = nNewArraySize;
      paszFileArray = paszNewArray;
   }

   /* Get address to place new string at, while incrementing total number */
   /* of filenames.                                                       */
   pszNewString = GetFilename(nTotalFiles++);

   /* Copy up to the maximum number of filename characters to the string. */
   strncpy(pszNewString, pszFilename, FILENAME_SIZE - 1);
   pszNewString[FILENAME_SIZE - 1] = '\0';

   return(TRUE);
}


void FreeFileList(void)
{
   if(nFileArraySize > 0)
   {
      free(paszFileArray);
      nFileArraySize = 0;
      nTotalFiles = 0;
      paszFileArray = NULL;
   }
}


void DisplayFileName(int nLine, void *pCallbackData)
{
   (void)pCallbackData;

   od_printf(GetFilename(nLine));
}


void DisplayFile(char *pszFilename)
{
   char szLine[LINE_SIZE];
   long lnOffset;

   /* Clear the screen. */
   od_clr_scr();

   /* Attempt to open the file. */
   pfCurrentFile = fopen(pszFilename, "r");
   if(pfCurrentFile == NULL)
   {
      od_printf("Unable to open file.\n\r\n\r");
      od_printf("Press [Enter] to continue.\n\r");
      od_get_answer("\n\r");
      return;
   }

   /* Get file offsets of each line and total line count from file. */
   for(;;)
   {
      lnOffset = fTell(pfCurrentFile);

      if(fgets(szLine, LINE_SIZE, pfCurrentFile) == NULL) break;

      AddOffsetToArray(lnOffset);
   }

   /* Use PagedViewer() to view the file. */
   PagedViewer(0, nTotalLines, DisplayFileLine, NULL, FALSE, NULL, 21);

   /* Deallocate array of line offsets. */
   FreeLineArray();

   /* Close the file. */
   fclose(pfCurrentFile);
}


void DisplayFileLine(int nLine, void *pCallbackData)
{
   char szLine[LINE_SIZE];
   long lnTargetOffset = palLineOffset[nLine];
   int nLineLen;

   (void)pCallbackData;

   /* Move to proper offset in file. */
   if(lnTargetOffset != ftell(pfCurrentFile))
   {
      fseek(pfCurrentFile, lnTargetOffset, SEEK_SET);
   }

   /* Get line from line. */
   if(fgets(szLine, LINE_SIZE, pfCurrentFile) != NULL)
   {
      /* Remote any trailing CR/LF sequence from line. */
      nLineLen = strlen(szLine);
      while(nLineLen > 0
         && (szLine[nLineLen - 1] == '\r' || szLine[nLineLen - 1] == '\n'))
      {
         szLine[--nLineLen] = '\0';
      }

      /* Display the line on the screen. */
      od_disp_str(szLine);
   }
}


int AddOffsetToArray(long lOffset)
{
   long *palNewArray;
   int nNewArraySize;

   /* If array is full, then grow it. */
   if(nTotalLines == nLineArraySize)
   {
      nNewArraySize = nLineArraySize + ARRAY_GROW_SIZE;

      if((palNewArray =
         realloc(palLineOffset, nNewArraySize * sizeof(long))) == NULL)
      {
         return(FALSE);
      }

      nLineArraySize = nNewArraySize;
      palLineOffset = palNewArray;
   }

   palLineOffset[nTotalLines++] = lOffset;

   return(TRUE);
}


void FreeLineArray(void)
{
   if(nLineArraySize > 0)
   {
      nTotalLines = 0;
      nLineArraySize = 0;
      free(palLineOffset);
      palLineOffset = NULL;
   }
}