/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/****************************************************/
/*                                                  */
/* Private profile library                          */
/*                                                  */
/* Attempt to write a cross-platform library for    */
/* dealing with Windows style .INI profiles.        */
/*                                                  */
/* For API see pplib.h                              */
/*                                                  */
/* 11.26.97 - av started                            */
/*                                                  */
/* Current limitations:                             */
/*   - profile must NOT contain space chars         */
/*   - colon indicating comments must be in the     */
/*     first position                               */
/*                                                  */
/****************************************************/

#include "xp.h"

static BOOL isCurrentLineCommentedOut(char * pBuf, char * pPosition)
{
  for(;;)
  {
    if((*pPosition == ';') && ((pPosition - 1 < pBuf) || (*(pPosition - 1) == '\n') || (*(pPosition - 1) == '\r')))
      return TRUE;
    pPosition--;
    if(pPosition < pBuf)
      return FALSE;
    if((*pPosition == '\n') || (*pPosition == '\r'))
      return FALSE;
  }
}

/*******************************************************************/
/*                                                                 */
/* Note: For a section header to be found, the [ must be the first */
/* character on the line.  This prevents the [Options] string in   */
/* the comments of the Sample.pts script from being found as the   */
/* top of the Options section.          -pollmann                  */
/*                                                                 */
/*******************************************************************/
static char * goToSectionBody(char * pBuf, char * szSection)
{
  if((pBuf == NULL) || (szSection == NULL))
    return NULL;
  char szSectionString[256];
  strcpy(szSectionString, "\n");   /* Line feed before [ */
  strcat(szSectionString, "[");
  strcat(szSectionString, szSection);
  strcat(szSectionString, "]");
  char * p = strstr(pBuf, szSectionString);
  if(p == NULL) {
    szSectionString[0] = '\r';     /* Carriage return before [ */
    char * p = strstr(pBuf, szSectionString);
    if(p == NULL)
      return NULL;
  }
  p += strlen(szSectionString) + 1; // 1 is for new line character
  return p;
}

static int getSectionLength(char * pBuf, char * szSection)
{
  int iRet = 0;

  char * pSectionStart = goToSectionBody(pBuf, szSection);

  if(pSectionStart == NULL)
    return 0;

  char * pNextSection = strstr(pSectionStart, "[");

  if(pNextSection != NULL)
    iRet = (int)(pNextSection - pSectionStart);
  else
    iRet = (int)(pBuf + strlen(pBuf) - pSectionStart);

  return iRet;
}

static char * goToKeyBody(char * pSectionStart, int iSectionLength, char * szKey)
{
  if((pSectionStart == NULL) || (szKey == NULL))
    return NULL;
  if((int)strlen(szKey) >= iSectionLength)
    return NULL;
  char szKeyString[256];
  strcpy(szKeyString, szKey);
  strcat(szKeyString, "=");
  char chBackup = pSectionStart[iSectionLength];
  pSectionStart[iSectionLength] = '\0';
  char * p = strstr(pSectionStart, szKeyString);
  if(p == NULL)
  {
    pSectionStart[iSectionLength] = chBackup;
    return NULL;
  }
  p = strstr(p, "=");
  if(p == NULL)
  {
    pSectionStart[iSectionLength] = chBackup;
    return NULL;
  }
  p++;
  pSectionStart[iSectionLength] = chBackup;
  if(isCurrentLineCommentedOut(pSectionStart, p))
    return NULL;
  else
    return p;
}

static int getKeyLength(char * pSectionStart, int iSectionLength, char * szKey)
{
  int iRet = 0;

  char * pKeyStart = goToKeyBody(pSectionStart, iSectionLength, szKey);

  if(pKeyStart == NULL)
    return 0;

  char * pFirstCarriageReturn = strchr(pKeyStart, '\r');
  char * pFirstNewLine = strchr(pKeyStart, '\n');

  if((pFirstCarriageReturn == NULL) && (pFirstNewLine == NULL))
    return iSectionLength;

  char * p = NULL;

  if((pFirstCarriageReturn != NULL) && (pFirstNewLine != NULL))
    p = (pFirstCarriageReturn < pFirstNewLine) ? pFirstCarriageReturn : pFirstNewLine;
  else
    p = (pFirstCarriageReturn != NULL) ? pFirstCarriageReturn : pFirstNewLine;

  p--;

  // cut off ending spaces and tabs
  while((*p == ' ') || (*p == '\t'))
    p--;

  p++;

  iRet = (int)(p - pKeyStart);

  return iRet;
}

static char * readFileToNewAllocatedZeroTerminatedBuffer(XP_HFILE hFile)
{
  if(hFile == NULL)
    return NULL;

  int iSize;
  
#ifdef XP_MAC
  iSize = 32000;  /* HACK for the MAC */
#else  
  #if defined(XP_UNIX) || defined(XP_OS2)
    struct stat buf;
    fstat(fileno(hFile), &buf);
    iSize = buf.st_size;
  #else
    iSize = (int)_filelength(/*_fileno*/(hFile));
  #endif
#endif

  if(iSize <= 0)
    return NULL;

  char * pBuf = new char[iSize + 1];
  if(pBuf == NULL)
    return NULL;

  fseek((FILE *)hFile, 0L, SEEK_SET);
  fread((void *)pBuf, iSize, 1, (FILE *)hFile);

  // cut off strange stuff at the end and reallocate
  char * p = pBuf + iSize - 1;
  for(;;)
  {
    BOOL bNormalChar = ((int)*p > 0) && (isprint(*p) != 0);
    if(bNormalChar || (*p == '\n') || (*p == '\r') || (p < pBuf))
      break;
    p--;
  }
  p++;
  *p = '\0';

  iSize = strlen(pBuf);
  char * pBufFinal = new char[iSize + 1];
  memcpy((void *)pBufFinal, (void *)pBuf, iSize);
  pBufFinal[iSize] = '\0';
  delete [] pBuf;
  return pBufFinal;
}

DWORD PP_GetString(char * szSection, char * szKey, char * szDefault, char * szString, DWORD dwSize,  XP_HFILE hFile)
{
  char * pBuf = NULL;
  char * pSectionStart = NULL;
  int iSectionLength = 0;
  int iSize = 0;

  pBuf = readFileToNewAllocatedZeroTerminatedBuffer(hFile);

  if(pBuf == NULL)
    goto ReturnDefault;

  iSize = strlen(pBuf);

  //
  // first consider two special cases: szSection = NULL and szKey = NULL
  //
  if(szSection == NULL) // find all sections and return
  {
    char * pNextSection = NULL;
    char * pSource = pBuf;
    char * pDest = szString;
    int iBytes = 0;

    for(;;)
    {
      pSource = strstr(pSource, "[");
      if(pSource == NULL)
        break;

      pNextSection = pSource + 1;
      char * pEnd = strstr(pSource, "]");
      int iLength = pEnd - pNextSection;

      if(!isCurrentLineCommentedOut(pBuf, pSource))
      {
        if(iBytes + iLength + 2 < (int)dwSize)
          memcpy((void *)pDest, (void *)pNextSection, iLength);
        else
          break;

        pDest += iLength;
        *pDest = '\0';
        pDest++;
        iBytes += iLength + 1;  // for '\0'
        if(iBytes >= iSize)
          break;
      }
      pSource += iLength + 2; // for "[" and "]"
    }

    *pDest = '\0';
    iBytes++; // for this '\0'
    delete [] pBuf;
    return (DWORD)iBytes;
  }

  pSectionStart = goToSectionBody(pBuf, szSection);

  if(pSectionStart == NULL)
    goto ReturnDefault;

  iSectionLength = getSectionLength(pBuf, szSection);

  if(iSectionLength == 0)
    goto ReturnDefault;

  if(szKey == NULL) // find all keys and return
  {
    int iBytes = 0;
    char * pSource = pSectionStart - 1; // point to previous new line char
    char * pDest = szString;

    for(;;)
    {
      char * pEqualSign = strstr(pSource, "=");

      if(pEqualSign > pSectionStart + iSectionLength)
        break;

      *pEqualSign = '\0';

      char * pLastCarriageReturn = strrchr(pSource, '\r');
      char * pLastNewLine = strrchr(pSource, '\n');

      if((pLastCarriageReturn == NULL) && (pLastNewLine == NULL))
      {
        pSource = pEqualSign + 1;
        continue;
      }

      char * pKey = (pLastCarriageReturn > pLastNewLine) ? pLastCarriageReturn : pLastNewLine;
      pKey++;
      int iLength = strlen(pKey);

      if(!isCurrentLineCommentedOut(pBuf, pKey))
      {
        if(iBytes + iLength + 2 < (int)dwSize)
          memcpy((void *)pDest, (void *)pKey, iLength);
        else
          break;

        pDest += iLength;
        *pDest = '\0';
        pDest++;
        iBytes += iLength + 1; // for '\0'
        if(iBytes >= iSize)
          break;
      }
      pSource = pEqualSign + 1;
    }

    *pDest = '\0';
    iBytes++; // for this '\0'
    delete [] pBuf;
    return (DWORD)iBytes;
  }

  //
  // Now the real thing
  //
  {
    char * pKeyStart = goToKeyBody(pSectionStart, iSectionLength, szKey);
    if(pKeyStart == NULL)
      goto ReturnDefault;
    int iKeyLength = getKeyLength(pSectionStart, iSectionLength, szKey);
    int iLength = (iKeyLength < (int)dwSize - 1) ? iKeyLength : (int)dwSize - 1;
    memcpy((void *)szString, (void *)pKeyStart, iLength);
    szString[iLength] = '\0';
    delete [] pBuf;
    return (DWORD)iLength;
  }

ReturnDefault:

  if(pBuf != NULL)
    delete [] pBuf;
  if((szDefault != NULL) && (szString != NULL))
    strcpy(szString, szDefault);
  return (DWORD)strlen(szString);
}

static BOOL replaceBytesInReallocatedBufferAfter(
                 char ** ppBuf,       // address of buffer pointer
                 char * szString,     // string to make a replacement with
                 int iPosition,       // byte after which to insert the string
                 int iBytesToReplace) // number of bytes to replace
{
  if(ppBuf == NULL)
    return FALSE;
  char * pBuf = *ppBuf;
  if(pBuf == NULL)
    return FALSE;
  
  int iNewLength = strlen(pBuf) - iBytesToReplace + strlen(szString);
  char * pBufNew = new char[iNewLength + 1]; // 1 - for zero termination
  if(pBufNew == NULL)
    return FALSE;
  if(iPosition > 0)
    memcpy((void *)pBufNew, (void *)pBuf, iPosition);
  char * p = pBufNew + iPosition;
  memcpy((void *)p, (void *)szString, strlen(szString));
  p += strlen(szString);
  memcpy((void *)p, (void *)(pBuf + iPosition + iBytesToReplace), strlen(pBuf) - iPosition - iBytesToReplace);
  p += strlen(pBuf) - iPosition - iBytesToReplace;
  *p = '\0';
  delete [] pBuf;
  *ppBuf = pBufNew;
  return TRUE;
}

BOOL PP_WriteString(char * szSection, char * szKey, char * szString, XP_HFILE hFile)
{
  if((szSection == NULL) || (szKey == NULL) || (szString == NULL) || (hFile == NULL))
    return FALSE;

  char * pBuf = readFileToNewAllocatedZeroTerminatedBuffer(hFile);

  if(pBuf == NULL)
    return FALSE;

#if !(defined XP_MAC || defined XP_UNIX || defined XP_OS2)
  long lOldSize = (long)strlen(pBuf);
#endif

  char * pSectionStart = goToSectionBody(pBuf, szSection);

  // add section if not present
  if(pSectionStart == NULL)
  {
    char szNewSection[256];
    strcpy(szNewSection, "[");
    strcat(szNewSection, szSection);
    strcat(szNewSection, "]");
    strcat(szNewSection, "\n");
    strcat(szNewSection, "\n");
    if(!replaceBytesInReallocatedBufferAfter(&pBuf, szNewSection, 0, 0))
    {
      if(pBuf != NULL)
        delete [] pBuf;
      return FALSE;
    }
    pSectionStart = goToSectionBody(pBuf, szSection);
  }

  if(pSectionStart == NULL)
    return FALSE;

  int iSectionLength = getSectionLength(pBuf, szSection);
  char * pKeyStart = goToKeyBody(pSectionStart, iSectionLength, szKey);

  // add key if not present
  if(pKeyStart == NULL)
  {
    char szNewKey[256];
    strcpy(szNewKey, szKey);
    strcat(szNewKey, "=");
    strcat(szNewKey, "\n");
    int iPos = pSectionStart - pBuf;
    if(!replaceBytesInReallocatedBufferAfter(&pBuf, szNewKey, iPos, 0))
    {
      if(pBuf != NULL)
        delete [] pBuf;
      return FALSE;
    }
    pSectionStart = goToSectionBody(pBuf, szSection);
    iSectionLength = getSectionLength(pBuf, szSection);
    pKeyStart = goToKeyBody(pSectionStart, iSectionLength, szKey);
  }

  if(pKeyStart == NULL)
    return FALSE;

  int iKeyLength = getKeyLength(pSectionStart, iSectionLength, szKey);
  int iPos = pKeyStart - pBuf;
  if(!replaceBytesInReallocatedBufferAfter(&pBuf, szString, iPos, iKeyLength))
  {
    if(pBuf != NULL)
      delete [] pBuf;
    return FALSE;
  }
  
#if (defined XP_MAC || defined XP_UNIX || defined XP_OS2)
	rewind(hFile);
#else
  // change file size
  long lNewSize = (long)strlen(pBuf);
  long lSizeChange = lNewSize - lOldSize;
  if(lSizeChange != 0L)
  {
    long lOldFileSize = (long)_filelength(/*_fileno*/(hFile));
    long lNewFileSize = lOldFileSize + lSizeChange;
    _chsize(/*_fileno*/(hFile), lNewFileSize);
  }

  fseek((FILE *)hFile, 0L, SEEK_SET);
#endif

  fwrite(pBuf, strlen(pBuf), 1, (FILE *)hFile);

  delete [] pBuf;

  return TRUE;
}
