/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 Dan Everton
 *
 * 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"
"\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,

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

    /* 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;

};

/* Used to store strings read from the WPS file */
static char wps_strings[2048];
static char *wps_current_string = wps_strings;
static struct wps_token tokens[MAX_TOKENS];
static int num_tokens;

/* Parse alignment information (%b) tokens */
static int parse_token_alignment_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {

        case 'l':
            tokens[num_tokens++].type = WPS_TOKEN_ALIGNMENT_LEFT;
            break;

        case 'c':
            tokens[num_tokens++].type = WPS_TOKEN_ALIGNMENT_CENTER;
            break;

        case 'r':
            tokens[num_tokens++].type = WPS_TOKEN_ALIGNMENT_RIGHT;
            break;

        default:
            // DEBUGF("Unknown alignment token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse battery information (%b) tokens */
static int parse_token_battery_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'l':
            tokens[num_tokens++].type = WPS_TOKEN_BATTERY_PERCENT;
            break;

        case 'v':
            tokens[num_tokens++].type = WPS_TOKEN_BATTERY_VOLTS;
            break;

        case 't':
            tokens[num_tokens++].type = WPS_TOKEN_BATTERY_TIME;
            break;

#ifdef HAVE_CHARGING
        case 'p':
            tokens[num_tokens++].type = WPS_TOKEN_BATTERY_CHARGER_CONNECTED;
            break;
#endif

#if defined(HAVE_CHARGE_CTRL) || \
    defined (HAVE_CHARGE_STATE) || \
    CONFIG_BATTERY == BATT_LIION2200
        case 'c':
            tokens[num_tokens++].type = WPS_TOKEN_BATTERY_CHARGING;
            break;
#endif

        default:
            // DEBUGF("Unknown battery token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse database information (%r) tokens */
static int parse_token_database_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        
        case 'p':
            tokens[num_tokens++].type = WPS_TOKEN_DATABASE_PLAYCOUNT;
            break;

        case 'r':
            tokens[num_tokens++].type = WPS_TOKEN_DATABASE_RATING;
            break;

        default:
            // DEBUGF("Unknown database information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse directory information (%d) tokens */
static int parse_token_directory_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {

        case '1':
            tokens[num_tokens++].type = WPS_TOKEN_DIRECTORY_1;
            break;

        case '2':
            tokens[num_tokens++].type = WPS_TOKEN_DIRECTORY_2;
            break;

        case '3':
            tokens[num_tokens++].type = WPS_TOKEN_DIRECTORY_3;
            break;

        default:
            // DEBUGF("Unknown directory information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse file information (%f) tokens */
static int parse_token_file_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {

        case 'b':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_BITRATE;
            break;

        case 'c':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_CODEC;
            break;

        case 'f':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_FREQUENCY;
            break;

        case 'm':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_NAME_WITH_EXTENSION;
            break;

        case 'n':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_NAME;
            break;

        case 'p':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_PATH;
            break;

        case 's':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_SIZE;
            break;

        case 'v':
            tokens[num_tokens++].type = WPS_TOKEN_FILE_VBR;
            break;

        default:
            // DEBUGF("Unknown file information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse image (%X, %P, %x) tokens */
static int parse_token_image_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'X':
            tokens[num_tokens++].type = WPS_TOKEN_IMAGE_BACKDROP;
            break;

        case 'P':
            tokens[num_tokens++].type = WPS_TOKEN_IMAGE_PROGRESS_BAR;
            break;

        case 'x':
            {
                skip = 1;

                switch(*wps_token++) {
                    case '|':
                        tokens[num_tokens++].type = WPS_TOKEN_IMAGE_DISPLAY;
                        skip++;
                        break;

                    case 'd':
                        tokens[num_tokens].type = WPS_TOKEN_IMAGE_PRELOAD_DISPLAY;
                        tokens[num_tokens].value.c = *wps_token;
                        num_tokens++;
                        skip++;
                        break;

                    case 'l':
                        tokens[num_tokens++].type = WPS_TOKEN_IMAGE_PRELOAD;
                        skip++;
                        break;
                }
            }
            break;

        default:
            // DEBUGF("Unknown image information token %c\n", *(wps_token - 1));
            break;
    }

    /* Read to end of line */
    while(wps_token && *wps_token++ != '\n') {
        skip++;
    }

    return skip;
}

/* Parse metadata (%i) information tokens. */
static int parse_token_metadata_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'a':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_ARTIST;
            break;

        case 'c':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_COMPOSER;
            break;

        case 'd':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_ALBUM;
            break;

        case 'g':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_GENRE;
            break;

        case 'n':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_TRACK_NUMBER;
            break;

        case 't':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_TRACK_TITLE;
            break;

        case 'v':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_VERSION;
            break;

        case 'y':
            tokens[num_tokens++].type = WPS_TOKEN_METADATA_YEAR;
            break;

        default:
            // DEBUGF("Unknown metadata information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

/* Parse mode (%i) information tokens. */
static int parse_token_mode_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'h':
            tokens[num_tokens++].type = WPS_TOKEN_MODE_HOLD;
            break;

        case 'r':
            tokens[num_tokens++].type = WPS_TOKEN_MODE_REMOTE_HOLD;
            break;

        case 'm':
            tokens[num_tokens++].type = WPS_TOKEN_MODE_REPEAT;
            break;

        case 'p':
            tokens[num_tokens++].type = WPS_TOKEN_MODE_PLAYBACK;
            break;
            
        default:
            // DEBUGF("Unknown mode information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}


/* Parse playlist information (%p) tokens */
static int parse_token_playlist_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'b':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_PROGRESSBAR;
            break;

        case 'f':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_PROGRESSBAR_TIME;
            break;

        case 'c':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_TIME_IN_SONG;
            break;

        case 'e':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_ENTRIES;
            break;

        case 'm':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_PEAKMETER;
            break;

        case 'n':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_NAME;
            break;

        case 'p':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_POSITION;
            break;

        case 'r':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_TIME_REMAINING;
            break;

        case 's':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_SHUFFLE;
            break;

        case 't':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_TOTAL_TRACK_TIME;
            break;

        case 'v':
            tokens[num_tokens++].type = WPS_TOKEN_PLAYLIST_VOLUME;
            break;

        default:
            // DEBUGF("Unknown playlist information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}


/* Parse statusbar (%w) control tokens. */
static int parse_token_statusbar_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {
        case 'e':
            tokens[num_tokens++].type = WPS_TOKEN_STATUSBAR_ENABLED;
            break;

        case 'd':
            tokens[num_tokens++].type = WPS_TOKEN_STATUSBAR_DISABLED;
            break;

        default:
            // DEBUGF("Unknown statusbar information token %c\n", *(wps_token - 1));
            break;
    }

    /* Read to end of line */
    while(wps_token && *wps_token++ != '\n') {
        skip++;
    }
    
    return skip;
}

/* Parse virtual LED (%l) control tokens. */
static int parse_token_vled_info(const char* wps_token)
{
    int skip = 0;

    switch(*wps_token++) {

        case 'h':
            tokens[num_tokens++].type = WPS_TOKEN_VLED_HDD;
            break;

        default:
            // DEBUGF("Unknown vled information token %c\n", *(wps_token - 1));
            break;
    }

    return skip;
}

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

reparse:

    switch(*wps_token++) {

        /* Conditional */
        case '?':
            tokens[num_tokens++].type = WPS_TOKEN_CONDITIONAL;
            skip++;
            goto reparse;

        /* Alignment information */
        case 'a':
            skip += parse_token_alignment_info(wps_token);
            break;

        /* Battery information */
        case 'b':
            skip += parse_token_battery_info(wps_token);
            break;

        /* Directory information */
        case 'd':
            skip += parse_token_directory_info(wps_token);
            break;

        /* File information */
        case 'f':
            skip += parse_token_file_info(wps_token);
            break;

        /* Image information */
        case 'X':
        case 'P':
        case 'x':
            skip += parse_token_image_info(wps_token - 1);
            break;
            
        /* Metadata information */
        case 'i':
            skip += parse_token_metadata_info(wps_token);
            break;

        /* VLED information */
        case 'l':
            skip += parse_token_vled_info(wps_token);
            break;

        /* Mode information */
        case 'm':
            skip += parse_token_mode_info(wps_token);
            break;

        /* Playlist information */
        case 'p':
            skip += parse_token_playlist_info(wps_token);
            break;

        /* Runtime database information */
        case 'r':
            skip += parse_token_database_info(wps_token);
            break;

        /* Statusbar information */
        case 'w':
            skip += parse_token_statusbar_info(wps_token);
            break;

        /* Escaped characters */
        case '%':
        case '<':
        case '|':
        case '>':
        case ';':
            tokens[num_tokens].type = WPS_TOKEN_CHARACTER;
            tokens[num_tokens].value.c = *(wps_token - 1);
            num_tokens++;
            break;

        default:
            // printf("Ignoring unknown token %c found in WPS.\n", *offset);
            break;
    }

    return skip;
}

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

        switch(*wps_string++) {

            /* Token */
            case '%':
                wps_string += parse_token(wps_string);
                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++;
                }

                /* nul 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;
                
            default:
                snprintf(buf, sizeof(buf), "%s", "FIXME");
                break;
        }
        
        for(j = 0; j < indent; j++) {
            printf("\t");
        }
        
        printf("[%03d] = '%s'\n", i, buf);
    }

    return 0;
}

