/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006       Matthias Mohr  (Massa)
 * based on code written by Dan Everton    (safetydan)
 *
 * 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.
 *
 * TODOs:
 *   - handle conditionals completely correct
 *     (problems when one argument contains more than one token)
 *   - handle sublines (including %t)
 *   - create wps execution after parsing
 *   - make the code much easier and cleaner
 *
 ****************************************************************************/

#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
#define MAX_CONDARGS    (MAX_TOKENS * 3)

#ifdef _DEBUG
#define LOGF(x)       printf x
#else
#define LOGF(x)
#endif

enum wps_token_type {
    /* Dummy, for unset tokens */
    WPS_TOK_NONE = 0,

    /* Markers */
    /* TODO: do we still need those? */
    WPS_TOK_STRING,
    WPS_TOK_EOL,

    /* Alignment */
    WPS_TOK_ALIGN_LEFT,
    WPS_TOK_ALIGN_CENTER,
    WPS_TOK_ALIGN_RIGHT,

    /* Battery */
    WPS_TOK_BATT_PERCENT,
    WPS_TOK_BATT_VOLTS,
    WPS_TOK_BATT_TIME,
    WPS_TOK_BATT_CHARGER_CONNECTED,
    WPS_TOK_BATT_CHARGING,
    WPS_TOK_BATT_SLEEPTIME,

    /* Database */
    WPS_TOK_DATABASE_PLAYCOUNT,
    WPS_TOK_DATABASE_RATING,

    /* Directory */
    WPS_TOK_DIRECTORY_1,
    WPS_TOK_DIRECTORY_2,
    WPS_TOK_DIRECTORY_3,

    /* File */
    WPS_TOK_FILE_BITRATE,
    WPS_TOK_FILE_CODEC,
    WPS_TOK_FILE_FREQUENCY,
    WPS_TOK_FILE_NAME,
    WPS_TOK_FILE_NAME_WITH_EXTENSION,
    WPS_TOK_FILE_PATH,
    WPS_TOK_FILE_SIZE,
    WPS_TOK_FILE_VBR,

    /* Image */
    WPS_TOK_IMAGE_BACKDROP,
    WPS_TOK_IMAGE_PROGRESS_BAR,
    WPS_TOK_IMAGE_PRELOAD,
    WPS_TOK_IMAGE_PRELOAD_DISPLAY,
    WPS_TOK_IMAGE_DISPLAY,

    /* Metadata */
    WPS_TOK_METADATA_ARTIST,
    WPS_TOK_METADATA_COMPOSER,
    WPS_TOK_METADATA_ALBUM,
    WPS_TOK_METADATA_GENRE,
    WPS_TOK_METADATA_TRACK_NUMBER,
    WPS_TOK_METADATA_TRACK_TITLE,
    WPS_TOK_METADATA_VERSION,
    WPS_TOK_METADATA_YEAR,

    /* Mode */
    WPS_TOK_MODE_REPEAT,
    WPS_TOK_MODE_PLAYBACK,
    WPS_TOK_MODE_HOLD,
    WPS_TOK_MODE_REMOTE_HOLD,

    /* Playlist */
    WPS_TOK_PL_PROGRESSBAR,
    WPS_TOK_PL_PROGRESSBAR_TIME,
    WPS_TOK_PL_TIME_IN_SONG,
    WPS_TOK_PL_ENTRIES,
    WPS_TOK_PL_PEAKMETER,
    WPS_TOK_PL_NAME,
    WPS_TOK_PL_POSITION,
    WPS_TOK_PL_TIME_REMAINING,
    WPS_TOK_PL_SHUFFLE,
    WPS_TOK_PL_TOTAL_TRACK_TIME,
    WPS_TOK_PL_VOLUME,

    /* Statusbar */
    WPS_TOK_STATUSBAR_ENABLED,
    WPS_TOK_STATUSBAR_DISABLED,

    /* Virtual LED */
    WPS_TOK_VLED_HDD,

    /* Realtime Clock */
    WPS_TOK_RTC,

    /* Subline tags */
    WPS_TOK_SCROLLINE,
    WPS_TOK_TIMEOUT,
};


/* combineable flags for tokens: */
#define WPS_TOKFLAG_NONE          0
#define WPS_TOKFLAG_EATLINE       1  /* must be alone at a line (e.g. %xl) */
#define WPS_TOKFLAG_VALIDFORLINE  2  /* works until end of (sub)line (e.g. %s) */
#define WPS_TOKFLAG_NOCOND        4  /* not allowed as conditional */


/* pointerdefinition to parse and action functions */
struct wps_token;
typedef int (*wps_tag_parse_func)(    struct wps_token* token,
                                      char* tag_str);
typedef int (*wps_tag_action_func)(   const struct wps_token* token,
                                      bool preload);

/* prototypes of all parse and action functions */
static int parse_tag_default(         struct wps_token* token,
                                      char* tag_str);
static int parse_cond_default(        struct wps_token* token,
                                      char* tag_str);
static int parse_tag_rtc(             struct wps_token* token,
                                      char* tag_str);
static int parse_tag_image_preload_display(
                                      struct wps_token* token,
                                      char* tag_str);

static int action_tag_alignment_info( const struct wps_token* token,
                                      bool preload);
static int action_tag_battery_info(   const struct wps_token* token,
                                      bool preload);
static int action_tag_database_info(  const struct wps_token* token,
                                      bool preload);
static int action_tag_directory_info( const struct wps_token* token,
                                      bool preload);
static int action_tag_file_info(      const struct wps_token* token,
                                      bool preload);
static int action_tag_image_info(     const struct wps_token* token,
                                      bool preload);
static int action_tag_metadata_info(  const struct wps_token* token,
                                      bool preload);
static int action_tag_mode_info(      const struct wps_token* token,
                                      bool preload);
static int action_tag_playlist_info(  const struct wps_token* token,
                                      bool preload);
static int action_tag_rtc(            const struct wps_token* token,
                                      bool preload);
static int action_tag_statusbar_info( const struct wps_token* token,
                                      bool preload);
static int action_tag_vled_info(      const struct wps_token* token,
                                      bool preload);


/* definition of a possible tag */
struct wps_tag {
    const char           tag_name[3];
    char                 nparams;       /* number of parameters for the tag */
    enum wps_token_type  type;          /* token type */
    unsigned short       flags;         /* flags (e.g. eat rest of line) */
    wps_tag_parse_func   func_parse;    /* func to call for parsing */
    wps_tag_action_func  func_preload;  /* func to call during preload phase */
    wps_tag_action_func  func_load;     /* func to call during loading phase */
};


/* definition of a parsed token */
struct wps_token {
    bool            is_valid;        /* TODO: FIXME: remove this flag */
    struct wps_tag *tag;             /* pointer into an entry of all_tags */
    bool            is_conditional;  /* is token a conditional? */
    const char     *params;               /* pointer to params in wps_strings or to
                                        first array index in condargs (which
                                        contains informations about conditional
                                        arguments) */
    int              condidx;
};


/* 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",  0, WPS_TOK_ALIGN_CENTER, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_alignment_info },
    { "al",  0, WPS_TOK_ALIGN_LEFT, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_alignment_info },
    { "ar",  0, WPS_TOK_ALIGN_RIGHT, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_alignment_info },
#if defined(HAVE_CHARGE_CTRL) || defined (HAVE_CHARGE_STATE) || CONFIG_BATTERY == BATT_LIION2200
    { "bc",  0, WPS_TOK_BATT_CHARGING, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_battery_info },
#endif
    { "bl",  0, WPS_TOK_BATT_PERCENT, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_battery_info },
    { "bs",  0, WPS_TOK_BATT_SLEEPTIME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_battery_info },
    { "bt",  0, WPS_TOK_BATT_TIME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_battery_info },
#ifdef HAVE_CHARGING
    { "bp",  0, WPS_TOK_BATT_CHARGER_CONNECTED, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_battery_info },
#endif
    { "bv",  0, WPS_TOK_BATT_VOLTS, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_battery_info },
    { "c",   1, WPS_TOK_RTC, WPS_TOKFLAG_NOCOND,
             parse_tag_rtc,
             NULL,
             action_tag_rtc },
    { "d1",  0, WPS_TOK_DIRECTORY_1, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "D1",  0, WPS_TOK_DIRECTORY_1, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "d2",  0, WPS_TOK_DIRECTORY_2, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "D2",  0, WPS_TOK_DIRECTORY_2, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "d3",  0, WPS_TOK_DIRECTORY_3, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "D3",  0, WPS_TOK_DIRECTORY_3, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_directory_info },
    { "fb",  0, WPS_TOK_FILE_BITRATE, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fb",  0, WPS_TOK_FILE_BITRATE, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fc",  0, WPS_TOK_FILE_CODEC, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fc",  0, WPS_TOK_FILE_CODEC, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "ff",  0, WPS_TOK_FILE_FREQUENCY, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Ff",  0, WPS_TOK_FILE_FREQUENCY, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fm",  0, WPS_TOK_FILE_NAME_WITH_EXTENSION, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fm",  0, WPS_TOK_FILE_NAME_WITH_EXTENSION, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fn",  0, WPS_TOK_FILE_NAME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fn",  0, WPS_TOK_FILE_NAME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fp",  0, WPS_TOK_FILE_PATH, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fp",  0, WPS_TOK_FILE_PATH, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fs",  0, WPS_TOK_FILE_SIZE, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fs",  0, WPS_TOK_FILE_SIZE, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_file_info },
    { "fv",  0, WPS_TOK_FILE_VBR, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_file_info },
    { "Fv",  0, WPS_TOK_FILE_VBR, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_file_info },
    { "ia",  0, WPS_TOK_METADATA_ARTIST, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Ia",  0, WPS_TOK_METADATA_ARTIST, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "ic",  0, WPS_TOK_METADATA_COMPOSER, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Ic",  0, WPS_TOK_METADATA_COMPOSER, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "id",  0, WPS_TOK_METADATA_ALBUM, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Id",  0, WPS_TOK_METADATA_ALBUM, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "ig",  0, WPS_TOK_METADATA_GENRE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Ig",  0, WPS_TOK_METADATA_GENRE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "in",  0, WPS_TOK_METADATA_TRACK_NUMBER, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "In",  0, WPS_TOK_METADATA_TRACK_NUMBER, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "it",  0, WPS_TOK_METADATA_TRACK_TITLE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "It",  0, WPS_TOK_METADATA_TRACK_TITLE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "iv",  0, WPS_TOK_METADATA_VERSION, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Iv",  0, WPS_TOK_METADATA_VERSION, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "iy",  0, WPS_TOK_METADATA_YEAR, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "Iy",  0, WPS_TOK_METADATA_YEAR, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_metadata_info },
    { "lh",  0, WPS_TOK_VLED_HDD, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_vled_info },
    { "mh",  0, WPS_TOK_MODE_HOLD, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_mode_info },
    { "mm",  0, WPS_TOK_MODE_REPEAT, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_mode_info },
    { "mr",  0, WPS_TOK_MODE_REMOTE_HOLD, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_mode_info },
    { "mp",  0, WPS_TOK_MODE_PLAYBACK, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_mode_info },
    { "pb",  3, WPS_TOK_PL_PROGRESSBAR, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pc",  0, WPS_TOK_PL_TIME_IN_SONG, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pe",  0, WPS_TOK_PL_ENTRIES, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pf",  0, WPS_TOK_PL_PROGRESSBAR_TIME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pm",  0, WPS_TOK_PL_PEAKMETER, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pn",  0, WPS_TOK_PL_NAME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pp",  0, WPS_TOK_PL_POSITION, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pr",  0, WPS_TOK_PL_TIME_REMAINING, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "ps",  0, WPS_TOK_PL_SHUFFLE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pt",  0, WPS_TOK_PL_TOTAL_TRACK_TIME, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "pv",  0, WPS_TOK_PL_VOLUME, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_playlist_info },
    { "P",   1, WPS_TOK_IMAGE_PROGRESS_BAR, WPS_TOKFLAG_EATLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             action_tag_image_info,
             NULL },
    { "rp",  0, WPS_TOK_DATABASE_PLAYCOUNT, WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_database_info },
    { "rr",  0, WPS_TOK_DATABASE_RATING, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             action_tag_database_info },
    { "s",   0, WPS_TOK_SCROLLINE, WPS_TOKFLAG_VALIDFORLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             NULL }, /* TODO: here is something missing :-) */
    { "t",   1, WPS_TOK_TIMEOUT, WPS_TOKFLAG_VALIDFORLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             NULL }, /* TODO: here is something missing :-) */
    { "wd",  0, WPS_TOK_STATUSBAR_ENABLED, WPS_TOKFLAG_EATLINE,
             NULL,
             action_tag_statusbar_info,
             NULL },
    { "we",  0, WPS_TOK_STATUSBAR_DISABLED, WPS_TOKFLAG_EATLINE,
             NULL,
             action_tag_statusbar_info,
             NULL },
    { "xd",  1, WPS_TOK_IMAGE_PRELOAD_DISPLAY, WPS_TOKFLAG_NONE | WPS_TOKFLAG_NOCOND,
             parse_tag_image_preload_display,
             NULL,
             action_tag_image_info },
    { "xl",  4, WPS_TOK_IMAGE_PRELOAD, WPS_TOKFLAG_EATLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             action_tag_image_info,
             NULL },
    { "x",   4, WPS_TOK_IMAGE_DISPLAY, WPS_TOKFLAG_EATLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_image_info },
    { "X",   1, WPS_TOK_IMAGE_BACKDROP, WPS_TOKFLAG_EATLINE | WPS_TOKFLAG_NOCOND,
             NULL,
             NULL,
             action_tag_image_info },
    { "",    -1, WPS_TOK_NONE, WPS_TOKFLAG_NONE,
             NULL,
             NULL,
             NULL },
};


/* buffer to store text strings and tag arguments read from the WPS file */
static char wps_strings[2048];
/* pointer to current position in buffer: */
static char *wps_next_string = wps_strings;

/* identified tokens */
static struct wps_token tokens[MAX_TOKENS];
static int num_tokens = 0;

/* indexes in "tokens" for conditional arguments
   (actually the first index for a conditional is
    the number of arguments for that conditional) */
static int condargs[MAX_CONDARGS];
static int next_condarg = 0;


/* search in array "all_tags" for appropriate entry;
 * returns pointer to tag if found, NULL if not */
static struct wps_tag  *wps_find_tag(const char *wps_tag)
{
    struct wps_tag *ptr;

    if (!wps_tag || *wps_tag == '\0')
        return NULL;

    ptr = all_tags;
    while (ptr && ptr->tag_name[0] != '\0') {
        if ( strncmp(wps_tag, ptr->tag_name, strlen( ptr->tag_name )) == 0 )
            break;
        ptr++;
    }

    if (ptr && ptr->tag_name[0] == '\0')
        return NULL;
    return ptr;
}


static void wps_token_init(struct wps_token *token, bool valid)
{
    if (!token)
        return;

    token->is_valid = valid;
    token->tag = NULL;
    token->is_conditional = false;
    token->params = NULL;
    token->condidx = -1;
}


/* creates a new textstring token
 * returns the length read 
 * returns 0 if no appropriate tag has been found (it's a special char) */
static int parse_text(const char* text_start, const char *text_end, int token_num)
{
    int skip;

    skip = text_end - text_start;
    if (skip > 0) {
        char *pos;

        /* TODO: check remaining size of wps_strings */
        strncpy(wps_next_string, text_start, skip);
        wps_next_string[skip] = 0;

        /* remove all %
         * (this were special chars which represent themselve) */
        pos = strchr(wps_next_string, '%');
        while (pos) {
            strcpy(pos, pos + 1);
            if (*pos == '%') /* %% -> % */
                pos++;
            pos = strchr(pos, '%');
        }
        skip = strlen(wps_next_string);

        /* now create a new textstring token: */
        if (token_num < 0) {
            token_num = num_tokens++;
            wps_token_init(&tokens[token_num], true);
        }
        tokens[token_num].is_valid = true;
        tokens[token_num].params = wps_next_string;
        LOGF(("wps_parse: text found: '%s' (num=%d, skip=%d)\n",
               tokens[token_num].params, token_num, skip));
        wps_next_string += skip + 1;
    }

    return skip;
}


/* Parse a tag from the given string
 * (which must point to the first character after the '%').
 * returns the length read 
 * returns 0 if no appropriate tag has been found (it's a special char)
 * returns -1 if something is wrong with the tag */
static int parse_tag(char* tag_str)
{
    int skip = 0;

    wps_token_init(&tokens[num_tokens], false);

    if (*tag_str == '?') {
        /* Conditional */
        tokens[num_tokens].is_conditional = true;
        skip++;
        tag_str++;
    }

    /* get the tag */
    tokens[num_tokens].tag = wps_find_tag(tag_str);
    if (tokens[num_tokens].tag) {
        /* found -> call the parsing function 
         * (either the default function or a
         * tag specific parsing function) */
        int skip_tag;
        int cur_token = num_tokens++;

        tokens[cur_token].is_valid = true;

        skip    += strlen(tokens[cur_token].tag->tag_name);
        tag_str += strlen(tokens[cur_token].tag->tag_name);

        wps_tag_parse_func func_parse = parse_tag_default;
        if (tokens[cur_token].tag->func_parse)
            func_parse = tokens[cur_token].tag->func_parse;
        skip_tag = func_parse(&tokens[cur_token], tag_str);
        if (skip_tag >= 0) {
            skip    += skip_tag;
            tag_str += skip_tag;

            if (tokens[cur_token].tag->flags & WPS_TOKFLAG_EATLINE) {
                /* the tag has to be on it's own line
                 * -> eat the rest of the line */
                while (*tag_str != '\n' && *tag_str != '\0') {
                    tag_str++;
                    skip++;
                }

                /* eat EOL as well */
                if (*tag_str == '\n') {
                    tag_str++;
                    skip++;
                }
            }
        } else
            skip = -1;

        /* TODO: FIXME: remove this flag... */
        if (skip < 0)
            tokens[cur_token].is_valid = false;

        LOGF(("parse_tag: %stag '%s' found (params='%s'; skip=%d, valid=%s, token_num=%d)\n",
              tokens[cur_token].is_conditional ? "conditional " : "",
              tokens[cur_token].tag->tag_name,
              tokens[cur_token].params ? tokens[cur_token].params : "",
              skip,
              tokens[cur_token].is_valid ? "true" : "false",
              cur_token));
    } else {
        /* no appropriate tag found -> do nothing... */
        skip = 0;
    }

    return skip;
}


/* parse the given string and create appropriate tokens
 * until the string is at its end (contains '\0') or
 * the given end_ptr has been reached */
char *wps_parse(char* wps_string, char *end_ptr)
{
    char *texttoken_start = wps_string;
    int texttoken_num = -1;

    if (!end_ptr)
        end_ptr = wps_string + strlen(wps_string);

    /* TODO: check num_tokens >= MAX_TOKENS */
    while (*wps_string != '\0' && wps_string < end_ptr) {
        switch (*wps_string) {
            /* tag (or special char) */
            case '%':
            {
                int skip;
                skip = parse_tag(wps_string + 1);
                if (skip > 0) {
                    /* tag found; this may also end the current textstring token
                     * -> check and add it to the tokens (and start a new one) */ 
                    parse_text(texttoken_start, wps_string, texttoken_num);

                    wps_string += skip;
                    texttoken_start = wps_string + 1;
                    texttoken_num = -1;
                } else {
                    /* skip = 0: no tag found; it seems to be a special
                     *   character which represents itself (e.g. '%|' -> '|')
                     *   -> keep it as is (=add it to the cur. textstring token)
                     * skip < 0: there is something wrong with the tag
                     *   -> ignore it and treat it as normal text atm.
                     *   TODO: some better error handling needed? */
                    wps_string++;
                }
            } break;

            /* Comment */
            case '#':
            {
                /* this may also end the current textstring token
                 * -> check and add it to the tokens */ 
                parse_text(texttoken_start, wps_string, texttoken_num);

                /* TODO: shall we eat the EOL or not?
                 *   (or only if it's alone on a line?) */
                while (*wps_string != '\n' && *wps_string != '\0')
                    wps_string++;

                texttoken_start = wps_string + 1;
                texttoken_num = -1;
            } break;

            /* EOL token */
            case '\n':
            {
                /* this also ends the current textstring token
                 * -> check and add it to the tokens */ 
                parse_text(texttoken_start, wps_string + 1, texttoken_num);
                texttoken_start = wps_string + 1;
                texttoken_num = -1;
            } break;

            /* subline end */
            case ';':
            {
                /* this also ends the current textstring token
                 * -> check and add it to the tokens */ 
                parse_text(texttoken_start, wps_string, texttoken_num);
                texttoken_start = wps_string + 1;
                texttoken_num = -1;

                /* TODO: what else do we need to do here? */
            } break;
        }

        wps_string++;

        if (texttoken_start < wps_string && texttoken_num < 0) {
            /* make a reservation for a new textstring token */
            texttoken_num = num_tokens++;
            wps_token_init(&tokens[texttoken_num], false);
        }
    }

    /* maybe we still have an "open" textstring token
     * -> check and add it to the tokens */
    parse_text(texttoken_start, wps_string, texttoken_num);

    return wps_string;
}


/* get number of conditional arguments */
static int count_cond_arguments(char* tag_str, int *len)
{
    int nparams = 0;
    int level   = 0;
    char* pos;

    if (len)
        *len = 0;

    pos = tag_str;
    while (pos && *pos) {
        switch (*pos) {
            case '<':
            {
                level++;
                if (level == 1)
                    nparams++;
            } break;

            case '|':
            {
                if (level == 1 && *(pos -1) != '%')
                    nparams++;
            } break;

            case '>':
                level--;
                if (level == 0) {
                    /* finished */
                    if (len)
                        *len = pos - tag_str + 1;
                    pos = NULL;
                }
            break;
        }

        if (pos)
            pos++;
    }

    return nparams;
}


/* get next argument string of a conditional
 * returns the end of the string */
static char *get_cond_argument(char* tag_str)
{
    char* pos = tag_str;
    int   level = 0;

    bool  finished = false;
    while (pos && !finished) {
        switch (*pos) {
            case '<': /* another conditional starts */
                level++;
            break;

            case '>': /* conditional ends */
                level--;
                /* our conditional ends (and so does our argument :-) */
                if (level <= 0)
                    finished = true;
            break;

            case '|':
                /* argument ends */
                if (level == 0)
                    finished = true;
            break;

            case '\n':
            case '\0':
                /* line ends (and so does our argument :-) */
                finished = true;
            break;
        }

        if (!finished)
            pos++;
    }

    return pos;
}


/* Default conditional parsing function (if no specific function is needed)
 * it detects the number of parameters itself */
static int parse_cond_default(struct wps_token* token, char* tag_str)
{
    int nparams = 0;
    int skip = 0;
    char *pos;

    if (!token || !token->tag)
        return -1;

    if (!token->is_conditional)
        return -1;

    pos = tag_str;
    if (*pos != '<')
        return -1;

    /* get the number of arguments of the conditional
     * and the number of characters to skip: */
    nparams = count_cond_arguments(pos, &skip);
    if (nparams > 0) {
        int i;
        char* arg_end;

        pos++;

        /* reserve space for the argument indexes */
        token->condidx = next_condarg;
        condargs[next_condarg++] = nparams;
        next_condarg += nparams;

        for (i = 0; i < nparams; i++ ) {
            condargs[token->condidx + 1 + i] = -1;

            arg_end = get_cond_argument(pos);
            if (arg_end > pos) {
                /* argument string found
                 * -> parse it */
                int   cur_token = num_tokens;

                pos = wps_parse(pos, arg_end);
                if (num_tokens > cur_token)
                    condargs[token->condidx + 1 + i] = cur_token;

                if (pos)
                    pos++;
            }
        }
    }

    return skip;
}


/* Default parsing function (if no specific function is needed)
 * it uses the nparams to know the number of parameters
*/
static int parse_tag_default(struct wps_token* token, char* tag_str)
{
    int nparams;
    int i;
    int skip = 0;

    if (!token || !token->tag)
        return -1;

    /* conditional is not allowed... */
    if (token->is_conditional) {
        if (token->tag->flags & WPS_TOKFLAG_NOCOND)
            return -1;

        /* conditional parsing is done in another function */
        return parse_cond_default(token, tag_str);
    }

    /* not allowed... */
    nparams = token->tag->nparams;
    if (!token->is_conditional && nparams < 0)
        return -1;

    token->params = NULL;
    if (nparams > 0) {
        /* get the length of the parameters */
        char* pos = tag_str;
        int   len = 0;
        if (*pos != '|')
            return -1;  /* there is something wrong */

        pos++;
        for (i = 0; i < nparams; i++) {
            pos = strchr(pos, '|');
            if (!pos)
                break;
            pos++;
        }
        if (!pos)
            return -1; /* not enough parameters */

        /* TODO: check remaining size of wps_strings */
        pos--;
        len = pos - tag_str;
        skip += len + 1; /* the '|' does also count */
        strncpy(wps_next_string, tag_str + 1, len);
        wps_next_string[len - 1] = 0;
        token->params = wps_next_string;

        wps_next_string += len;
    }

    return skip;
}


/* Parse information for realtime clock (c) token */
static int parse_tag_rtc(struct wps_token* token, char* tag_str)
{
    int nparams;
    int skip = 0;

    if (!token || !token->tag)
        return -1;

    /* conditional is not allowed... */
    if (token->is_conditional) {
        if (token->tag->flags & WPS_TOKFLAG_NOCOND)
            return -1;

        /* conditional parsing is done in another function */
        return parse_cond_default(token, tag_str);
    }

    /* not allowed... */
    nparams = token->tag->nparams;
    if (!token->is_conditional && nparams < 0)
        return -1;

    /* conditional parsing is done in another function */
    if (token->is_conditional)
        return parse_cond_default(token, tag_str);

    token->params = NULL;
    if (nparams > 0) {
        char *pos = tag_str;
        int   len = 0;

        /* everything until an ending "c"
         * (or end of line) is the parameter */
        while (*pos != 'c' && *pos != '\n' && *pos != '\0')
            pos++;

        /* TODO: check remaining size of wps_strings */
        pos--;
        len = pos - tag_str;
        skip += len + 1; /* the '|' does also count */
        strncpy(wps_next_string, tag_str + 1, len);
        wps_next_string[len - 1] = 0;
        token->params = wps_next_string;

        wps_next_string += len;
    }

    return skip;
}


/* Parse information for preload image display (xd) token */
static int parse_tag_image_preload_display(struct wps_token* token, char* tag_str)
{
    int nparams;
    int skip = 0;

    if (!token || !token->tag)
        return -1;

    /* conditional is not allowed... */
    if (token->is_conditional) {
        if (token->tag->flags & WPS_TOKFLAG_NOCOND)
            return -1;

        /* conditional parsing is done in another function */
        return parse_cond_default(token, tag_str);
    }

    /* not allowed... */
    nparams = token->tag->nparams;
    if (!token->is_conditional && nparams < 0)
        return -1;

    /* conditional parsing is done in another function */
    if (token->is_conditional)
        return parse_cond_default(token, tag_str);

    token->params = NULL;
    if (nparams > 0) {
        /* the next character is the image id */
        if ( (*tag_str >= 'a' && *tag_str <= 'z') ||
             (*tag_str >= 'A' && *tag_str <= 'Z') )

        /* TODO: check remaining size of wps_strings */
        wps_next_string[0] = *tag_str;
        wps_next_string[1] = '\0';

        token->params = wps_next_string;
        wps_next_string += 2;
        skip += 2;
    }

    return skip;
}


/* action for alignment information (ac, al, ar) tokens */
static int action_tag_alignment_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* action for battery information (bc, bl, bt, bp, bv) tokens */
static int action_tag_battery_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* action for database information (rp, rr) tokens */
static int action_tag_database_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* action for directory information (d1, d2, d3) tokens */
static int action_tag_directory_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* action for file information (fb, fc, ff, ff, fm, fn, fp, fs, fv) tokens */
static int action_tag_file_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* action for image (X, P, xd, xl, x) tokens and returns the number of chars to skip */
static int action_tag_image_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* Parse metadata (%i) information tokens. */
static int action_tag_metadata_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* Parse mode (%m) information tokens. */
static int action_tag_mode_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* Parse playlist information (%p) tokens */
static int action_tag_playlist_info(const struct wps_token *token, bool preload)
{
    return 0;
}


static int action_tag_rtc(const struct wps_token* token, bool preload)
{
    return 0;
}


/* Parse statusbar (wd, we) control tokens. */
static int action_tag_statusbar_info(const struct wps_token *token, bool preload)
{
    return 0;
}


/* Parse virtual LED (%l) control tokens. */
static int action_tag_vled_info(const struct wps_token *token, bool preload)
{
    /* nothing to skip */
    return 0;
}


unsigned int read_line(const char *str, char *buf, unsigned int buf_size)
{
    static const char *pos = NULL;
    const char *newpos = NULL;
    unsigned int size = 0;

    *buf = 0;
    if (!pos)
        pos = str;
    if (!pos)
        return 0;

    newpos = strchr(pos, '\n');
    if (!newpos)
        newpos = pos + strlen(pos) - 1;

    size = newpos + 1 - pos;
    if (size > buf_size)
        size = buf_size;
    strncpy(buf, pos, (size_t)size);
    buf[size] = 0;

    pos = newpos + 1;

    return size;
}


int dump_token(const struct wps_token* token, int idx, int level)
{
    int i = 0;
    int nextidx = idx + 1;

    for (i = 0; i < level; i++)
        printf("  ");
    printf("%3.3d: ", idx);

    if (!token->is_valid) {
        printf("invalid token\n");
        return nextidx;
    }

    if (token->tag) {
        printf("%stag '%s'; ",
               token->is_conditional ? "conditional " : "",
               token->tag->tag_name);
        if (token->is_conditional) {
            if (token->condidx >= 0) {
                int condidx = token->condidx;
                int j;

                printf("%d arguments:\n", condargs[condidx]);
                for (j = 1; j <= condargs[condidx]; j++ ) {
                    int newidx;
                    newidx = condargs[condidx + j];
                    if (j > 1) {
                        /* TODO: FIXME: HACK to display missing arguments */
                        if (newidx > 0 && newidx > condargs[condidx + j - 1] + 1) {
                            int k;
                            for (k = condargs[condidx + j - 1] + 1; k < newidx; k++) {
                                int newnextidx = -1;
                                struct wps_token *newtoken = &tokens[k];
                                newnextidx = dump_token(newtoken, k, level + 1);
                                if (newnextidx > nextidx)
                                    nextidx = newnextidx;
                            }
                        }
                        for (i = 0; i < level + 1; i++)
                            printf("  ");
                        printf("-------------------------------------------\n");
                    }
                    if (newidx > 0) {
                        int newnextidx = -1;
                        struct wps_token *newtoken = &tokens[newidx];
                        newnextidx = dump_token(newtoken, newidx, level + 1);
                        if (newnextidx > nextidx)
                            nextidx = newnextidx;
                    } else {
                        for (i = 0; i < level + 1; i++)
                            printf("  ");
                        printf("empty argument (idx=-1)\n");
                    }
                }
            }
            else
                printf("no arguments\n");
        } else {
            printf("%sparams",
                   token->params ? "" : "no ");
            if (token->params)
                printf("='%s'", token->params);
            printf("\n");
        }
    } else {
        printf("textstring '%s'\n",
               token->params ? token->params : "");
    }

    return nextidx;
}


int main(int argc, char **argv)
{
    int i;
    char buf[1024];

    /* 1. parse wps and build list of tokens */
    i = 0;
    memset(wps_strings, '\0', sizeof(wps_strings));
    while (read_line( test_wps, buf, sizeof(buf) ) > 0) {
        LOGF(("%d.Zeile: %s", ++i, buf));
        if (buf[strlen(buf) - 1]) {
            LOGF(("\n"));
        }
        wps_parse(buf, NULL);
    }

    /* dump parsed WPS */
    printf("*** DUMP PARSED TOKENS ***\n");
    for (i = 0; i < num_tokens; i++) {
        int nextidx;
        nextidx = dump_token(&tokens[i], i, 0);
        if (nextidx > (i + 1))
            i = nextidx - 1;
    }
    printf("*** END OF DUMP ***\n");

    /* 2. execute the wps data (preload phase) */

    /* 3. execute the wps data (normal phase) */

    return 0;
}

/* vi: set ts=4 sts=4 sw=4 et ai ic: */
