
/*
 *@@sourcefile stringh.c:
 *      contains string/text helper functions that are independent
 *      of a single application, i.e. you can use them in any
 *      program. These are good for parsing/splitting strings and
 *      other stuff used throughout XFolder.
 *
 *      Function prefixes (new with V0.81):
 *      --  strh*       string helper functions.
 *
 *@@include #define INCL_DOSDATETIME
 *@@include #include <os2.h>
 *@@include #include "stringh.h"
 */

/*
 *      Copyright (C) 1997-99 Ulrich M”ller.
 *      This file is part of the XFolder source package.
 *      XFolder is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XFolder main distribution.
 *      This program 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 General Public License for more details.
 */

#define INCL_WINSHELLDATA
#include <os2.h>

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

#include "stringh.h"

// #define _PMPRINTF_
#include "pmprintf.h"

/*
 *@@ strhReplace:
 *      replace oldStr with newStr in str.
 *
 *      str should have enough allocated space for the replacement, no check
 *      is made for this. str and OldStr/NewStr should not overlap.
 *      The empty string ("") is found at the beginning of every string.
 *
 *      Returns: pointer to first location behind where NewStr was inserted
 *      or NULL if OldStr was not found.
 *      This is useful for multiple replacements also.
 *      (be careful not to replace the empty string this way !)
 *
 *      Author:     Gilles Kohl
 *      Started:    09.06.1992   12:16:47
 *      Modified:   09.06.1992   12:41:41
 *      Subject:    Replace one string by another in a given buffer.
 *                  This code is public domain. Use freely.
 */

char* strhReplace(char* str, char* oldStr, char* newStr)
{
      int OldLen, NewLen;
      char *p, *q;

      if (NULL == (p = strstr(str, oldStr)))
            return p;
      OldLen = strlen(oldStr);
      NewLen = strlen(newStr);
      memmove(q = p+NewLen, p+OldLen, strlen(p+OldLen)+1);
      memcpy(p, newStr, NewLen);
      return (q);
}

/*
 *@@ strhInsert:
 *      inserts to_insert into in_str at position place and
 *      copies the result to out_str
 *
 *      Author:     Gilles Kohl
 *      Started:    09.06.1992   12:16:47
 *      Modified:   09.06.1992   12:41:41
 *      Subject:    Replace one string by another in a given buffer.
 *                  This code is public domain. Use freely.
 */

char* strhInsert(char* out_str, char* in_str, char* to_insert, int place)
{
    char *out_ptr = out_str, buffer[255], *ptr = buffer;

    if (place <= 0)
        return ptr;

    if ( in_str == out_str )
    {
        while( -- place )
            if ( ! *out_str++ ) return(out_ptr);
            while ( *ptr++ = *in_str++ )
                ; /* strcpy : used if in_str = out_str */
            ptr = buffer;
    }
    else {
        while( -- place )
            if ( ! (*out_str++ = *in_str++) )
                return(out_ptr);

        ptr = in_str;
    }

    while( *out_str++ = *to_insert++ )
        ;
    out_str --;

    while ( *out_str++ = *ptr++ )
        ;

    return out_ptr;
}

/*
 *@@ strhistr:
 *      like strstr, but case-insensitive.
 */

PSZ strhistr(PSZ string1, PSZ string2)
{
    PSZ prc = NULL;

    if ((string1) && (string2)) {
        PSZ pszSrchIn = strdup(string1);
        PSZ pszSrchFor = strdup(string2);
        strupr(pszSrchIn);
        strupr(pszSrchFor);

        if ((pszSrchIn) && (pszSrchFor)) {
            prc = strstr(pszSrchIn, pszSrchFor);
            if (prc) {
                // prc now has the first occurence of the string,
                // but in pszSrchIn; we need to map this
                // return value to the original string
                prc = (prc-pszSrchIn) // offset in pszSrchIn
                      + string1;
            }
        }
        if (pszSrchFor)
            free(pszSrchFor);
        if (pszSrchIn)
            free(pszSrchIn);
    }
    return (prc);
}

/*
 *@@ strhThousandsULong:
 *      converts a ULONG into a decimal string, while
 *      inserting thousands separators into it. Specify
 *      the separator char in cThousands.
 *      Returns pszTarget so you can use it directly
 *      with sprintf and the "%s" flag.
 *      For cThousands, you should use the data in
 *      OS2.INI ("PM_National" application), which is
 *      always set according to the "Country" object.
 *      Use strhThousandsDouble for "double" values.
 */

PSZ strhThousandsULong(PSZ pszTarget,       // out: decimal as string
                       ULONG ul,            // in: decimal to convert
                       CHAR cThousands)     // in: separator char (e.g. '.')
{
    USHORT ust, uss, usc;
    CHAR   szTemp[40];
    sprintf(szTemp, "%d", ul);

    ust = 0;
    usc = strlen(szTemp);
    for (uss = 0; uss < usc; uss++)
    {
        if (uss)
            if (((usc - uss) % 3) == 0)
            {
                pszTarget[ust] = cThousands;
                ust++;
            }
        pszTarget[ust] = szTemp[uss];
        ust++;
    }
    pszTarget[ust] = '\0';

    return (pszTarget);
}

/*
 *@@ strhThousandsDouble:
 *      like strhThousandsULong, but for a "double"
 *      value. Note that after-comma values are truncated.
 */

PSZ strhThousandsDouble(PSZ pszTarget, double dbl, CHAR cThousands)
{
    USHORT ust, uss, usc;
    CHAR   szTemp[40];
    sprintf(szTemp, "%.0f", floor(dbl));

    ust = 0;
    usc = strlen(szTemp);
    for (uss = 0; uss < usc; uss++)
    {
        if (uss)
            if (((usc - uss) % 3) == 0)
            {
                pszTarget[ust] = cThousands;
                ust++;
            }
        pszTarget[ust] = szTemp[uss];
        ust++;
    }
    pszTarget[ust] = '\0';

    return (pszTarget);
}

/*
 *@@ strhFileDate:
 *      converts file date data to a string (to pszBuf).
 *      You can pass any FDATE structure to this function,
 *      which are returned in those FILEFINDBUF* or
 *      FILESTATUS* structs by the Dos* functions.
 *      ulDateFormat can be queried using
 +              PrfQueryProfileInt(HINI_USER, "PM_National", "iDate", 0);
 *
 *      meaning:
 *      --  0   mm.dd.yyyy  (English)
 *      --  1   dd.mm.yyyy  (e.g. German)
 *      --  2   yyyy.mm.dd  (Japanese)
 *      --  3   yyyy.dd.mm
 *
 *      cDateSep is used as a date separator (e.g. '.').
 *      This can be queried using:
 +              PrfQueryProfileString(HINI_USER, "PM_National", "sDate", "/",
 +                  szDateSep, sizeof(szDateSep)-1);
 */

VOID strhFileDate(PSZ pszBuf,           // out: string returned
                  FDATE* pfDate,        // in: date information
                  ULONG ulDateFormat,   // in: date format (0-3)
                  CHAR cDateSep)        // in: date separator (e.g. '.')
{
    switch (ulDateFormat) {
        case 0:  // mm.dd.yyyy  (English)
            sprintf(pszBuf, "%02d%c%02d%c%04d",
                pfDate->month,
                    cDateSep,
                pfDate->day,
                    cDateSep,
                ((pfDate->year)+1980));
        break;

        case 1:  // dd.mm.yyyy  (e.g. German)
            sprintf(pszBuf, "%02d%c%02d%c%04d",
                pfDate->day,
                    cDateSep,
                pfDate->month,
                    cDateSep,
                ((pfDate->year)+1980));
        break;

        case 2: // yyyy.mm.dd  (Japanese)
            sprintf(pszBuf, "%04d%c%02d%c%02d",
                ((pfDate->year)+1980),
                    cDateSep,
                pfDate->month,
                    cDateSep,
                pfDate->day);
        break;

        default: // yyyy.dd.mm
            sprintf(pszBuf, "%04d%c%02d%c%02d",
                ((pfDate->year)+1980), cDateSep,
                pfDate->day, cDateSep,
                pfDate->month);
        break;
    }
}

/*
 *@@ strhFileTime:
 *      converts file time data to a string (to pszBuf).
 *      You can pass any FTIME structure to this function,
 *      which are returned in those FILEFINDBUF* or
 *      FILESTATUS* structs by the Dos* functions.
 *      ulTimeFormat can be queried using
 +              PrfQueryProfileInt(HINI_USER, "PM_National", "iTime", 0);
 *      meaning:
 *      --  0   12-hour clock
 *      --  >0  24-hour clock
 *
 *      cDateSep is used as a time separator (e.g. ':').
 *      This can be queried using:
 +              PrfQueryProfileString(HINI_USER, "PM_National", "sTime", ":",
 +                  szTimeSep, sizeof(szTimeSep)-1);
 *
 *@@changed 99-03-15 fixed 12-hour crash
 */

VOID strhFileTime(PSZ pszBuf,           // out: string returned
                  FTIME* pfTime,        // in: time information
                  ULONG ulTimeFormat,   // in: 24-hour time format (0 or 1)
                  CHAR cTimeSep)        // in: time separator (e.g. ':')
{
    if (ulTimeFormat == 0)
    {
        // for 12-hour clock, we need additional INI data
        CHAR szAMPM[10] = "err";

        if (pfTime->hours > 12)
        {
            // > 12h: PM.

            // Note: 12:xx noon is 12 AM, not PM (even though
            // AM stands for "ante meridiam", but English is just
            // not logical), so that's handled below.

            PrfQueryProfileString(HINI_USER,
                "PM_National",
                "s2359",        // key
                "PM",           // default
                szAMPM, sizeof(szAMPM)-1);
            sprintf(pszBuf, "%02d%c%02d%c%02d %s",
                // leave 12 == 12 (not 0)
                pfTime->hours % 12,
                    cTimeSep,
                pfTime->minutes,
                    cTimeSep,
                pfTime->twosecs*2,
                szAMPM);
        } else
        {
            // <= 12h: AM
            PrfQueryProfileString(HINI_USER,
                "PM_National",
                "s1159",        // key
                "AM",           // default
                szAMPM, sizeof(szAMPM)-1);
            sprintf(pszBuf, "%02d%c%02d%c%02d %s",
                pfTime->hours,
                    cTimeSep,
                pfTime->minutes,
                    cTimeSep,
                pfTime->twosecs*2,
                szAMPM);
        }
    }
    else
        // 24-hour clock
        sprintf(pszBuf, "%02d%c%02d%c%02d",
            pfTime->hours, cTimeSep,
            pfTime->minutes, cTimeSep,
            pfTime->twosecs*2);
}

/*
 *@@ strhFindEOL:
 *      returns a pointer to the next \r, \n or NULL character
 *      following pszSearchIn. Stores the offset in *pulOffset.
 */

PSZ strhFindEOL(PSZ pszSearchIn, PULONG pulOffset)
{
    PSZ p = pszSearchIn;
    if (pulOffset)
        *pulOffset = 0;
    while (TRUE) {
        if ( (*p == '\r') || (*p == '\n') || (*p == 0) )
            return (p);
        p++;
        if (pulOffset)
            (*pulOffset)++;
    }

}

/*
 *@@ strhFindKey:
 *      finds pszKey in pszSearchIn; similar to strhistr,
 *      but this one makes sure the key is at the beginning
 *      of a line. Spaces before the key are tolerated.
 *      Returns NULL if the key was not found.
 *      Used by strhGetParameter/strhSetParameter; useful
 *      for analyzing CONFIG.SYS settings.
 */

PSZ strhFindKey(PSZ pszSearchIn,     // in: text buffer to search
               PSZ pszKey,          // in: key to search for
               PBOOL pfIsAllUpperCase) // out: TRUE if key is completely in upper case;
                                       // can be NULL if not needed
{
    PSZ     p = NULL;
    BOOL    fFound = FALSE;

    _Pmpf(("  strhFindKey %s", pszKey));
    p = pszSearchIn;
    do {
        p = strhistr(p, pszKey);
        _Pmpf(("    found @ ofs %d", (p-pszSearchIn) ));

        if ((p) && (p >= pszSearchIn)) {
            // make sure the key is at the beginning of a line
            // by going backwards until we find a char != " "
            PSZ p2 = p;
            while ( (*p2 == ' ') && (p2 > pszSearchIn) )
                p2--;

            // if previous char is an EOL sign, go on
            if (    (*(p2-1) == '\r')
                 || (*(p2-1) == '\n')
                 || (p2 == pszSearchIn)
               )
            {
                // found:
                fFound = TRUE; // go on, p contains found key

                if (pfIsAllUpperCase) {
                    // test for all upper case
                    ULONG cbKey = strlen(pszKey),
                          ul = 0;
                    *pfIsAllUpperCase = TRUE;
                    for (ul = 0; ul < cbKey; ul++)
                        if (islower(*(p+ul))) {
                            _Pmpf(("  Lower case found: %c", *(p+ul)));
                            *pfIsAllUpperCase = FALSE;
                            break;
                        }

                }
            } // else search next key
            else
                p++; // search on after this key
        }
    } while ((!fFound) && (p != NULL) && (p != pszSearchIn));

    return (p);
}


/*
 *@@ strhGetParameter:
 *      searches pszSearchIn for the key pszKey; if found, it
 *      returns a pointer to the following characters in pszSearchIn
 *      and, if pszCopyTo != NULL, copies the rest of the line to
 *      that buffer, of which cbCopyTo specified the size.
 *      If the key is not found, NULL is returned.
 *      This is useful for querying CONFIG.SYS settings.
 *      <P><B>Example:</B> this would return "YES" if you searched
 *      for "PAUSEONERROR", and "PAUSEONERROR=YES" existed in pszSearchIn.
 */

PSZ strhGetParameter(PSZ pszSearchIn,   // in: text buffer to search
                     PSZ pszKey,        // in: key to search for
                     PSZ pszCopyTo,     // out: key value
                     ULONG cbCopyTo)    // out: sizeof(*pszCopyTo)
{
    PSZ p = strhFindKey(pszSearchIn, pszKey, NULL),
        prc = NULL;
    // copy to pszCopyTo
    if (p) {
        prc = p + strlen(pszKey);
        if (pszCopyTo) {
            ULONG cb;
            PSZ pEOL = strhFindEOL(prc, &cb);
            if (pEOL) {
                _Pmpf(("%s: cb: %d, p2-p1: %d", pszKey, cb, pEOL-prc));
                if (cb > cbCopyTo)
                    cb = cbCopyTo-1;
                strncpy(pszCopyTo, prc, cb);
                pszCopyTo[cbCopyTo-1] = '\0';
            }
        }
    }

    return (prc);
}

/*
 *@@ strhSetParameter:
 *      searches pszSearchIn for the key pszKey; if found, it
 *      replaces the characters following this key up to the
 *      end of the line by pszParam. If pszKey is not found in
 *      pszSearchIn, it is appended to the file in a new line. You
 *      must ensure that *pszSearchin is large enough for the
 *      manipulation, i.e. it must be at least of the following
 *      size:
 *          strlen(pszSearchIn) + strlen(pszNewParam) + 1
 *      in case the key was not found and will be appended.
 *      This function searches w/out case sensitivity.
 *      Returns a pointer to the new parameter.
 */

PSZ strhSetParameter(PSZ pszSearchIn,    // in: text buffer to search
                    PSZ pszKey,         // in: key to search for
                    PSZ pszNewParam,    // in: new parameter to set for key
                    BOOL fRespectCase)  // in: if TRUE, pszNewParam will
                            // be converted to upper case if the found key is
                            // in upper case also. pszNewParam should be in
                            // lower case if you use this.
{
    BOOL fIsAllUpperCase = FALSE;
    PSZ pKey = strhFindKey(pszSearchIn, pszKey, &fIsAllUpperCase),
        prc = NULL;

    _Pmpf(("%s, upper case: %d", pszKey, fIsAllUpperCase));

    if (pKey) {
        // key found in file:
        // replace existing parameter
        PSZ pOldParam = pKey + strlen(pszKey);
        prc = pOldParam;
        // pOldParam now has the old parameter, which we
        // will overwrite now

        // printf("  found %s @ %d\n", pszKey, (pKey-pszSearchIn));
        if (pOldParam) {
            ULONG cbOldParam;
            PSZ pEOL = strhFindEOL(pOldParam, &cbOldParam);
            // pEOL now has first end-of-line after the
            // parameter

            // printf("  old param is @ %d\n", pOldParam-pszSearchIn);
            if (pEOL) {
                PSZ pszOldCopy = (PSZ)malloc(cbOldParam+1);
                strncpy(pszOldCopy, pOldParam, cbOldParam);
                pszOldCopy[cbOldParam] = '\0';
                // printf("  found EOL @ %d\n", pEOL-pszSearchIn);

                if (fIsAllUpperCase)
                    strupr(pszNewParam);
                strhReplace(pOldParam, pszOldCopy, pszNewParam);

                free(pszOldCopy);
            }
        }
    } else {
        // key not found: append to end of file
        strcat(pszSearchIn, "\r\n");
        strcat(pszSearchIn, pszKey);
        strcat(pszSearchIn, strupr(pszNewParam));
        strcat(pszSearchIn, "\r\n");
        // printf("  Appended %s%s to end of file\n", pszKey, pszNewParam);
    }

    return (prc);
}

/*
 *@@ strhDeleteLine:
 *      this deletes the line in pszSearchIn which starts with
 *      the key pszKey. Returns TRUE if the line was found and
 *      deleted.
 */

BOOL strhDeleteLine(PSZ pszSearchIn, PSZ pszKey)
{
    BOOL fIsAllUpperCase = FALSE;
    PSZ pKey = strhFindKey(pszSearchIn, pszKey, &fIsAllUpperCase);
    BOOL brc = FALSE;

    if (pKey) {
        PSZ pEOL = strhFindEOL(pKey, NULL);
        // pEOL now has first end-of-line after the key
        if (pEOL) {
            // delete line by overwriting it with
            // the next line
            strcpy(pKey, pEOL+2);
        } else {
            // EOL not found: we must be at the end of the file
            *pKey = '\0';
        }
        brc = TRUE;
    }

    return (brc);
}

/*
 *@@ strhBeautifyTitle:
 *      replaces all line breaks (0xd, 0xa) with spaces.
 */

BOOL strhBeautifyTitle(PSZ psz)
{
    BOOL rc = FALSE;
    CHAR *p;
    while (p = strchr(psz, 0xa))
    {
        *p = ' ';
        rc = TRUE;
    }
    while (p = strchr(psz, 0xd))
    {
        *p = ' ';
        rc = TRUE;
    }
    return (rc);
}


