/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007 Nicolas Pennequin
 *
 * This program 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.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <stdio.h>
#include "string-extra.h"
#include "system.h"
#include "albumart.h"
#include "metadata.h"
#include "buffering.h"
#include "dircache.h"
#include "misc.h"
#include "pathfuncs.h"
#include "settings.h"
#include "wps.h"

/* Define LOGF_ENABLE to enable logf output in this file */
/*#define LOGF_ENABLE*/
#include "logf.h"

#if defined(HAVE_JPEG) || defined(PLUGIN)
#define USE_JPEG_COVER
#endif

/* Strip filename from a full path
 *
 * buf      - buffer to extract directory to.
 * buf_size - size of buffer.
 * fullpath - fullpath to extract from.
 *
 * Split the directory part of the given fullpath and store it in buf
 *   (including last '/').
 * The function return parameter is a pointer to the filename
 *   inside the given fullpath.
 */
static char* strip_filename(char* buf, int buf_size, const char* fullpath)
{
    char* sep;
    int   len;

    if (!buf || buf_size <= 0 || !fullpath)
        return NULL;

    /* if 'fullpath' is only a filename return immediately */
    sep = strrchr(fullpath, '/');
    if (sep == NULL)
    {
        buf[0] = 0;
        return (char*)fullpath;
    }

    len = MIN(sep - fullpath + 1, buf_size - 1);
    strlcpy(buf, fullpath, len + 1);
    return (sep + 1);
}

/* Make sure part of path only contain chars valid for a FAT32 long name.
 * Double quotes are replaced with single quotes, other unsupported chars 
 * are replaced with an underscore.
 *
 * path   - path to modify.
 * offset - where in path to start checking.
 * count  - number of chars to check.
 */
static void fix_path_part(char* path, int offset, int count)
{
    static const char invalid_chars[] = "*/:<>?\\|";
    int i;
    
    path += offset;
    
    for (i = 0; i <= count; i++, path++)
    {
        if (*path == 0)
            return;
        if (*path == '"')
            *path = '\'';
        else if (strchr(invalid_chars, *path))
            *path = '_';
    }
}

#ifdef USE_JPEG_COVER
static const char * extensions[] = { "jpeg", "jpg", "bmp" };
static const unsigned char extension_lens[] = { 4, 3, 3 };
/* Try checking for several file extensions, return true if a file is found and
 * leaving the path modified to include the matching extension.
 */
static bool try_exts(char *path, int len)
{
    int i;
    for (i = 0; i < 3; i++)
    {
        if (extension_lens[i] + len > MAX_PATH)
            continue;
        strcpy(path + len, extensions[i]);
        if (file_exists(path))
            return true;
    }
    return false;
}
#define EXT
#else
#define EXT "bmp"
#define try_exts(path, len) file_exists(path)
#endif

/* Look for the first matching album art bitmap in the following list:
 *  ./<trackname><size>.{jpeg,jpg,bmp}
 *  ./<albumname><size>.{jpeg,jpg,bmp}
 *  ./cover<size>.bmp
 *  ../<albumname><size>.{jpeg,jpg,bmp}
 *  ../cover<size>.{jpeg,jpg,bmp}
 *  ROCKBOX_DIR/albumart/<artist>-<albumname><size>.{jpeg,jpg,bmp}
 * <size> is the value of the size_string parameter, <trackname> and
 * <albumname> are read from the ID3 metadata.
 * If a matching bitmap is found, its filename is stored in buf.
 * Return value is true if a bitmap was found, false otherwise.
 *
 * If the first symbol in size_string is a colon (e.g. ":100x100")
 * then the colon is skipped ("100x100" will be used) and the track
 * specific image (./<trackname><size>.bmp) is tried last instead of first.
 */
bool search_albumart_files(const struct mp3entry *id3, const char *size_string,
                           char *buf, int buflen)
{
    char path[MAX_PATH + 1];
    char dir[MAX_PATH + 1];
    bool found = false;
    int track_first = 1;
    int pass;
    const char *trackname;
    const char *artist;
    int dirlen;
    int albumlen;
    int pathlen;

    if (!id3 || !buf)
        return false;

    trackname = id3->path;

    if (strcmp(trackname, "No file!") == 0)
        return false;

    if (*size_string == ':')
    {
        size_string++;
        track_first = 0;
    }

    strip_filename(dir, sizeof(dir), trackname);
    dirlen = strlen(dir);
    albumlen = id3->album ? strlen(id3->album) : 0;

    for(pass = 0; pass < 2 - track_first; pass++)
    {
        if (track_first || pass)
        {
            /* the first file we look for is one specific to the
               current track */
            strip_extension(path, sizeof(path) - strlen(size_string) - 4,
                            trackname);
            strcat(path, size_string);
            strcat(path, "." EXT);
#ifdef USE_JPEG_COVER
            pathlen = strlen(path);
#endif
            found = try_exts(path, pathlen);
        }
        if (pass)
            break;
        if (!found && albumlen > 0)
        {
            /* if it doesn't exist,
            * we look for a file specific to the track's album name */
            pathlen = snprintf(path, sizeof(path),
                            "%s%s%s." EXT, dir, id3->album, size_string);
            fix_path_part(path, dirlen, albumlen);
            found = try_exts(path, pathlen);
        }

        if (!found)
        {
            /* if it still doesn't exist, we look for a generic file */
            pathlen = snprintf(path, sizeof(path),
                            "%scover%s." EXT, dir, size_string);
            found = try_exts(path, pathlen);
        }

#ifdef USE_JPEG_COVER
        if (!found && !*size_string)
        {
            snprintf (path, sizeof(path), "%sfolder.jpg", dir);
            found = file_exists(path);
        }
#endif

        artist = id3->albumartist != NULL ? id3->albumartist : id3->artist;

        if (!found && artist && id3->album)
        {
            /* look in the albumart subdir of .rockbox */
            pathlen = snprintf(path, sizeof(path),
                            ROCKBOX_DIR "/albumart/%s-%s%s." EXT,
                            artist,
                            id3->album,
                            size_string);
            fix_path_part(path, strlen(ROCKBOX_DIR "/albumart/"), MAX_PATH);
            found = try_exts(path, pathlen);
        }

        if (!found)
        {
            /* if it still doesn't exist,
            * we continue to search in the parent directory */
            strcpy(path, dir);
            path[dirlen - 1] = 0;
            strip_filename(dir, sizeof(dir), path);
            dirlen = strlen(dir);
        }

        /* only try parent if there is one */
        if (dirlen > 0)
        {
            if (!found && albumlen > 0)
            {
                /* we look in the parent directory
                * for a file specific to the track's album name */
                pathlen = snprintf(path, sizeof(path),
                                "%s%s%s." EXT, dir, id3->album, size_string);
                fix_path_part(path, dirlen, albumlen);
                found = try_exts(path, pathlen);
            }

            if (!found)
            {
                /* if it still doesn't exist, we look in the parent directory
                * for a generic file */
                pathlen = snprintf(path, sizeof(path),
                                "%scover%s." EXT, dir, size_string);
                found = try_exts(path, pathlen);
            }
        }
        if (found)
            break;
    }

    if (!found)
        return false;

    strlcpy(buf, path, buflen);
    logf("Album art found: %s", path);
    return true;
}

#ifndef PLUGIN
/* Look for albumart bitmap in the same dir as the track and in its parent dir.
 * Stores the found filename in the buf parameter.
 * Returns true if a bitmap was found, false otherwise */
bool find_albumart(const struct mp3entry *id3, char *buf, int buflen,
                    const struct dim *dim)
{
    if (!id3 || !buf)
        return false;

    char size_string[9];
    logf("Looking for album art for %s", id3->path);

    /* Write the size string, e.g. ".100x100". */
    snprintf(size_string, sizeof(size_string), ".%dx%d",
              dim->width, dim->height);

    /* First we look for a bitmap of the right size */
    if (search_albumart_files(id3, size_string, buf, buflen))
        return true;

    /* Then we look for generic bitmaps */
    *size_string = 0;
    return search_albumart_files(id3, size_string, buf, buflen);
}

/* Draw the album art bitmap from the given handle ID onto the given WPS.
   Call with clear = true to clear the bitmap instead of drawing it. */
void draw_album_art(struct gui_wps *gwps, int handle_id, bool clear)
{
    if (!gwps || !gwps->data || !gwps->display || handle_id < 0)
        return;

    struct wps_data *data = gwps->data;
    struct skin_albumart *aa = SKINOFFSETTOPTR(get_skin_buffer(data), data->albumart);

    if (!aa)
        return;
        
    struct bitmap *bmp;
    if (bufgetdata(handle_id, 0, (void *)&bmp) <= 0)
        return;

    short x = aa->x;
    short y = aa->y;
    short width = bmp->width;
    short height = bmp->height;

    if (aa->width > 0)
    {
        /* Crop if the bitmap is too wide */
        width = MIN(bmp->width, aa->width);

        /* Align */
        if (aa->xalign & WPS_ALBUMART_ALIGN_RIGHT)
            x += aa->width - width;
        else if (aa->xalign & WPS_ALBUMART_ALIGN_CENTER)
            x += (aa->width - width) / 2;
    }

    if (aa->height > 0)
    {
        /* Crop if the bitmap is too high */
        height = MIN(bmp->height, aa->height);

        /* Align */
        if (aa->yalign & WPS_ALBUMART_ALIGN_BOTTOM)
            y += aa->height - height;
        else if (aa->yalign & WPS_ALBUMART_ALIGN_CENTER)
            y += (aa->height - height) / 2;
    }

    if (!clear)
    {
        /* Draw the bitmap */
        gwps->display->bitmap_part((fb_data*)bmp->data, 0, 0, 
                                    STRIDE(gwps->display->screen_type, 
                                        bmp->width, bmp->height),
                                   x, y, width, height);
#ifdef HAVE_LCD_INVERT
        if (global_settings.invert) {
            gwps->display->set_drawmode(DRMODE_COMPLEMENT);
            gwps->display->fillrect(x, y, width, height);
            gwps->display->set_drawmode(DRMODE_SOLID);
        }
#endif
    }
    else
    {
        /* Clear the bitmap */
        gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
        gwps->display->fillrect(x, y, width, height);
        gwps->display->set_drawmode(DRMODE_SOLID);
    }
}

#endif /* PLUGIN */
