/*
** main.c - Main module for DOS command-line LZA file compression / expansion
**          programs.
**
** Author: DavidDi
**
** This module is compiled twice - once for COMPRESS (COMPRESS defined) and
** once for EXPAND (COMPRESS not defined).
*/


// Headers
///////////

#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <share.h>
#include <sys\types.h>
#include <sys\stat.h>

#include "lz_common.h"
#include "lz_buffers.h"
#include "lz_header.h"

#include "args.h"
#include "main.h"
#include "messages.h"

#include <diamondc.h>
#include "mydiam.h"

// Globals
///////////

CHAR ARG_PTR *pszInFileName,     // input file name
             *pszOutFileName,    // output file name
             *pszTargetName;     // target path name

TCHAR   ErrorMsg[1024];


// Module Variables
////////////////////

#ifndef COMPRESS
static BOOL bCopyingFile;        // Is current file being copied or expanded?
#endif


// Local Prototypes
////////////////////

static VOID DisplayErrorMessage(INT fError);
static VOID MakeDestFileName(CHAR ARG_PTR *argv[], CHAR ARG_PTR *pszDest);
static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf);
static BOOL ActuallyTheSameFile(CHAR ARG_PTR *pszFile1,
                                CHAR ARG_PTR *pszFile2);
static BOOL ProcessNotification(CHAR ARG_PTR *pszSource,
                                CHAR ARG_PTR *pszDest, WORD wNotification);


/*
** static void DisplayErrorMessage(int fError);
**
** Display error message for given error condition.
**
** Arguments:  LZERROR_ code
**
** Returns:    void
**
** Globals:    none
*/
static VOID DisplayErrorMessage(INT fError)
{
   switch(fError)
   {
      case LZERROR_BADINHANDLE:
         LoadString(NULL, SID_NO_OPEN_INPUT, ErrorMsg, 1024);
         printf(ErrorMsg, pszInFileName);
         break;

      case LZERROR_BADOUTHANDLE:
         LoadString(NULL, SID_NO_OPEN_OUTPUT, ErrorMsg, 1024);
         printf(ErrorMsg, pszOutFileName);
         break;

      case LZERROR_READ:
         LoadString(NULL, SID_NO_READ_INPUT, ErrorMsg, 1024);
         printf(ErrorMsg, pszInFileName);
         break;

      case LZERROR_WRITE:
         LoadString(NULL, SID_OUT_OF_SPACE, ErrorMsg, 1024);
         printf(ErrorMsg, pszOutFileName);
         break;

      case BLANK_ERROR:
         break;

      default:
         LoadString(NULL, SID_GEN_FAILURE, ErrorMsg, 1024);
         printf(ErrorMsg, pszInFileName, pszOutFileName);
         break;
   }
}


/*
** static void MakeDestFileName(char ARG_PTR *argv[], char ARG_PTR *pszDest);
**
** Create the appropriate destination file name.
**
** Arguments:  argv    - like argument to main()
**             pszDest - pointer to destination file name buffer to be filled
**                       in
**
** Returns:    void
**
** Globals:    none
*/
static VOID MakeDestFileName(CHAR ARG_PTR *argv[], CHAR ARG_PTR *pszDest)
{
   CHAR ARG_PTR *pszDestFile;

   if (nNumFileSpecs == 2 && bTargetIsDir == FALSE && bDoRename == FALSE)
      // Compress a single input file to a single output file.  N.b., we must
      // be careful to eat up the output file name command-line argument so
      // it doesn't get processed like another input file!
      STRCPY(pszDest, argv[GetNextFileArg(argv)]);
   else if (bTargetIsDir == TRUE)
   {
      // Prepend output file name with destination directory path name.
      STRCPY(pszDest, pszTargetName);

      // Isolate source file name from source file specification.
      pszDestFile = ExtractFileName(pszInFileName);

      // Add destination file name to destination directory path
      // specification.
      MakePathName(pszDest, pszDestFile);
   }
   else
      // Destination file name same as source file name.  N.b., this is an
      // error condition if (bDoRename == FALSE).
      STRCPY(pszDest, pszInFileName);
}


/*
** static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf);
**
** Gets the canonical name for a given file specification.
**
** Arguments:  pszFileName    - file specification
**             szCanonicalBuf - buffer to be filled with canonical name
**
** Returns:    TRUE if successful.  FALSE if unsuccessful.
**
** N.b., szCanonicalBuf must be at least 128 bytes long.  The contents of
** szCanonicalBuf are only defined if the funstion returns TRUE.
**
*/
static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf)
{
   BOOL bRetVal = FALSE;
   LPSTR lpszLastComp;

   return((BOOL) GetFullPathName(lpszFileName, MAX_PATH, lpszCanonicalBuf,  &lpszLastComp));
}


/*
** static BOOL ActuallyTheSameFile(char ARG_PTR *pszFile1,
**                                 char ARG_PTR *pszFile2);
**
** Checks to see if two file specifications point to the same physical file.
**
** Arguments:  pszFile1 - first file specification
**             pszFile2 - second file specification
**
** Returns:    BOOL - TRUE if the file specifications point to the same
**                    physical file.  FALSE if not.
**
** Globals:    none
*/
static BOOL ActuallyTheSameFile(CHAR ARG_PTR *pszFile1,
                                CHAR ARG_PTR *pszFile2)
{
   CHAR szCanonicalName1[MAX_PATH],
        szCanonicalName2[MAX_PATH];

   if (GetCanonicalName(pszFile1, szCanonicalName1) &&
       GetCanonicalName(pszFile2, szCanonicalName2))
   {
      if (! lstrcmpiA(szCanonicalName1, szCanonicalName2))
         return(TRUE);
   }

   return(FALSE);
}


/*
** static BOOL ProcessNotification(char ARG_PTR *pszSource,
**                                 char ARG_PTR *pszDest,
**                                 WORD wNotification);
**
** Callback function during file processing.
**
** Arguments:  pszSource     - source file name
**             pszDest       - destination file name
**             wNotification - process type query
**
** Returns:    BOOL - (wNotification == NOTIFY_START_*):
**                         TRUE if the source file should be "processed" into
**                         the destination file.  FALSE if not.
**                    else
**                         TRUE.
**
** Globals:    none
*/
static BOOL ProcessNotification(CHAR ARG_PTR *pszSource,
                                CHAR ARG_PTR *pszDest, WORD wNotification)
{
   switch(wNotification)
   {
      case NOTIFY_START_COMPRESS:
      {
         // Fail if the source and destination files are identical.
         if (ActuallyTheSameFile(pszSource, pszDest))
         {
            LoadString(NULL, SID_COLLISION, ErrorMsg, 1024);
            printf(ErrorMsg, pszSource);
            return(FALSE);
         }

         // Display start message.
         switch (byteAlgorithm)
         {
         case LZX_ALG:
             LoadString(
                NULL,
                SID_COMPRESSING_LZX,
                ErrorMsg,
                1024
                );
             printf(ErrorMsg, pszSource, pszDest,
                        CompressionMemoryFromTCOMP(DiamondCompressionType)
                        );
             break;

         case QUANTUM_ALG:
             LoadString(
                NULL,
                SID_COMPRESSING_QUANTUM,
                ErrorMsg,
                1024
                );
             printf(ErrorMsg, pszSource, pszDest,
                        CompressionLevelFromTCOMP(DiamondCompressionType),
                        CompressionMemoryFromTCOMP(DiamondCompressionType)
                        );
             break;

         default:
             LoadString(
                NULL,
                (byteAlgorithm == MSZIP_ALG) ? SID_COMPRESSING_MSZIP : SID_COMPRESSING,
                ErrorMsg,
                1024
                );
             printf(ErrorMsg, pszSource, pszDest);
         }
      }
         break;

      default:
         break;
   }

   return(TRUE);
}


//
//  static BOOL FileTimeIsNewer( const char* pszFile1, const char* pszFile2 );
//
//  Return value is TRUE if time stamp on pszFile1 is newer than the
//  time stamp on pszFile2.  If either of the two files do not exist,
//  the return value is also TRUE (for indicating that pszFile2 should
//  be update from pszFile1).  Otherwise, the return value is FALSE.
//

static BOOL FileTimeIsNewer( const char* pszFile1, const char* pszFile2 ) {

    struct _stat StatBufSource,
                 StatBufDest;

    if (( _stat( pszFile2, &StatBufDest   )) ||
        ( _stat( pszFile1, &StatBufSource )) ||
        ( StatBufSource.st_mtime > StatBufDest.st_mtime ))
        return TRUE;

    return FALSE;

    }


LPSTR
ValidListEntry(
    LPSTR szArg
    )
{
    // Check for special character at front of file
    if ( '@' == szArg[0] )
        return szArg + 1;
    else
        return NULL;
}

BOOL
GetNextFileListFile(
    const LPSTR szFileList,
    char **pszSource,
    char **pszDest
    )
{
    static char szList[MAX_PATH] = {0},
                szSource[MAX_PATH] = {0},
                szDest[MAX_PATH] = {0};
    static BOOL bParsingFile = FALSE;
    static FILE *hFile;
    static int  dEntryNum = 1;
    int dRetVal;

    // Initialize out paramters to NULL
    *pszSource = *pszDest = NULL;

    // Open file if we are not currently parsing another one
    if ( !bParsingFile ) {
        // Do not reopen last file used as this is our signal to stop
        if ( !_stricmp( szFileList, szList ) ) {
            return TRUE;
        }

        // Attempt to open specified file
        hFile = fopen( szFileList, "rt" );
        if ( NULL == hFile ) {
            LoadString( NULL, SID_NO_OPEN_INPUT, ErrorMsg, 1024 );
            printf( ErrorMsg, szFileList );
            return FALSE;
        }

        // Store new file name in static buffer
        strcpy( szList, szFileList );

        bParsingFile = TRUE;
    }

    dRetVal = fscanf( hFile, "%s %s", szSource, szDest );
    if ( EOF == dRetVal ) {
        fclose( hFile );
        bParsingFile = FALSE;
        return TRUE;
    }
    else if ( 0 == dRetVal ) {
        LoadString( NULL, SID_INVALID_LIST_FILE, ErrorMsg, 1024 );
        printf( ErrorMsg, dEntryNum );
        return FALSE;
    }

    // Point to new source and destination entries
    *pszSource = szSource;
    *pszDest = szDest;
    // Track entry
    dEntryNum++;

    return TRUE;
}
/*
** int main(int argc, char *argv[]);
**
** Run command-line file compression program.
**
** Arguments:  figure it out
**
** Returns:    int - EXIT_SUCCESS if compression finished successfully,
**                   EXIT_FAILURE if not.
**
** Globals:    none
*/
INT __cdecl main(INT argc, CHAR *argv[])
{
   INT iSourceFileName,
       fError,
       nTotalFiles = 0,
       nReturnCode = EXIT_SUCCESS;
   CHAR ARG_PTR pszDestFileName[MAX_PATH];
   CHAR chTargetFileName[ MAX_PATH ];
   LONG cblTotInSize = 0L,
        cblTotOutSize = 0L;

   PLZINFO pLZI;

   USHORT wLanguageId = LANGIDFROMLCID(GetThreadLocale());

   if ((LANG_JAPANESE == PRIMARYLANGID(wLanguageId)) ||
       (LANG_KOREAN   == PRIMARYLANGID(wLanguageId)) ||
       (LANG_CHINESE  == PRIMARYLANGID(wLanguageId)))
   {
      //
      // This used to be #ifdef DBCS. Now a runtime check.
      //
      DWORD dw = GetConsoleOutputCP();

      switch (dw) {
          case 932:
          case 936:
          case 949:
          case 950:
             SetThreadLocale(MAKELCID(
                                MAKELANGID(
                                   PRIMARYLANGID(GetSystemDefaultLangID()),
                                   SUBLANG_ENGLISH_US),
                                SORT_DEFAULT));
             break;
          default:
             SetThreadLocale(MAKELCID(
                                MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
                                SORT_DEFAULT ) );
             break;
      }
   }

   // Parse command-line arguments.
   if (ParseArguments(argc, argv) != TRUE)
      return(EXIT_FAILURE);

   // Display sign-on banner.
   if ( bNoLogo == FALSE ) {
     LoadString(NULL, SID_BANNER_TEXT, ErrorMsg, 1024);
     printf(ErrorMsg);
   }

   // Set up global target path name.
   pszTargetName = argv[iTarget];

   if (bDisplayHelp == TRUE)
   {
      // User asked for help.
      LoadString(NULL, SID_INSTRUCTIONS, ErrorMsg, 1024);
      printf(ErrorMsg);
      LoadString(NULL, SID_INSTRUCTIONS2, ErrorMsg, 1024);
      printf(ErrorMsg);
      LoadString(NULL, SID_INSTRUCTIONS3, ErrorMsg, 1024);
      printf(ErrorMsg);
      return(EXIT_SUCCESS);
   }

   // Check for command line problems.
   if (CheckArguments() == FALSE)
      return(EXIT_FAILURE);

   // Set up ring buffer and I/O buffers.
   pLZI = InitGlobalBuffersEx();
   if (!pLZI)
   {
      LoadString(NULL, SID_INSUFF_MEM, ErrorMsg, 1024);
      printf(ErrorMsg);
      return(EXIT_FAILURE);
   }

   // Process each source file.
   while ((iSourceFileName = GetNextFileArg(argv)) != FAIL)
   {
      char *pszFileList = NULL,
           *pszCurFile,
           *pszCurDestFile;

      // Determine if this is a directive file
      if ( pszFileList = ValidListEntry( argv[iSourceFileName] ) ) {
          if ( !GetNextFileListFile( pszFileList, &pszCurFile, &pszCurDestFile ) ) {
              return (EXIT_FAILURE);
          }
          
          // Handle empty directive lists
          if ( NULL == pszCurFile ) continue;
      }
      // Otherwise use current argument as file to compress
      else {
           pszCurFile = argv[iSourceFileName];
      }
      
      do {
          // Set up global input file name.
          pszInFileName = CharLowerA(pszCurFile);

          // Set up global output file name.
          if ( NULL == pszFileList ) {
              MakeDestFileName(argv, pszDestFileName);
              pszOutFileName = CharLowerA(pszDestFileName);
          }
          else {
              pszOutFileName = CharLowerA(pszCurDestFile);
          }

          strcpy( chTargetFileName, pszOutFileName );

          if ( bDoRename )
              MakeCompressedName( chTargetFileName );

          if (( ! bUpdateOnly ) ||
              ( FileTimeIsNewer( pszInFileName, chTargetFileName ))) {

              if(DiamondCompressionType) {
                 fError = DiamondCompressFile(ProcessNotification,pszInFileName,
                                                pszOutFileName,bDoRename,pLZI);
              } else {
                 fError = Compress(ProcessNotification, pszInFileName,
                                     pszOutFileName, byteAlgorithm, bDoRename, pLZI);
              }

              if(fError != TRUE)
                 // Deal with returned error codes.
                 DisplayErrorMessage(nReturnCode = fError);
              else
              {
                 nTotalFiles++;

                 if (pLZI && pLZI->cblInSize && pLZI->cblOutSize) {

                    // Keep track of cumulative statistics.
                    cblTotInSize += pLZI->cblInSize;
                    cblTotOutSize += pLZI->cblOutSize;

                    // Display report for each file.
                    LoadString(NULL, SID_FILE_REPORT, ErrorMsg, 1024);
                    printf(ErrorMsg, pszInFileName, pLZI->cblInSize, pLZI->cblOutSize,
                       (INT)(100 - ((100 * (LONGLONG) pLZI->cblOutSize) / pLZI->cblInSize)));

                 }
                 else {
                    LoadString(NULL, SID_EMPTY_FILE_REPORT, ErrorMsg, 1024);
                    printf(ErrorMsg, pszInFileName, 0, 0);
                 }

              }
              // Separate individual file processing message blocks by a blank line.
              printf("\n");
          }

          // If we are processing a directive file, get the next arguments
          if ( NULL != pszFileList ) {
              if ( !GetNextFileListFile( pszFileList, &pszCurFile, &pszCurDestFile ) ) {
                  return (EXIT_FAILURE);
              }
          }

       } while ( NULL != pszFileList && NULL != pszCurFile );
   }

   // Free memory used by ring buffer and I/O buffers.
   FreeGlobalBuffers(pLZI);

   // Display cumulative report for multiple files.
   if (nTotalFiles > 1) {

      // Scale results to get accurate %
      LONG cblAdjInSize = cblTotInSize,
           cblAdjOutSize = cblTotOutSize;
      while (cblAdjInSize > 100000) {
        cblAdjInSize /= 2;
        cblAdjOutSize /= 2;
        }
      cblAdjOutSize += (cblAdjInSize / 200);    // round off (+0.5%)
      if (cblAdjOutSize < 0) {
        cblAdjOutSize = 0;
        }

      LoadString(NULL, SID_TOTAL_REPORT, ErrorMsg, 1024);
      printf(ErrorMsg, nTotalFiles, cblTotInSize, cblTotOutSize,
             (INT)(100 - 100 * cblAdjOutSize / cblAdjInSize));
   }

   return(nReturnCode);
}