/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 Nicolas Pennequin, Dan Everton, Matthias Mohr
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

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

static const char * test_wps = "# Comment on this WPS\n"
"%xl|y|play.bmp|0|2|\n"
"%xl|p|pause.bmp|0|2|\n"
"%xl|f|ffwd.bmp|0|2|\n"
"%xl|v|rev.bmp|0|2|\n"
"%xl|h|hold.bmp|20|2|\n"
"%xl|b|blank.bmp|20|2|\n"
"%xl|r|rep.bmp|141|18|\n"
"%xl|s|reps.bmp|141|18|\n"
"%xl|t|rep1.bmp|141|18|\n"
"%xl|x|repb.bmp|141|18|\n"
"%xl|A|be.bmp|136|2|\n"
"%xl|B|bqe.bmp|136|2|\n"
"%xl|C|bh.bmp|136|2|\n"
"%xl|D|bqf.bmp|136|2|\n"
"%xl|E|bf.bmp|136|2|\n"
"%xl|F|bfp.bmp|136|2|\n"
"%x|V|vol.bmp|60|109|\n"
"%x|g|bg.bmp|0|0|\n"
"%wd\n"
"%ac%?mp<Now Stopped|Now Playing %?ps<(S)|>|Now Paused %?ps<(S)|>|Now Playing %?ps<(S)|>|Now Playing %?ps<(S)|>>\n"
"\n"
"%al%pp of %pe\n"
"\n"
"%s%ac%?it<%it|%fn>\n"
"%s%t3%ac%?ia<%ia|%d2>\n"
"%s%t3%ac%?id<%id|%d1>\n"
"%s%t4%ia;%s%it;%t3%pc %pr\n"
"\n"
"%pb\n"
"%al%pc %ac      %pv %ar-%pr\n"
"%?mp<|%xdy|%xdp|%xdf|%xdv>\n"
"%?mh<%xdh|%xdb>\n"
"%?bp<%xdF|%?bl<%xdA|%xdB|%xdC|%xdD|%xdE>>\n"
"%?mm<%xdx|%xdr|%xdt|%xds>";

#define MAX_TOKENS 512

enum wps_token_type {

    /* Markers */
    WPS_TOKEN_CHARACTER,
    WPS_TOKEN_STRING,
    WPS_TOKEN_EOL,

    /* Alignment */
    WPS_TOKEN_ALIGNMENT_LEFT,
    WPS_TOKEN_ALIGNMENT_CENTER,
    WPS_TOKEN_ALIGNMENT_RIGHT,

    /* Scrolling */
    WPS_TOKEN_SCROLL,

    /* Alternating sublines */
    WPS_TOKEN_SUBLINE_SEPARATOR,
    WPS_TOKEN_SUBLINE_TIMEOUT,

    /* Battery */
    WPS_TOKEN_BATTERY_PERCENT,
    WPS_TOKEN_BATTERY_VOLTS,
    WPS_TOKEN_BATTERY_TIME,
    WPS_TOKEN_BATTERY_CHARGER_CONNECTED,
    WPS_TOKEN_BATTERY_CHARGING,
    WPS_TOKEN_BATTERY_SLEEPTIME,

    /* Time */
    WPS_TOKEN_RTC,

    /* Conditional */
    WPS_TOKEN_CONDITIONAL,
    WPS_TOKEN_CONDITIONAL_START,
    WPS_TOKEN_CONDITIONAL_OPTION,
    WPS_TOKEN_CONDITIONAL_END,

    /* Database */
    WPS_TOKEN_DATABASE_PLAYCOUNT,
    WPS_TOKEN_DATABASE_RATING,

    /* Directory */
    WPS_TOKEN_DIRECTORY_1,
    WPS_TOKEN_DIRECTORY_2,
    WPS_TOKEN_DIRECTORY_3,

    /* File */
    WPS_TOKEN_FILE_BITRATE,
    WPS_TOKEN_FILE_CODEC,
    WPS_TOKEN_FILE_FREQUENCY,
    WPS_TOKEN_FILE_NAME,
    WPS_TOKEN_FILE_NAME_WITH_EXTENSION,
    WPS_TOKEN_FILE_PATH,
    WPS_TOKEN_FILE_SIZE,
    WPS_TOKEN_FILE_VBR,

    /* Image */
    WPS_TOKEN_IMAGE_BACKDROP,
    WPS_TOKEN_IMAGE_PROGRESS_BAR,
    WPS_TOKEN_IMAGE_PRELOAD,
    WPS_TOKEN_IMAGE_PRELOAD_DISPLAY,
    WPS_TOKEN_IMAGE_DISPLAY,

    /* Metadata */
    WPS_TOKEN_METADATA_ARTIST,
    WPS_TOKEN_METADATA_COMPOSER,
    WPS_TOKEN_METADATA_ALBUM,
    WPS_TOKEN_METADATA_GENRE,
    WPS_TOKEN_METADATA_TRACK_NUMBER,
    WPS_TOKEN_METADATA_TRACK_TITLE,
    WPS_TOKEN_METADATA_VERSION,
    WPS_TOKEN_METADATA_YEAR,

    /* Mode */
    WPS_TOKEN_MODE_REPEAT,
    WPS_TOKEN_MODE_PLAYBACK,
    WPS_TOKEN_MODE_HOLD,
    WPS_TOKEN_MODE_REMOTE_HOLD,

    /* Playlist */
    WPS_TOKEN_PLAYLIST_PROGRESSBAR,
    WPS_TOKEN_PLAYLIST_PROGRESSBAR_TIME,
    WPS_TOKEN_PLAYLIST_TIME_IN_SONG,
    WPS_TOKEN_PLAYLIST_ENTRIES,
    WPS_TOKEN_PLAYLIST_PEAKMETER,
    WPS_TOKEN_PLAYLIST_NAME,
    WPS_TOKEN_PLAYLIST_POSITION,
    WPS_TOKEN_PLAYLIST_TIME_REMAINING,
    WPS_TOKEN_PLAYLIST_SHUFFLE,
    WPS_TOKEN_PLAYLIST_TOTAL_TRACK_TIME,
    WPS_TOKEN_PLAYLIST_VOLUME,

    /* Statusbar */
    WPS_TOKEN_STATUSBAR_ENABLED,
    WPS_TOKEN_STATUSBAR_DISABLED,

    /* Virtual LED */
    WPS_TOKEN_VLED_HDD,
};

struct wps_token {
    enum wps_token_type type;

    union {
        char *s;
        char c;
        int i;
    } value;

};

typedef int (*wps_tag_parse_func)(const char* wps_token);

/* prototypes of all special parse functions */
static int parse_image_preload(const char* wps_token);
static int parse_image_display_preloaded(const char* wps_token);
static int eat_eol(const char* wps_token);

struct wps_tag {
    const char name[3];
    enum wps_token_type type;
    wps_tag_parse_func parse_func;
};

/* array of available tags - those with more characters have to go first
 * (e.g. "xl" and "xd" before "x") */
static struct wps_tag all_tags[] = {

    { "ac",  WPS_TOKEN_ALIGNMENT_CENTER, NULL },
    { "al",  WPS_TOKEN_ALIGNMENT_LEFT, NULL },
    { "ar",  WPS_TOKEN_ALIGNMENT_RIGHT, NULL },

        /* #if defined(HAVE_CHARGE_CTRL) || defined (HAVE_CHARGE_STATE) || CONFIG_BATTERY == BATT_LIION2200 */
    { "bc",  WPS_TOKEN_BATTERY_CHARGING, NULL },
        /* #endif */
    { "bl",  WPS_TOKEN_BATTERY_PERCENT, NULL },
    { "bs",  WPS_TOKEN_BATTERY_SLEEPTIME, NULL },
    { "bt",  WPS_TOKEN_BATTERY_TIME, NULL },
        /* #ifdef HAVE_CHARGING */
    { "bp",  WPS_TOKEN_BATTERY_CHARGER_CONNECTED, NULL },
        /* #endif */
    { "bv",  WPS_TOKEN_BATTERY_VOLTS, NULL },

    { "c",   WPS_TOKEN_RTC, NULL },

    { "d1",  WPS_TOKEN_DIRECTORY_1, NULL },
    { "D1",  WPS_TOKEN_DIRECTORY_1, NULL },
    { "d2",  WPS_TOKEN_DIRECTORY_2, NULL },
    { "D2",  WPS_TOKEN_DIRECTORY_2, NULL },
    { "d3",  WPS_TOKEN_DIRECTORY_3, NULL },
    { "D3",  WPS_TOKEN_DIRECTORY_3, NULL },

    { "fb",  WPS_TOKEN_FILE_BITRATE, NULL },
    { "Fb",  WPS_TOKEN_FILE_BITRATE, NULL },
    { "fc",  WPS_TOKEN_FILE_CODEC, NULL },
    { "Fc",  WPS_TOKEN_FILE_CODEC, NULL },
    { "ff",  WPS_TOKEN_FILE_FREQUENCY, NULL },
    { "Ff",  WPS_TOKEN_FILE_FREQUENCY, NULL },
    { "fm",  WPS_TOKEN_FILE_NAME_WITH_EXTENSION, NULL },
    { "Fm",  WPS_TOKEN_FILE_NAME_WITH_EXTENSION, NULL },
    { "fn",  WPS_TOKEN_FILE_NAME, NULL },
    { "Fn",  WPS_TOKEN_FILE_NAME, NULL },
    { "fp",  WPS_TOKEN_FILE_PATH, NULL },
    { "Fp",  WPS_TOKEN_FILE_PATH, NULL },
    { "fs",  WPS_TOKEN_FILE_SIZE, NULL },
    { "Fs",  WPS_TOKEN_FILE_SIZE, NULL },
    { "fv",  WPS_TOKEN_FILE_VBR, NULL },
    { "Fv",  WPS_TOKEN_FILE_VBR, NULL },

    { "ia",  WPS_TOKEN_METADATA_ARTIST, NULL },
    { "Ia",  WPS_TOKEN_METADATA_ARTIST, NULL },
    { "ic",  WPS_TOKEN_METADATA_COMPOSER, NULL },
    { "Ic",  WPS_TOKEN_METADATA_COMPOSER, NULL },
    { "id",  WPS_TOKEN_METADATA_ALBUM, NULL },
    { "Id",  WPS_TOKEN_METADATA_ALBUM, NULL },
    { "ig",  WPS_TOKEN_METADATA_GENRE, NULL },
    { "Ig",  WPS_TOKEN_METADATA_GENRE, NULL },
    { "in",  WPS_TOKEN_METADATA_TRACK_NUMBER, NULL },
    { "In",  WPS_TOKEN_METADATA_TRACK_NUMBER, NULL },
    { "it",  WPS_TOKEN_METADATA_TRACK_TITLE, NULL },
    { "It",  WPS_TOKEN_METADATA_TRACK_TITLE, NULL },
    { "iv",  WPS_TOKEN_METADATA_VERSION, NULL },
    { "Iv",  WPS_TOKEN_METADATA_VERSION, NULL },
    { "iy",  WPS_TOKEN_METADATA_YEAR, NULL },
    { "Iy",  WPS_TOKEN_METADATA_YEAR, NULL },

    { "lh",  WPS_TOKEN_VLED_HDD, NULL },

    { "mh",  WPS_TOKEN_MODE_HOLD, NULL },
    { "mm",  WPS_TOKEN_MODE_REPEAT, NULL },
    { "mr",  WPS_TOKEN_MODE_REMOTE_HOLD, NULL },
    { "mp",  WPS_TOKEN_MODE_PLAYBACK, NULL },

    { "pb",  WPS_TOKEN_PLAYLIST_PROGRESSBAR, NULL },
    { "pc",  WPS_TOKEN_PLAYLIST_TIME_IN_SONG, NULL },
    { "pe",  WPS_TOKEN_PLAYLIST_ENTRIES, NULL },
    { "pf",  WPS_TOKEN_PLAYLIST_PROGRESSBAR_TIME, NULL },
    { "pm",  WPS_TOKEN_PLAYLIST_PEAKMETER, NULL },
    { "pn",  WPS_TOKEN_PLAYLIST_NAME, NULL },
    { "pp",  WPS_TOKEN_PLAYLIST_POSITION, NULL },
    { "pr",  WPS_TOKEN_PLAYLIST_TIME_REMAINING, NULL },
    { "ps",  WPS_TOKEN_PLAYLIST_SHUFFLE, NULL },
    { "pt",  WPS_TOKEN_PLAYLIST_TOTAL_TRACK_TIME, NULL },
    { "pv",  WPS_TOKEN_PLAYLIST_VOLUME, NULL },

    { "rp",  WPS_TOKEN_DATABASE_PLAYCOUNT, NULL },
    { "rr",  WPS_TOKEN_DATABASE_RATING, NULL },

    { "s",   WPS_TOKEN_SCROLL, NULL },
    { "t",   WPS_TOKEN_SUBLINE_TIMEOUT, NULL },

    { "we",  WPS_TOKEN_STATUSBAR_ENABLED, eat_eol },
    { "wd",  WPS_TOKEN_STATUSBAR_DISABLED, eat_eol },

    { "P",   WPS_TOKEN_IMAGE_PROGRESS_BAR, parse_image_preload },
    { "xd",  WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, parse_image_display_preloaded },
    { "xl",  WPS_TOKEN_IMAGE_PRELOAD, parse_image_preload },
    { "x",   WPS_TOKEN_IMAGE_DISPLAY, parse_image_preload },
    { "X",   WPS_TOKEN_IMAGE_BACKDROP, parse_image_preload },
};

/* Used to store strings read from the WPS file */
static char wps_strings[2048];
static char *wps_current_string = wps_strings;

/* tokens from the WPS */
static struct wps_token tokens[MAX_TOKENS];
static int num_tokens;


/* Parse a token from the given string. Return the length read */
static int parse_token(const char* wps_token)
{
    int skip = 0;
    int i = 0;

    switch(*wps_token)
    {

        case '%':
        case '<':
        case '|':
        case '>':
        case ';':
            /* escaped characters */
            tokens[num_tokens].type = WPS_TOKEN_CHARACTER;
            tokens[num_tokens].value.c = *wps_token;
            num_tokens++;
            skip++;
            break;

        case '?':
            /* conditional tag */
            tokens[num_tokens++].type = WPS_TOKEN_CONDITIONAL;
            *wps_token++;
            skip++;

        default:
            while (strncmp(wps_token, all_tags[i].name, strlen(all_tags[i].name)))
            {
                i++;
            }
            tokens[num_tokens].type = all_tags[i].type;
            skip += strlen(all_tags[i].name);

            if (all_tags[i].parse_func) {
                skip += all_tags[i].parse_func(wps_token + strlen(all_tags[i].name));
            }

            num_tokens++;
            break;
    }

    return skip;
}

static int parse_image_preload(const char* wps_token)
{
    return eat_eol(wps_token);
}

static int parse_image_display_preloaded(const char* wps_token)
{
    tokens[num_tokens].value.c = *wps_token;
    return 1;
}

static int eat_eol(const char* wps_token)
{
    int skip = 0;
    while(wps_token && *wps_token++ != '\n') {
        skip++;
    }
    skip++; /* also skip newline */
    return skip;
}

void wps_parse(const char* wps_string)
{
    while(*wps_string) {

        switch(*wps_string++) {

            /* Token */
            case '%':
                wps_string += parse_token(wps_string);
                break;

            /* Alternating sublines separator */
            case ';':
                tokens[num_tokens++].type = WPS_TOKEN_SUBLINE_SEPARATOR;
                break;

            /* Conditional list start */
            case '<':
                tokens[num_tokens++].type = WPS_TOKEN_CONDITIONAL_START;
                break;

            /* Conditional list end */
            case '>':
                tokens[num_tokens++].type = WPS_TOKEN_CONDITIONAL_END;
                break;

            /* Conditional list option */
            case '|':
                tokens[num_tokens++].type = WPS_TOKEN_CONDITIONAL_OPTION;
                break;

            /* Comment */
            case '#':
                while(*wps_string != '\n') {
                    wps_string++;
                }
                wps_string++; /* Skip newline as well */
                break;

            /* End of this line */
            case '\n':
                tokens[num_tokens++].type = WPS_TOKEN_EOL;
                break;

            default:
                tokens[num_tokens].type = WPS_TOKEN_STRING;
                tokens[num_tokens].value.s = wps_current_string;
                num_tokens++;

                /* Copy the first byte */
                *wps_current_string++ = *(wps_string - 1);

                /* Scan ahead until we hit something we shouldn't just read */
                while(wps_string &&
                    *wps_string != '%' && *wps_string != '#' &&
                    *wps_string != '<' && *wps_string != '>' &&
                    *wps_string != '|' && *wps_string != '\n') {
                    *wps_current_string++ = *wps_string++;
                }

                /* null terminate the string */
                *wps_current_string++ = '\0';

                break;
        }
    }
}

int main(void)
{
    int i, j;
    int indent = 0;
    char buf[64];

    wps_parse(test_wps);

    /* Dump parsed WPS */
    for(i = 0; i < num_tokens; i++) {
        switch(tokens[i].type) {
            case WPS_TOKEN_CHARACTER:
                snprintf(buf, sizeof(buf), "Character '%c'", tokens[i].value.c);
                break;

            case WPS_TOKEN_STRING:
                snprintf(buf, sizeof(buf), "String '%s'", tokens[i].value.s);
                break;

            case WPS_TOKEN_EOL:
                snprintf(buf, sizeof(buf), "%s", "EOL");
                break;

            case WPS_TOKEN_ALIGNMENT_LEFT:
                snprintf(buf, sizeof(buf), "%s", "align left");
                break;

            case WPS_TOKEN_ALIGNMENT_CENTER:
                snprintf(buf, sizeof(buf), "%s", "align center");
                break;

            case WPS_TOKEN_ALIGNMENT_RIGHT:
                snprintf(buf, sizeof(buf), "%s", "align right");
                break;

            case WPS_TOKEN_CONDITIONAL:
                snprintf(buf, sizeof(buf), "%s", "conditional");
                break;

            case WPS_TOKEN_CONDITIONAL_START:
                snprintf(buf, sizeof(buf), "%s", "conditional start");
                indent++;
                break;

            case WPS_TOKEN_CONDITIONAL_OPTION:
                snprintf(buf, sizeof(buf), "%s", "conditional option");
                break;

            case WPS_TOKEN_CONDITIONAL_END:
                snprintf(buf, sizeof(buf), "%s", "conditional end");
                indent--;
                break;

            case WPS_TOKEN_IMAGE_PRELOAD:
                snprintf(buf, sizeof(buf), "%s", "preload image");
                break;

            case WPS_TOKEN_IMAGE_PRELOAD_DISPLAY:
                snprintf(buf, sizeof(buf), "%s %c", "display preloaded image", tokens[i].value.c);
                break;

            case WPS_TOKEN_IMAGE_DISPLAY:
                snprintf(buf, sizeof(buf), "%s", "display image");
                break;

            case WPS_TOKEN_MODE_HOLD:
                snprintf(buf, sizeof(buf), "%s", "mode hold");
                break;

            case WPS_TOKEN_MODE_REMOTE_HOLD:
                snprintf(buf, sizeof(buf), "%s", "mode remote hold");
                break;

            case WPS_TOKEN_MODE_REPEAT:
                snprintf(buf, sizeof(buf), "%s", "mode repeat");
                break;

            case WPS_TOKEN_MODE_PLAYBACK:
                snprintf(buf, sizeof(buf), "%s", "mode playback");
                break;

            case WPS_TOKEN_STATUSBAR_ENABLED:
                snprintf(buf, sizeof(buf), "%s", "statusbar enable");
                break;

            case WPS_TOKEN_STATUSBAR_DISABLED:
                snprintf(buf, sizeof(buf), "%s", "statusbar disable");
                break;

            case WPS_TOKEN_PLAYLIST_PROGRESSBAR:
            case WPS_TOKEN_PLAYLIST_PROGRESSBAR_TIME:
            case WPS_TOKEN_PLAYLIST_TIME_IN_SONG:
            case WPS_TOKEN_PLAYLIST_ENTRIES:
            case WPS_TOKEN_PLAYLIST_PEAKMETER:
            case WPS_TOKEN_PLAYLIST_NAME:
            case WPS_TOKEN_PLAYLIST_POSITION:
            case WPS_TOKEN_PLAYLIST_TIME_REMAINING:
            case WPS_TOKEN_PLAYLIST_SHUFFLE:
            case WPS_TOKEN_PLAYLIST_TOTAL_TRACK_TIME:
            case WPS_TOKEN_PLAYLIST_VOLUME:
                snprintf(buf, sizeof(buf), "%s", "some playlist info");
                break;

            case WPS_TOKEN_METADATA_ARTIST:
            case WPS_TOKEN_METADATA_COMPOSER:
            case WPS_TOKEN_METADATA_ALBUM:
            case WPS_TOKEN_METADATA_GENRE:
            case WPS_TOKEN_METADATA_TRACK_NUMBER:
            case WPS_TOKEN_METADATA_TRACK_TITLE:
            case WPS_TOKEN_METADATA_VERSION:
            case WPS_TOKEN_METADATA_YEAR:
                snprintf(buf, sizeof(buf), "%s", "some metadata info");
                break;

            case WPS_TOKEN_BATTERY_PERCENT:
            case WPS_TOKEN_BATTERY_VOLTS:
            case WPS_TOKEN_BATTERY_TIME:
            case WPS_TOKEN_BATTERY_CHARGER_CONNECTED:
            case WPS_TOKEN_BATTERY_CHARGING:
                snprintf(buf, sizeof(buf), "%s", "some battery info");
                break;

            case WPS_TOKEN_DIRECTORY_1:
            case WPS_TOKEN_DIRECTORY_2:
            case WPS_TOKEN_DIRECTORY_3:
                snprintf(buf, sizeof(buf), "%s", "directory");
                break;

            case WPS_TOKEN_SCROLL:
                snprintf(buf, sizeof(buf), "%s", "scrolling line");
                break;

            case WPS_TOKEN_SUBLINE_TIMEOUT:
                snprintf(buf, sizeof(buf), "%s", "subline timeout value");
                break;

            case WPS_TOKEN_SUBLINE_SEPARATOR:
                snprintf(buf, sizeof(buf), "%s", "subline separator");
                break;

            default:
                snprintf(buf, sizeof(buf), "%s (code: %d)", "FIXME", tokens[i].type);
                break;
        }

        for(j = 0; j < indent; j++) {
            printf("\t");
        }

        printf("[%03d] = '%s'\n", i, buf);
    }

    return 0;
}

