/*  SPDX-License-Identifier: MIT */
/*
 *  Copyright (c) 2014-2022 Intel Corp
 */

#include "safeclib_private.h"
#include "safe_str_constraint.h"
#include "safe_str_lib.h"
#include "snprintf_s.h"

#define FMT_CHAR    'c'
#define FMT_WCHAR   'C'
#define FMT_SHORT   'h'
#define FMT_INT        'd'
#define FMT_LONG    'l'
#define FMT_STRING    's'
#define FMT_WSTRING    'S'
#define FMT_DOUBLE    'g'
#define FMT_LDOUBLE    'G'
#define FMT_VOID    'p'
#define FMT_PCHAR    '1'
#define FMT_PSHORT    '2'
#define FMT_PINT    '3'
#define FMT_PLONG    '4'



#define MAX_FORMAT_ELEMENTS    16

#define CHK_FORMAT(X,Y)   (((X)==(Y))?1:0)


unsigned int
parse_format(const char *format, char pformatList[], unsigned int maxFormats)
{
    unsigned int  numFormats = 0;
    unsigned int  index = 0;
    unsigned int  start = 0;
    char          lmod = 0;

    while (index < RSIZE_MAX_STR && format[index] != '\0' && numFormats < maxFormats)
    {
        if (format[index] == '%') {
            start = index; // remember where the format string started
            // Check for flags
            switch( format[++index]) {
            case '\0': continue; // skip - end of format string
            case '%' : continue; // skip - actually a percent character
            case '#' : // convert to alternate form
            case '0' : // zero pad
            case '-' : // left adjust
            case ' ' : // pad with spaces
            case '+' : // force a sign be used
                index++; // skip the flag character
                break;
            }
            // check for and skip the optional field width
            while ( format[index] != '\0' && format[index] >= '0' && format[index] <= '9') {
                index++;
            }
            // Check for an skip the optional precision
            if ( format[index] != '\0' && format[index] == '.') {
                index++; // skip the period
                while ( format[index] != '\0' && format[index] >= '0' && format[index] <= '9') {
                    index++;
                }
            }
            // Check for and skip the optional length modifiers
            lmod = ' ';
            switch( format[index]) {
            case 'h' : if ( format[++index] == 'h') {
                           ++index; //also recognize the 'hh' modifier
                           lmod = 'H'; // for char
                       } else {
                           lmod = 'h'; // for short
                       }
                       break;
            case 'l' : if ( format[++index] == 'l') {
                           ++index; //also recognize the 'll' modifier
                           lmod = 'd'; // for long long
                       } else {
                           lmod = 'l'; // for long
                       }
                       break;
            case 'L' : lmod = 'L'; break;
            case 'j' :
            case 'z' :
            case 't' : index++;
                       break;
            }

            // Recognize and record the actual modifier
            switch( format[index]) {
            case 'c' :
                 if ( lmod == 'l') {
                     pformatList[numFormats] = FMT_WCHAR; // store the format character
                 } else {
                     pformatList[numFormats] = FMT_CHAR;
                 }
                 numFormats++;
                 index++; // skip the format character
                 break;

            case 'd' : case 'i' : // signed
            case 'o' : case 'u' : // unsigned
            case 'x' : case 'X' : // unsigned
                 if ( lmod == 'H') {
                     pformatList[numFormats] = FMT_CHAR; // store the format character
                 } else if ( lmod == 'l') {
                      pformatList[numFormats] = FMT_LONG; // store the format character
                 } else if ( lmod == 'h') {
                     pformatList[numFormats] = FMT_SHORT; // store the format character
                 } else{
                     pformatList[numFormats] = FMT_INT;
                 }
                 numFormats++;
                 index++; // skip the format character
                 break;

            case 'e' : case 'E' :
            case 'f' : case 'F' :
            case 'g' : case 'G' :
            case 'a' : case 'A' :
                 if ( lmod == 'L') {
                     pformatList[numFormats] = FMT_LDOUBLE; // store the format character
                 } else{
                     pformatList[numFormats] = FMT_DOUBLE;
                 }
                 numFormats++;
                 index++; // skip the format character
                 break;

            case 's' :
                 if ( lmod == 'l' || lmod == 'L') {
                     pformatList[numFormats] = FMT_WSTRING; // store the format character
                 } else {
                     pformatList[numFormats] = FMT_STRING;
                 }
                 numFormats++;
                 index++; // skip the format character
                 break;

            case 'p' :
                 pformatList[numFormats] = FMT_VOID;
                 numFormats++;
                 index++; // skip the format character
                 break;

            case 'n' :
                 if ( lmod == 'H') {
                     pformatList[numFormats] = FMT_PCHAR; // store the format character
                 } else if ( lmod == 'l') {
                     pformatList[numFormats] = FMT_PLONG; // store the format character
                 } else if ( lmod == 'h') {
                     pformatList[numFormats] = FMT_PSHORT; // store the format character
                 } else{
                     pformatList[numFormats] = FMT_PINT;
                 }
                 numFormats++;
                 index++; // skip the format character
                 break;
            case 'm' :
                 // Does not represent an argument in the call stack
                 index++; // skip the format character
                 continue;
            default:
                printf("failed to recognize format string [");
                for (;start<index; start++) { printf("%c", format[start]); }
                puts("]");
                break;
            }
        } else {
            index++; // move past this character
        }
    }

    return numFormats;
}

unsigned int
check_integer_format(const char format)
{
    unsigned int  retValue = 0; // default failure
    switch( format) {
        case FMT_CHAR  :
        case FMT_SHORT :
        case FMT_INT   :
            retValue = 1;
            break;
    }
    return retValue;
}



inline int snprintf_s_i(char *dest, rsize_t dmax, const char *format, int a)
{
    char pformatList[MAX_FORMAT_ELEMENTS];
    unsigned int index = 0;

    // Determine the number of format options in the format string
    unsigned int  nfo = parse_format(format, &pformatList[0], MAX_FORMAT_ELEMENTS);

    // Check that there are not too many format options
    if ( nfo != 1 ) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESBADFMT);
    }
    // Check that the format is for an integer type
    if ( check_integer_format(pformatList[index]) == 0) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    return snprintf(dest, dmax, format, a);
}



inline int snprintf_s_l(char *dest, rsize_t dmax, const char *format, long a)
{
    char pformatList[MAX_FORMAT_ELEMENTS];
    unsigned int index = 0;

    // Determine the number of format options in the format string
    unsigned int  nfo = parse_format(format, &pformatList[0], MAX_FORMAT_ELEMENTS);

    // Check that there are not too many format options
    if ( nfo != 1 ) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESBADFMT);
    }
    // Check that the format is for an long type
    if ( CHK_FORMAT(FMT_LONG, pformatList[index]) == 0) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    return snprintf(dest, dmax, format, a);
}


inline int snprintf_s_si(char *dest, rsize_t dmax, const char *format, char *s, int a)
{
    char pformatList[MAX_FORMAT_ELEMENTS];
    unsigned int index = 0;

    // Determine the number of format options in the format string
    unsigned int  nfo = parse_format(format, &pformatList[0], MAX_FORMAT_ELEMENTS);

    // Check that there are not too many format options
    if ( nfo != 2 ) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESBADFMT);
    }
    // Check first format is of string type
    if ( CHK_FORMAT(FMT_STRING, pformatList[index]) == 0) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    // Check that the format is for an integer type
    if ( check_integer_format(pformatList[index]) == 0) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    return snprintf(dest, dmax, format, s, a);
}


inline int snprintf_s_sl(char *dest, rsize_t dmax, const char *format, char *s, long a)
{
    char pformatList[MAX_FORMAT_ELEMENTS];
    unsigned int index = 0;

    // Determine the number of format options in the format string
    unsigned int  nfo = parse_format(format, &pformatList[0], MAX_FORMAT_ELEMENTS);

    // Check that there are not too many format options
    if ( nfo != 2 ) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESBADFMT);
    }
    // Check first format is of string type
    if ( CHK_FORMAT(FMT_STRING, pformatList[index]) == 0) {
        dest[0] = '\0';
        return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    // Check that the format is for an integer type
    if ( CHK_FORMAT(FMT_LONG, pformatList[index]) == 0) {
            dest[0] = '\0';
            return SNPRFNEGATE(ESFMTTYP);
    }
    index++;

    return snprintf(dest, dmax, format, s, a);
}
