/* 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: ODUtil.c
 *
 * Description: Implements the non-platform specific utility functions that
 *              are defined in odutil.h. Platform specific utility functions
 *              are implemented in odplat.c.
 *
 *
 *   Revisions: Date          Ver   Who  Change
 *              ---------------------------------------------------------------
 *              Nov 01, 1994  6.00  BP   Created.
 *              Dec 31, 1994  6.00  BP   Added ODMakeFilename().
 *              Nov 13, 1995  6.00  BP   32-bit portability.
 *              Nov 23, 1995  6.00  BP   Added ODDWordDivide().
 *              Nov 23, 1995  6.00  BP   Added ODDStringHasTail().
 *              Nov 23, 1995  6.00  BP   Added ODFileSize().
 *              Nov 24, 1995  6.00  BP   ODMakeFilename(): handle empty path.
 *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
 *              Mar 03, 1996  6.10  BP   Begin version 6.10.
 *              Mar 06, 1996  6.10  BP   Added ODDWordMultiply().
 *              Aug 10, 2003  6.23  SH   *nix support
 */

#define BUILDING_OPENDOORS

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

#include "OpenDoor.h"
#include "ODStr.h"
#include "ODUtil.h"
#include "ODGen.h"


/* ========================================================================= */
/* General string manipulation functions.                                    */
/* ========================================================================= */

/* ----------------------------------------------------------------------------
 * ODStringCopy()
 *
 * Safely copies one string to another. Unlike strncpy(), ODStringCopy()
 * ensures that the destination string is always '\0' terminated.
 *
 * Parameters: pszDest     - Pointer to destination string to which to copy
 *                           characters.
 *
 *             pszSource   - Pointer to source string from which to copy
 *                           characters.
 *
 *             nSizeOfDest - Maximum number of characters to place in pszDest,
 *                           INCLUDING the '\0' string terminator.
 *
 *     Return: void
 */
void ODStringCopy(char *pszDest, CONST char *pszSource, INT nSizeofDest)
{
   ASSERT(pszDest != NULL);
   ASSERT(pszSource != NULL);
   ASSERT(nSizeofDest > 0);

   /* Copy at most the specified number of bytes from source to dest, using */
   /* (presumably well optimized) strncpy().                                */
   strncpy(pszDest, pszSource, nSizeofDest);

   /* Ensure that destination string is '\0' terminated. This will not */
   /* already be the case if strlen(pszSource) >= nSizeofDest.         */
   pszDest[nSizeofDest - 1] = '\0';
}


/* ----------------------------------------------------------------------------
 * ODStringCToPascal()
 *
 * Converts a string from C's zero-terminated string format to Pascal's
 * length byte + string data format.
 *
 * Parameters: psPascalString    - Pointer to the destination string.
 *
 *             btMaxPascalLength - Size of the destination string, as declared
 *                                 in Pascal.
 *
 *             pszCString        - Pointer to the source string, in C format.
 *
 *     Return: A pointer to psPascalString.
 */
char *ODStringCToPascal(char *psPascalString, BYTE btMaxPascalLength,
   char *pszCString)
{
   BYTE btCStringLength = strlen(pszCString);

   ASSERT(psPascalString != NULL);
   ASSERT(btMaxPascalLength > 0);
   ASSERT(pszCString != NULL);

   memcpy((char *)psPascalString + 1,
      pszCString, *psPascalString = (btCStringLength < btMaxPascalLength)
      ? btCStringLength : btMaxPascalLength);
   return(psPascalString);
}


/* ----------------------------------------------------------------------------
 * ODStringPascalToC()
 *
 * Converts a string from Pascal's length byte + string data format to C's
 * zero-terminated string format.
 *
 * Parameters: pszCString     - Pointer to destination string.
 *
 *             psPascalString - Pointer to Pascal format source string.
 *
 *             btMaxLength    - Length of C string.
 *
 *     Return: A pointer to pszCString.
 */
char *ODStringPascalToC(char *pszCString, char *psPascalString,
   BYTE btMaxLength)
{
   ASSERT(pszCString != NULL);
   ASSERT(psPascalString != NULL);
   ASSERT(btMaxLength > 0);

   if(*(BYTE *)psPascalString <= btMaxLength)
   {
      memcpy(pszCString, (char *)psPascalString + 1, *psPascalString);
      pszCString[(int)psPascalString[0]] = '\0';
   }
   else
   {
      pszCString[0] = '\0';
   }
   return(pszCString);
}


/* ----------------------------------------------------------------------------
 * ODStringHasTail()
 *
 * Determines whether a string ends in exactly the specified sequence of
 * characters.
 *
 * Parameters: pszFullString  - String to examine.
 *
 *             pszTail        - String to look for at the end of
 *                              pszFullString.
 *
 *     Return: TRUE if the pszFullString does end with pszTail, FALSE if
 *             it does not.
 */
BOOL ODStringHasTail(char *pszFullString, char *pszTail)
{
   INT nTailLength = strlen(pszTail);
   INT nFullStringLength = strlen(pszFullString);

   ASSERT(pszFullString != NULL);
   ASSERT(pszTail != NULL);

   if(nFullStringLength < nTailLength)
   {
      return(FALSE);
   }

   return(stricmp(pszFullString + (nFullStringLength - nTailLength), pszTail) == 0);
}


/* ========================================================================= */
/* File-related functions.                                                   */
/* ========================================================================= */

/* ----------------------------------------------------------------------------
 * ODMakeFilename()
 *
 * Generates a fully-qualified filename from a path and base filename.
 *
 * Parameters: pszOut      - String to store generated filename in.
 *
 *             pszPath     - Directory name. May be the same as pszOut, or
 *                           may be different.
 *
 *             pszFilename - Base filename.
 *
 *             nMaxOutSize - Size of pszOut. This value should be one more
 *                           than the maximum number of characters to be
 *                           stored in the output string.
 *
 *     Return: kODRCSuccess on success, or an error code on failure.
 */
tODResult ODMakeFilename(char *pszOut, CONST char *pszPath,
   CONST char *pszFilename, INT nMaxOutSize)
{
   /* Validate parameters in debug mode */
   ASSERT(pszPath != NULL);
   ASSERT(pszFilename != NULL);
   ASSERT(pszOut != NULL);
   ASSERT(pszFilename != pszOut);
   ASSERT(nMaxOutSize > 0);

   /* Check that there is enough room in the destination string to hold */
   /* both source strings plus possibly an additional \-seperator.      */
   if((INT)(strlen(pszPath) + strlen(pszFilename) + 1) > nMaxOutSize - 1)
   {
      return(kODRCFilenameTooLong);
   }

   /* Copy path to output filename, if the addresses are different. */
   if(pszPath != pszOut)
   {
      strcpy(pszOut, pszPath);
   }

   /* Ensure there is a trailing backslash, if path was not empty. */
#ifdef ODPLAT_NIX

#else
   if(pszOut[strlen(pszOut) - 1] != DIRSEP && strlen(pszOut) > 0)
   {
      strcat(pszOut, DIRSEP_STR);
   }
#endif

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

   return(kODRCSuccess);
}


/* ----------------------------------------------------------------------------
 * ODFileSize()
 *
 * Determines the size of a currently open file.
 *
 * Parameters: pfFile - Pointer to an already open file to examine.
 *
 *     Return: The size of the file. In the case of a file that is open in
 *             binary mode, this will be the file length in bytes.
 */
DWORD ODFileSize(FILE *pfFile)
{
   DWORD dwOriginal;
   DWORD dwFileSize;

   ASSERT(pfFile != NULL);

   dwOriginal = ftell(pfFile);
   fseek(pfFile, 0L, SEEK_END);
   dwFileSize = ftell(pfFile);
   fseek(pfFile, dwOriginal, SEEK_SET);
   return(dwFileSize);
}


/* ========================================================================= */
/* DWORD math functions.                                                     */
/* ========================================================================= */

/* ----------------------------------------------------------------------------
 * ODDWordShiftLeft()
 *
 * Shifts a DWORD to the left by the specified number of bits.
 *
 * Parameters: dwValue    - Value to be shifted.
 *
 *             btDistance - Distance to shift dwValue by.
 *
 *     Return: Result of the shift operation.
 */
DWORD ODDWordShiftLeft(DWORD dwValue, BYTE btDistance)
{
   WORD wUpper;
   WORD wLower;

   wLower = (WORD)dwValue;
   wUpper = *(WORD *)(((BYTE *)(&dwValue)) + 2);

   while(btDistance--)
   {
      wUpper <<= 1;
      wUpper |= (wLower & 0x8000) >> 15;
      wLower <<= 1;
   }

   dwValue = wLower;
   *(WORD *)(((BYTE *)(&dwValue)) + 2) = wUpper;

   return(dwValue);
}


/* ----------------------------------------------------------------------------
 * ODDWordShiftRight()
 *
 * Shifts a DWORD to the right by the specified number of bits.
 *
 * Parameters: dwValue    - Value to be shifted.
 *
 *             btDistance - Distance to shift dwValue by.
 *
 *     Return: Result of the shift operation.
 */
DWORD ODDWordShiftRight(DWORD dwValue, BYTE btDistance)
{
   WORD wUpper;
   WORD wLower;

   wLower = (WORD)dwValue;
   wUpper = *(WORD *)(((BYTE *)(&dwValue)) + 2);

   while(btDistance--)
   {
      wLower >>= 1;
      wLower |= (wUpper & 0x0001) << 15;
      wUpper >>= 1;
   }

   dwValue=wLower;
   *(WORD *)(((BYTE *)(&dwValue)) + 2) = wUpper;
   return(dwValue);
}


/* ----------------------------------------------------------------------------
 * ODDWordDivide()
 *
 * Divides one DWORD by another DWORD, calculating the quotient and remainder.
 *
 * Parameters: pdwQuotient   - Location where the quotient should be stored,
 *                             or NULL if quotient is not required.
 *
 *             pdwRemainder  - Location where remainder should be stored,
 *                             or NULL if remainder is not required.
 *
 *             dwDividend    - Dividend to be divided by divisor.
 *
 *             dwDivisor     - Divisor to divide dividend by.
 *
 *     Return: TRUE on success or FALSE on failure.
 */
BOOL ODDWordDivide(DWORD *pdwQuotient, DWORD *pdwRemainder,
   DWORD dwDividend, DWORD dwDivisor)
{
   INT nTimes = 0;
   DWORD dwQuotient;
   DWORD dwRemainder;

   /* Check for divide by zero in debug versions. */
   ASSERT(dwDivisor != 0);

   /* Check that divisor is not zero. (An attempt to divide by zero will */
   /* put this algorithm into an infinite loop, rather than triggering   */
   /* a divide fault.)                                                   */
   if(dwDivisor == 0L)
   {
      return(FALSE);
   }

   /* Initialize remainder to be entire dividend */
   dwRemainder = dwDividend;

   /* Initialize quotient to 0 */
   dwQuotient = 0L;

   /* Determine largest required multiple of divisor */
   while(dwRemainder >=  dwDivisor)
   {
      dwDivisor = ODDWordShiftLeft(dwDivisor, 1);
      ++nTimes;
   }

   /* Loop across for all multiples of divisor, beginning with the largest */
   do
   {
      dwQuotient = ODDWordShiftLeft(dwQuotient, 1);

      /* If current remainder is >= this multiple of the divisor */
      if(dwRemainder >= dwDivisor)
      {
         /* Subtract the multiple of the divisor from the remainder */
         dwRemainder -= dwDivisor;

         /* The next bit of the quotient should be a 1 */
         dwQuotient |= 1L;
      }

      /* Divide current multiple of divisor by two */
      dwDivisor = ODDWordShiftRight(dwDivisor, 1);

      /* Repeat for all multiples of the divisor */
   } while(nTimes--);

   /* If caller asked for quotient, then return it */
   if(pdwQuotient != NULL)
   {
      *pdwQuotient = dwQuotient;
   }

   /* If caller asked for remainder, then return it */
   if(pdwRemainder != NULL)
   {
      *pdwRemainder = dwRemainder;
   }

   return(TRUE);
}


/* ----------------------------------------------------------------------------
 * ODDWordDivide()
 *
 * Multiplies one DWORD by another, returning the product. Multiplication
 * is performed by using at most 32 additions.
 *
 * Parameters: dwMultiplicand - The multiplicand.
 *
 *             dwMultiplier   - The multiplier.
 *
 *     Return: Result of the multiplication.
 */
DWORD ODDWordMultiply(DWORD dwMultiplicand, DWORD dwMultiplier)
{
   DWORD dwResult = 0;

   /* Loop while multiplier is not zero */
   while(dwMultiplier != 0)
   {
      /* If least significant bit of multiplier is set */
      if(dwMultiplier & 0x00000001)
      {
         /* Add multiplicand to product */
         dwResult += dwMultiplicand;
      }

      /* Shift multiplicand left one bit */
      dwMultiplicand = ODDWordShiftLeft(dwMultiplicand, 1);

      /* Shift multiplier right one bit */
      dwMultiplier = ODDWordShiftRight(dwMultiplier, 1);
   }

   /* Return the final result to the caller. */
   return(dwResult);
}