/*
 * This file is part of MPlayer.
 *
 * MPlayer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPlayer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file
 * @brief Skin parser
 */

#include <math.h>
#include <stdio.h>
#include <string.h>

#include "skin.h"
#include "font.h"
#include "gui/interface.h"
#include "gui/app/app.h"
#include "gui/app/gui.h"
#include "gui/dialog/dialog.h"
#include "gui/util/bitmap.h"
#include "gui/util/misc.h"
#include "gui/util/string.h"

#include "help_mp.h"
#include "mp_msg.h"
#include "libavutil/attributes.h"
#include "libavutil/avstring.h"
#include "libavutil/common.h"

typedef struct {
    const char *name;
    int (*func)(char *in);
} _item;

char *skinDirInHome;
char *skinDirInData;

static guiItems *skin;

static int linenumber;
static unsigned char currItem[32];
static unsigned char path[512];

static unsigned char currWinName[32];
static guiItem *currWin;
static int *currWinItemIdx;
static guiItem *currWinItems;

/**
 * @brief Print a legacy information on an entry.
 *
 * @param old identifier (and deprecated entry)
 * @param data string necessary for checking and to print the information on @a old
 */
static void skin_legacy(const char *old, const char *data)
{
    const char *p;

    if (strcmp(old, "fontid") == 0) {
        p = strchr(data, ',');

        if (p)
            mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, p, "font = fontfile");
    } else if (strcmp(old, "$l") == 0) {
        p = strstr(old, data);

        if (p && (p == data || p[-1] != '$'))
            mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, "$p");
    } else if (strcmp(old, "evSetURL") == 0 && strcmp(data, old) == 0)
        mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, "evLoadURL");
    else if (strcmp(old, "sub") == 0 || strcmp(old, "potmeter") == 0)
        mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, data);
}

/**
 * @brief Display a skin error message.
 *
 * @param format format string
 * @param ... arguments
 */
static void skin_error(const char *format, ...)
{
    char p[512];
    va_list ap;

    va_start(ap, format);
    vsnprintf(p, sizeof(p), format, ap);
    va_end(ap);

    gmp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinErrorMessage), linenumber, p);
}

/**
 * @brief Check whether a @a section definition has started.
 *
 * @param item name of the item to be put in a message in case of an error
 *
 * @return #True (ok) or #False (error)
 */
static int section_item(char *item)
{
    if (!skin) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorSection), item);
        return False;
    }

    return True;
}

/**
 * @brief Check whether a @a window definition has started.
 *
 * @param item name of the item to be put in a message in case of an error
 *
 * @return #True (ok) or #False (error)
 */
static int window_item(char *item)
{
    if (!currWinName[0]) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorWindow), item);
        return False;
    }

    return True;
}

/**
 * @brief Check whether a specific @a window definition has started.
 *
 * @param name name of the window to be checked
 *
 * @return 0 (ok) or 1 (error)
 */
static int in_window(char *name)
{
    if (strcmp(currWinName, name) == 0) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), name);
        return 1;
    }

    return 0;
}

/**
 * @brief Get next free item in current @a window.
 *
 * @return pointer to next free item (ok) or NULL (error)
 */
static guiItem *next_item(void)
{
    guiItem *item = NULL;

    if (*currWinItemIdx < MAX_ITEMS - 1) {
        (*currWinItemIdx)++;
        item = &currWinItems[*currWinItemIdx];
    } else
        skin_error(_(MSGTR_GUI_MSG_SkinTooManyItems));

    return item;
}

/**
 * @brief Parse a @a section definition.
 *
 *        Syntax: section=movieplayer
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_section(char *in)
{
    if (skin) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), currItem);
        return 1;
    }

    if (strcmp(strlower(in), "movieplayer") == 0)
        skin = &guiApp;
    else {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownName), in);
        return 1;
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]  %s: %s\n", currItem, in);

    return 0;
}

/**
 * @brief Parse an @a end definition.
 *
 *        Syntax: end
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_end(char *in)
{
    char *space, *name;

    (void)in;

    if (currWinName[0]) {
        space = " ";
        name  = currWinName;
    } else {
        space = "";
        name  = "section";
    }

    if (!section_item(currItem))
        return 1;

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]  %s%s (%s)\n", space, currItem, name);

    if (currWinName[0]) {
        currWinName[0] = 0;
        currWin = NULL;
        currWinItemIdx = NULL;
        currWinItems   = NULL;
    } else
        skin = NULL;

    return 0;
}

/**
 * @brief Parse a @a window definition.
 *
 *        Syntax: window=main|video|playbar|menu
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_window(char *in)
{
    if (!section_item(currItem))
        return 1;

    if (currWinName[0]) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), currItem);
        return 1;
    }

    strlower(in);

    // legacy
    if (strcmp(in, "sub") == 0) {
        strcpy(in, "video");
        skin_legacy("sub", in);
    }

    if (strcmp(in, "main") == 0) {
        currWin = &skin->main;
        currWinItemIdx = &skin->IndexOfMainItems;
        currWinItems   = skin->mainItems;
    } else if (strcmp(in, "video") == 0) {
        currWin = &skin->video;
        currWinItemIdx = NULL;
        currWinItems   = NULL;
    } else if (strcmp(in, "playbar") == 0) {
        currWin = &skin->playbar;
        currWinItemIdx = &skin->IndexOfPlaybarItems;
        currWinItems   = skin->playbarItems;
    } else if (strcmp(in, "menu") == 0) {
        currWin = &skin->menu;
        currWinItemIdx = &skin->IndexOfMenuItems;
        currWinItems   = skin->menuItems;
    } else {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownName), in);
        return 1;
    }

    av_strlcpy(currWinName, in, sizeof(currWinName));

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]   %s: %s\n", currItem, currWinName);

    return 0;
}

/**
 * @brief Parse a @a base definition.
 *
 *        Syntax: base=image,x,y[,width,height]
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_base(char *in)
{
    unsigned char fname[256];
    unsigned char file[512];
    int x, y, w, h;
    int is_video, is_bar, is_menu;

    if (!window_item(currItem))
        return 1;

    is_video = (strcmp(currWinName, "video") == 0);
    is_bar   = (strcmp(currWinName, "playbar") == 0);
    is_menu  = (strcmp(currWinName, "menu") == 0);

    cutStr(in, fname, ',', 0);
    x = cutInt(in, ',', 1);
    y = cutInt(in, ',', 2);
    w = cutInt(in, ',', 3);
    h = cutInt(in, ',', 4);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    image: %s", fname);

    currWin->type = itBase;

    if (!is_menu) {
        currWin->x = x;
        currWin->y = y;

        mp_msg(MSGT_GPLAYER, MSGL_DBG2, " %d,%d", x, y);
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "\n");

    if (is_video && (strcmp(fname, "NULL") == 0)) {
        currWin->width  = 0;
        currWin->height = 0;
    } else {
        av_strlcpy(file, path, sizeof(file));
        av_strlcat(file, fname, sizeof(file));

        if (skinImageRead(file, &currWin->Bitmap) != 0)
            return 1;

        currWin->width  = currWin->Bitmap.Width;
        currWin->height = currWin->Bitmap.Height;
    }

    if (is_video) {
        if (w && h) {
            currWin->width  = w;
            currWin->height = h;
        }
    }

    if (currWin->width == 0 || currWin->height == 0)
        return 1;

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %dx%d\n", is_video && w && h ? "size" : " bitmap", currWin->width, currWin->height);

    if (!is_video) {
        if (!bpRenderMask(&currWin->Bitmap, &currWin->Mask)) {
            skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
            return 1;
        }
        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     mask: %ux%u\n", currWin->Mask.Width, currWin->Mask.Height);
    }

    if (is_bar)
        skin->playbarIsPresent = True;
    if (is_menu)
        skin->menuIsPresent = True;

    return 0;
}

/**
 * @brief Parse a @a background definition.
 *
 *        Syntax: background=R,G,B
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_background(char *in)
{
    if (!window_item(currItem))
        return 1;

    if (in_window("main"))
        return 1;
    if (in_window("playbar"))
        return 1;
    if (in_window("menu"))
        return 1;

    currWin->R = cutInt(in, ',', 0);
    currWin->G = cutInt(in, ',', 1);
    currWin->B = cutInt(in, ',', 2);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s color: #%02x%02x%02x\n", currItem, currWin->R, currWin->G, currWin->B);

    return 0;
}

/**
 * @brief Parse a @a button definition.
 *
 *        Syntax: button=image,x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_button(char *in)
{
    unsigned char fname[256];
    unsigned char file[512];
    int x, y, w, h, message;
    char msg[32];
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    cutStr(in, fname, ',', 0);
    x = cutInt(in, ',', 1);
    y = cutInt(in, ',', 2);
    w = cutInt(in, ',', 3);
    h = cutInt(in, ',', 4);
    cutStr(in, msg, ',', 5);

    message = appFindMessage(msg);

    if (message == -1) {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), msg);
        return 1;
    }
    // legacy
    else
        skin_legacy("evSetURL", msg);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d\n", currItem, fname, x, y);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", msg, message);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     size: %dx%d\n", w, h);

    item = next_item();

    if (!item)
        return 1;

    item->type    = itButton;
    item->x       = x;
    item->y       = y;
    item->width   = w;
    item->height  = h;
    item->message = message;
    item->pressed = btnReleased;

    if (item->message == evPauseSwitchToPlay)
        item->pressed = btnDisabled;

    item->Bitmap.Image = NULL;

    if (strcmp(fname, "NULL") != 0) {
        av_strlcpy(file, path, sizeof(file));
        av_strlcat(file, fname, sizeof(file));

        if (skinImageRead(file, &item->Bitmap) != 0)
            return 1;

        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (bitmap: %ux%u)\n", item->Bitmap.Width, item->Bitmap.Height);
    }

    return 0;
}

/**
 * @brief Parse a @a selected definition.
 *
 *        Syntax: selected=image
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_selected(char *in)
{
    unsigned char file[512];
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("main"))
        return 1;
    if (in_window("video"))
        return 1;
    if (in_window("playbar"))
        return 1;

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    image %s: %s\n", currItem, in);

    item       = &skin->menuSelected;
    item->type = itBase;

    av_strlcpy(file, path, sizeof(file));
    av_strlcat(file, in, sizeof(file));

    if (skinImageRead(file, &item->Bitmap) != 0)
        return 1;

    item->width  = item->Bitmap.Width;
    item->height = item->Bitmap.Height;

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     bitmap: %dx%d\n", item->width, item->height);

    return 0;
}

/**
 * @brief Parse a @a menu definition.
 *
 *        Syntax: menu=x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_menu(char *in)
{
    int x, y, w, h, message;
    char msg[32];
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("main"))
        return 1;
    if (in_window("video"))
        return 1;
    if (in_window("playbar"))
        return 1;

    x = cutInt(in, ',', 0);
    y = cutInt(in, ',', 1);
    w = cutInt(in, ',', 2);
    h = cutInt(in, ',', 3);
    cutStr(in, msg, ',', 4);

    message = appFindMessage(msg);

    if (message == -1) {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), msg);
        return 1;
    }
    // legacy
    else
        skin_legacy("evSetURL", msg);

    item = next_item();

    if (!item)
        return 1;

    item->type    = itMenu;
    item->x       = x;
    item->y       = y;
    item->width   = w;
    item->height  = h;
    item->message = message;

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    item #%d: %d,%d %dx%d\n", *currWinItemIdx, x, y, w, h);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", msg, message);

    return 0;
}

/**
 * @brief Parse a hpotmeter, vpotmeter or rpotmeter definition.
 *
 *        Parameters: button,bwidth,bheight,phases,numphases,[x0,y0,x1,y1,]default,x,y,width,height,message
 *
 * @param item memory location of an item to store the parameters in
 * @param in definition to be analyzed
 *
 * @note item->type is already available.
 *
 * @return 0 (ok) or 1 (error)
 */
static int parse_potmeter(guiItem *item, char *in)
{
    unsigned char bfname[256];
    unsigned char phfname[256];
    unsigned char buf[512], dfmt[5];
    int i = 0, av_uninit(x0), av_uninit(y0), av_uninit(x1), av_uninit(y1);
    int bwidth, bheight, num, no_default, d, x, y, w, h, message;

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    cutStr(in, bfname, ',', i++);
    bwidth  = cutInt(in, ',', i++);
    bheight = cutInt(in, ',', i++);
    cutStr(in, phfname, ',', i++);
    num = cutInt(in, ',', i++);

    if (item->type == itRPotmeter) {
        x0 = cutInt(in, ',', i++);
        y0 = cutInt(in, ',', i++);
        x1 = cutInt(in, ',', i++);
        y1 = cutInt(in, ',', i++);
    }

    cutStr(in, buf, ',', i);
    no_default = (strcmp(buf, "-") == 0);

    d = cutInt(in, ',', i++);
    x = cutInt(in, ',', i++);
    y = cutInt(in, ',', i++);
    w = cutInt(in, ',', i++);
    h = cutInt(in, ',', i++);
    cutStr(in, buf, ',', i++);

    message = appFindMessage(buf);

    if (message == -1) {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), buf);
        return 1;
    }
    // legacy
    else
        skin_legacy("evSetURL", buf);

    if (d < 0 || d > 100) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorDefault), d);
        return 1;
    }

    if ((message == evSetVolume) && no_default) {
        d = -1;
        strcpy(dfmt, "-");
    } else
        sprintf(dfmt, "%d%%", d);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d %dx%d\n", currItem, phfname, x, y, w, h);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     button image: %s %dx%d\n", bfname, bwidth, bheight);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     numphases: %d, default: %s\n", num, dfmt);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", buf, message);

    item->x         = x;
    item->y         = y;
    item->width     = w;
    item->height    = h;
    item->pbwidth   = bwidth;
    item->pbheight  = bheight;
    item->numphases = num;
    item->value     = (float)d;
    item->message   = message;
    item->pressed   = btnReleased;

    if (item->type == itRPotmeter) {
        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     start: %d,%d / stop: %d,%d\n", x0, y0, x1, y1);

        item->zeropoint = appRadian(item, x0, y0);
        item->arclength = appRadian(item, x1, y1) - item->zeropoint;

        if (item->arclength < 0.0)
            item->arclength += 2 * M_PI;
        // else check if radians of (x0,y0) and (x1,y1) only differ below threshold
        else if (item->arclength < 0.05)
            item->arclength = 2 * M_PI;
    }

    item->Bitmap.Image = NULL;

    if (strcmp(phfname, "NULL") != 0) {
        if (num == 0) {
            skin_error(_(MSGTR_GUI_MSG_SkinErrorNumphases));
            return 1;
        }

        av_strlcpy(buf, path, sizeof(buf));
        av_strlcat(buf, phfname, sizeof(buf));

        if (skinImageRead(buf, &item->Bitmap) != 0)
            return 1;

        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (%s bitmap: %ux%u)\n", currItem, item->Bitmap.Width, item->Bitmap.Height);
    }

    item->Mask.Image = NULL;

    if (strcmp(bfname, "NULL") != 0) {
        av_strlcpy(buf, path, sizeof(buf));
        av_strlcat(buf, bfname, sizeof(buf));

        if (skinImageRead(buf, &item->Mask) != 0)
            return 1;

        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (button bitmap: %ux%u)\n", item->Mask.Width, item->Mask.Height);
    }

    return 0;
}

/**
 * @brief Parse a @a hpotmeter definition.
 *
 *        Syntax: hpotmeter=button,bwidth,bheight,phases,numphases,default,x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_hpotmeter(char *in)
{
    guiItem *item;

    item = next_item();

    if (!item)
        return 1;

    item->type = itHPotmeter;

    return parse_potmeter(item, in);
}

/**
 * @brief Parse a @a vpotmeter definition.
 *
 *        Syntax: vpotmeter=button,bwidth,bheight,phases,numphases,default,x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_vpotmeter(char *in)
{
    guiItem *item;

    item = next_item();

    if (!item)
        return 1;

    item->type = itVPotmeter;

    return parse_potmeter(item, in);
}

/**
 * @brief Parse a @a rpotmeter definition.
 *
 *        Syntax: rpotmeter=button,bwidth,bheight,phases,numphases,x0,y0,x1,y1,default,x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_rpotmeter(char *in)
{
    guiItem *item;

    item = next_item();

    if (!item)
        return 1;

    item->type = itRPotmeter;

    return parse_potmeter(item, in);
}

/**
 * @brief Parse a @a potmeter definition.
 *
 *        Syntax: potmeter=phases,numphases,default,x,y,width,height,message
 *
 * @note THIS ITEM IS DEPRECATED.
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_potmeter(char *in)
{
    char param[256];

    // legacy
    skin_legacy(currItem, "hpotmeter");

    snprintf(param, sizeof(param), "NULL,0,0,%s", in);

    return item_hpotmeter(param);
}

/**
 * @brief Parse a @a pimage definition.
 *
 *        Syntax: pimage=phases,numphases,default,x,y,width,height,message
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_pimage(char *in)
{
    unsigned char phfname[256];
    unsigned char buf[512];
    int num, d, x, y, w, h, message;
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    cutStr(in, phfname, ',', 0);
    num = cutInt(in, ',', 1);
    d   = cutInt(in, ',', 2);
    x   = cutInt(in, ',', 3);
    y   = cutInt(in, ',', 4);
    w   = cutInt(in, ',', 5);
    h   = cutInt(in, ',', 6);
    cutStr(in, buf, ',', 7);

    message = appFindMessage(buf);

    if (message == -1) {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), buf);
        return 1;
    }
    // legacy
    else
        skin_legacy("evSetURL", buf);

    if (d < 0 || d > 100) {
        skin_error(_(MSGTR_GUI_MSG_SkinErrorDefault), d);
        return 1;
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d %dx%d\n", currItem, phfname, x, y, w, h);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     numphases: %d, default: %d%%\n", num, d);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", buf, message);

    item = next_item();

    if (!item)
        return 1;

    item->type      = itPimage;
    item->x         = x;
    item->y         = y;
    item->width     = w;
    item->height    = h;
    item->numphases = num;
    item->value     = (float)d;
    item->message   = message;

    item->Bitmap.Image = NULL;

    if (strcmp(phfname, "NULL") != 0) {
        if (num == 0) {
            skin_error(_(MSGTR_GUI_MSG_SkinErrorNumphases));
            return 1;
        }

        av_strlcpy(buf, path, sizeof(buf));
        av_strlcat(buf, phfname, sizeof(buf));

        if (skinImageRead(buf, &item->Bitmap) != 0)
            return 1;

        mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (bitmap: %ux%u)\n", item->Bitmap.Width, item->Bitmap.Height);
    }

    return 0;
}

/**
 * @brief Parse a @a font definition.
 *
 *        Syntax: font=fontfile
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_font(char *in)
{
    char fnt[256];

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    cutStr(in, fnt, ',', 0);   // Note: This seems needless but isn't for compatibility
                               // reasons with a meanwhile deprecated second parameter.
    // legacy
    skin_legacy("fontid", in);

    switch (fntRead(path, fnt)) {
    case -1:
        skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
        return 1;

    case -2:
        skin_error(_(MSGTR_GUI_MSG_SkinTooManyFonts));
        return 1;

    case -3:
        skin_error(_(MSGTR_GUI_MSG_SkinFontFileNotFound));
        return 1;

    case -4:
        skin_error(_(MSGTR_GUI_MSG_SkinFontImageNotFound));
        return 1;
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %s (#%d)\n", currItem, fnt, fntFindID(fnt));

    return 0;
}

/**
 * @brief Parse a @a slabel definition.
 *
 *        Syntax: slabel=x,y,fontfile,"text"
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_slabel(char *in)
{
    int x, y, id;
    char fnt[256];
    char txt[256];
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    x = cutInt(in, ',', 0);
    y = cutInt(in, ',', 1);
    cutStr(in, fnt, ',', 2);
    cutStr(in, txt, ',', 3);
    cutStr(txt, txt, '"', 1);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: \"%s\"\n", currItem, txt);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     pos: %d,%d\n", x, y);

    id = fntFindID(fnt);

    if (id < 0) {
        skin_error(_(MSGTR_GUI_MSG_SkinFontNotFound), fnt);
        return 1;
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     font: %s (#%d)\n", fnt, id);

    item = next_item();

    if (!item)
        return 1;

    item->type   = itSLabel;
    item->x      = x;
    item->y      = y;
    item->width  = -1;
    item->height = -1;
    item->fontid = id;
    item->label  = strdup(txt);

    if (!item->label) {
        skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
        return 1;
    }

    return 0;
}

/**
 * @brief Parse a @a dlabel definition.
 *
 *        Syntax: dlabel=x,y,width,align,fontfile,"text"
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_dlabel(char *in)
{
    int x, y, w, a, id;
    char fnt[256];
    char txt[256];
    guiItem *item;

    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("menu"))
        return 1;

    x = cutInt(in, ',', 0);
    y = cutInt(in, ',', 1);
    w = cutInt(in, ',', 2);
    a = cutInt(in, ',', 3);
    cutStr(in, fnt, ',', 4);
    cutStr(in, txt, ',', 5);
    cutStr(txt, txt, '"', 1);

    // legacy
    skin_legacy("$l", txt);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: \"%s\"\n", currItem, txt);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     pos: %d,%d\n", x, y);
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     width: %d, align: %d\n", w, a);

    id = fntFindID(fnt);

    if (id < 0) {
        skin_error(_(MSGTR_GUI_MSG_SkinFontNotFound), fnt);
        return 1;
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     font: %s (#%d)\n", fnt, id);

    item = next_item();

    if (!item)
        return 1;

    item->type   = itDLabel;
    item->x      = x;
    item->y      = y;
    item->width  = w;
    item->height = -1;
    item->fontid = id;
    item->align  = a;
    item->label  = strdup(txt);

    if (!item->label) {
        skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
        return 1;
    }

    return 0;
}

/**
 * @brief Parse a @a decoration definition.
 *
 *        Syntax: decoration=enable|disable
 *
 * @param in definition to be analyzed
 *
 * @return 0 (ok) or 1 (error)
 */
static int item_decoration(char *in)
{
    if (!window_item(currItem))
        return 1;

    if (in_window("video"))
        return 1;
    if (in_window("playbar"))
        return 1;
    if (in_window("menu"))
        return 1;

    strlower(in);

    if (strcmp(in, "enable") != 0 && strcmp(in, "disable") != 0) {
        skin_error(_(MSGTR_GUI_MSG_SkinUnknownParameter), in);
        return 1;
    }

    skin->mainDecoration = (strcmp(in, "enable") == 0);

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %s\n", currItem, in);

    return 0;
}

/**
 * @brief Parsing functions responsible for skin item definitions.
 */
static _item skinItem[] = {
    { "background", item_background },
    { "base",       item_base       },
    { "button",     item_button     },
    { "decoration", item_decoration },
    { "dlabel",     item_dlabel     },
    { "end",        item_end        },
    { "font",       item_font       },
    { "hpotmeter",  item_hpotmeter  },
    { "menu",       item_menu       },
    { "pimage",     item_pimage     },
    { "potmeter",   item_potmeter   }, // legacy
    { "rpotmeter",  item_rpotmeter  },
    { "section",    item_section    },
    { "selected",   item_selected   },
    { "slabel",     item_slabel     },
    { "vpotmeter",  item_vpotmeter  },
    { "window",     item_window     }
};

/**
 * @brief Read a skin @a image file.
 *
 * @param fname filename (with path)
 * @param img memory location to store the image data
 *
 * @return return code of #bpRead()
 */
int skinImageRead(char *fname, guiImage *img)
{
    int i = bpRead(fname, img);

    switch (i) {
    case -1:
        skin_error(_(MSGTR_GUI_MSG_SkinErrorBitmap16Bit), fname);
        break;

    case -2:
        skin_error(_(MSGTR_GUI_MSG_SkinBitmapNotFound), fname);
        break;

    case -5:
        skin_error(_(MSGTR_GUI_MSG_SkinBitmapPngReadError), fname);
        break;

    case -8:
        skin_error(_(MSGTR_GUI_MSG_SkinBitmapConversionError), fname);
        break;
    }

    return i;
}

/**
 * @brief Build the skin file path for a skin name.
 *
 * @param dir skins directory
 * @param sname name of the skin
 *
 * @return skin file path
 *
 * @note As a side effect, variable #path gets set to the skin path.
 */
static char *setname(char *dir, char *sname)
{
    static char skinfname[512];

    av_strlcpy(skinfname, dir, sizeof(skinfname));
    av_strlcat(skinfname, "/", sizeof(skinfname));
    av_strlcat(skinfname, sname, sizeof(skinfname));
    av_strlcat(skinfname, "/", sizeof(skinfname));
    av_strlcpy(path, skinfname, sizeof(path));
    av_strlcat(skinfname, "skin", sizeof(skinfname));

    return skinfname;
}

/**
 * @brief Create the most minimal skin possible.
 *
 * @return 0 (ok) or -1 (error)
 */
static int skinNoskin(void)
{
    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin] using built-in skin\n");

    appFreeStruct();

    /* section = movieplayer */

    skin = &guiApp;

    /* window = main */

    currWin = &skin->main;
    currWinItemIdx = &skin->IndexOfMainItems;
    currWinItems   = skin->mainItems;

    /* decoration = disable */

    skin->mainDecoration = 0;

    /* base = <img>, 0, 0 (where <img> is a 1x1 transparent pixel image) */

    currWin->Bitmap.Width     = 1;
    currWin->Bitmap.Height    = 1;
    currWin->Bitmap.Bpp       = 32;
    currWin->Bitmap.ImageSize = 4;
    currWin->Bitmap.Image     = calloc(1, 4);

    if (!currWin->Bitmap.Image) {
        skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
        return -1;
    }

    *(uint32_t *)currWin->Bitmap.Image = GUI_TRANSPARENT;

    if (!bpRenderMask(&currWin->Bitmap, &currWin->Mask)) {
        skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
        return -1;
    }

    currWin->x      = 0;
    currWin->y      = 0;
    currWin->width  = currWin->Bitmap.Width;
    currWin->height = currWin->Bitmap.Height;

    /* window = video */

    currWin = &skin->video;
    currWinItemIdx = NULL;
    currWinItems   = NULL;

    /* base = NULL, -1, -1, 720, 404 */

    currWin->x      = -1;
    currWin->y      = -1;
    currWin->width  = 720;
    currWin->height = 404;

    /* background = 0, 0, 0 */

    currWin->R = 0;
    currWin->G = 0;
    currWin->B = 0;

    /* end */

    return 0;
}

/**
 * @brief Read and parse a skin.
 *
 * @param sname name of the skin
 *
 * @return 0 (ok), -1 (skin file not found or not readable) or -2 (parsing error)
 */
int skinRead(char *sname)
{
    char *skinfname;
    FILE *skinfile;
    unsigned char line[256];
    unsigned char param[256];
    unsigned int i;

    if (*sname == 0 || strcmp(sname, "Noskin") == 0)
        return skinNoskin();

    skinfname = setname(skinDirInHome, sname);

    if ((skinfile = fopen(skinfname, "rt")) == NULL) {
        skinfname = setname(skinDirInData, sname);

        if ((skinfile = fopen(skinfname, "rt")) == NULL) {
            mp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinFileNotFound), skinfname);
            return -1;
        }
    }

    mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin] configuration file: %s\n", skinfname);

    appFreeStruct();

    skin = NULL;
    currWinName[0] = 0;
    linenumber     = 0;

    while (fgetstr(line, sizeof(line), skinfile)) {
        linenumber++;

        strswap(line, '\t', ' ');
        despace(line);
        decomment(line);

        if (!*line)
            continue;

        cutStr(line, currItem, '=', 0);
        cutStr(line, param, '=', 1);
        strlower(currItem);

        for (i = 0; i < FF_ARRAY_ELEMS(skinItem); i++) {
            if (strcmp(currItem, skinItem[i].name) == 0) {
                if (skinItem[i].func(param) != 0) {
                    fclose(skinfile);
                    return -2;
                } else
                    break;
            }
        }

        if (i == FF_ARRAY_ELEMS(skinItem)) {
            skin_error(_(MSGTR_GUI_MSG_SkinUnknownItem), currItem);
            fclose(skinfile);
            return -2;
        }
    }

    fclose(skinfile);

    if (linenumber == 0) {
        mp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinFileNotReadable), skinfname);
        return -1;
    }

    return 0;
}
