#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>

static inline bool my_isspace(char c)
{
    return (c == ' ') || (c == '\t') || (c == '\n');
}

static inline bool my_isdigit(char c)
{
    return (c >= '0') && (c <= '9');
}

static inline bool my_isxdigit(char c)
{
    return ((c >= '0') && (c <= '9'))
        || ((c >= 'a') && (c <= 'f')) || ((c >= 'A') && (c <= 'F'));
}

static int parse_dec(int (*peek)(void *userp),
                     void (*pop)(void *userp),
                     void *userp,
                     long *vp,
                     int max_len)
{
    long v = 0;
    int n = 0;
    int minus = 0;
    char ch;

    if ((*peek)(userp) == '-')
    {
        (*pop)(userp);
        n++;
        minus = 1;
    }

    ch = (*peek)(userp);
    if (!my_isdigit(ch))
        return -1;

    do
    {
        v = v * 10 + ch - '0';
        (*pop)(userp);
        n++;
        ch = (*peek)(userp);
    } while (my_isdigit(ch) && n < max_len);

    *vp = minus ? -v : v;
    return n;
}

static int parse_chars(int (*peek)(void *userp),
                       void (*pop)(void *userp),
                       void *userp,
                       char *vp,
                       bool fake,
                       int max_len)
{
    int n = 0;

    char *pt=vp;

    while (!my_isspace((*peek)(userp)) && n < max_len)
    {
        if(fake==false)
            *(pt++) = (*peek)(userp);

        n++;
        (*pop)(userp);
    } 

    if(fake==false)
        (*pt)='\0';

    return n;
}

static int parse_hex(int (*peek)(void *userp),
                     void (*pop)(void *userp),
                     void *userp,
                     unsigned long *vp,
                     int max_len)
{
    unsigned long v = 0;
    int n = 0;
    char ch;
    
    ch = (*peek)(userp);
    if (!my_isxdigit(ch))
        return -1;

    do
    {
        if (ch >= 'a')
            ch = ch - 'a' + 10;
        else if (ch >= 'A')
            ch = ch - 'A' + 10;
        else
            ch = ch - '0';
        v = v * 16 + ch;
        (*pop)(userp);
        n++;
        ch = (*peek)(userp);
    } while (my_isxdigit(ch) && n < max_len);

    *vp = v;
    return n;
}

static int skip_spaces(int (*peek)(void *userp),
                       void (*pop)(void *userp),
                       void *userp)
{
    int n = 0;
    while (my_isspace((*peek)(userp))) {
        n++;
        (*pop)(userp);
    }
    return n;
}

static int scan(int (*peek)(void *userp),
                void (*pop)(void *userp),
                void *userp,
                const char *fmt,
                va_list ap)
{
    char ch;
    int n = 0;
    int n_chars = 0;
    int r;
    long lval;
    bool skip=false;
    unsigned long ulval;

    while ((ch = *fmt++) != '\0')
    {
        bool literal = false;

        if (ch == '%')
        {
            ch = *fmt++;

            if(ch== '*')  /* We should process this, but not store it in an arguement */
            {
                ch=*fmt++;
                skip=true;
            }
            else
            {
                skip=false;
            }

            int max_len = 0;

            while('0' <= ch && ch <= '9')
            {
                max_len *= 10;
                max_len += ch - '0';
                ch=*fmt++;
            }

            if (max_len == 0) {
                max_len = INT_MAX;
            }

            switch (ch)
            {
                case 'x':
                    n_chars += skip_spaces(peek, pop, userp);
                    if ((r = parse_hex(peek, pop, userp, &ulval, max_len)) >= 0)
                    {
                        if(skip==false)
                        {
                            *(va_arg(ap, unsigned int *)) = ulval;
                            n++;
                        }
                        n_chars += r;
                    }
                    else
                        return n;
                    break;
                case 'd':
                    n_chars += skip_spaces(peek, pop, userp);
                    if ((r = parse_dec(peek, pop, userp, &lval, max_len)) >= 0)
                    {
                        if(skip==false)
                        {
                            *(va_arg(ap, int *)) = lval;
                            n++;
                        }
                        n_chars += r;
                    }
                    else
                        return n;
                    break;
                case 'n':
                    if(skip==false)
                    {
                        *(va_arg(ap, int *)) = n_chars;
                        n++;
                    }
                    break;
                case 'l':
                    n_chars += skip_spaces(peek, pop, userp);
                    ch = *fmt++;
                    switch (ch)
                    {
                        case 'x':
                            if ((r = parse_hex(peek, pop, userp, &ulval, max_len)) >= 0)
                            {
                                if(skip==false)
                                {
                                    *(va_arg(ap, unsigned long *)) = ulval;
                                    n++;
                                }
                                n_chars += r;
                            }
                            else
                                return n;
                            break;
                        case 'd':
                            if ((r = parse_dec(peek, pop, userp, &lval, max_len)) >= 0)
                            {
                                if(skip==false)
                                {
                                    *(va_arg(ap, long *)) = lval;
                                    n++;
                                }    
                                n_chars += r;
                            }
                            else
                                return n;
                            break;
                        case '\0':
                            return n;
                        default:
                            literal = true;
                            break;
                    }
                    break;
                case 's':
                    n_chars += skip_spaces(peek, pop, userp);
                    n_chars += parse_chars(peek,pop, userp,skip?0:va_arg(ap, char *), skip, max_len);
                    if(skip==false)
                    {
                        n++;
                    }
                    break;
                case '\0':
                    return n;
                default:
                    literal = true;
                    break;
            }
        } else
            literal = true;

        if (literal)
        {
            n_chars += skip_spaces(peek, pop, userp);
            if ((*peek)(userp) != ch)
                continue;
            else
            {
                (*pop)(userp);
                n_chars++;
            }
        }
    }
    return n;
}

static int sspeek(void *userp)
{
    return **((char **)userp);
}

static void sspop(void *userp)
{
    (*((char **)userp))++;
}

int sscanf(const char *s, const char *fmt, ...)
{
    int r;
    va_list ap;
    const char *p;

    p = s;
    va_start(ap, fmt);
    r = scan(sspeek, sspop, &p, fmt, ap);
    va_end(ap);
    return r;
}
