/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2009 by Jens Arnold
 *
 * 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 "config.h"
#include "screendump.h"
#include "rbpaths.h"

#include "file.h"
#include "general.h"
#include "lcd.h"
#include "stdlib.h"
#include "string.h"
#include "system.h"

#ifdef HAVE_REMOTE_LCD
#include "lcd-remote.h"
#endif

#if LCD_DEPTH == 16
#define BMP_COMPRESSION 3 /* BI_BITFIELDS */
#define BMP_NUMCOLORS 3
#else /* LCD_DEPTH != 16 */
#define BMP_COMPRESSION 0 /* BI_RGB */
#if LCD_DEPTH <= 8
#ifdef HAVE_LCD_SPLIT
#define BMP_NUMCOLORS (2 << LCD_DEPTH)
#else
#define BMP_NUMCOLORS (1 << LCD_DEPTH)
#endif
#else /* LCD_DEPTH > 8 */
#define BMP_NUMCOLORS 0
#endif /* LCD_DEPTH > 8 */
#endif /* LCD_DEPTH != 16 */

#define BMP_HEADERSIZE (54 + 4 * BMP_NUMCOLORS)
#define BMP_DATASIZE   (DUMP_BMP_LINESIZE * (LCD_HEIGHT+LCD_SPLIT_LINES))
#define BMP_TOTALSIZE  (BMP_HEADERSIZE + BMP_DATASIZE)

static const unsigned char bmpheader[] =
{
    0x42, 0x4d,                 /* 'BM' */
    LE32_CONST(BMP_TOTALSIZE),  /* Total file size */
    0x00, 0x00, 0x00, 0x00,     /* Reserved */
    LE32_CONST(BMP_HEADERSIZE), /* Offset to start of pixel data */

    0x28, 0x00, 0x00, 0x00,     /* Size of (2nd) header */
    LE32_CONST(LCD_WIDTH),      /* Width in pixels */
    LE32_CONST(LCD_HEIGHT+LCD_SPLIT_LINES),  /* Height in pixels */
    0x01, 0x00,                 /* Number of planes (always 1) */
    LE16_CONST(DUMP_BMP_BPP),   /* Bits per pixel 1/4/8/16/24 */
    LE32_CONST(BMP_COMPRESSION),/* Compression mode */
    LE32_CONST(BMP_DATASIZE),   /* Size of bitmap data */
    0xc4, 0x0e, 0x00, 0x00,     /* Horizontal resolution (pixels/meter) */
    0xc4, 0x0e, 0x00, 0x00,     /* Vertical resolution (pixels/meter) */
    LE32_CONST(BMP_NUMCOLORS),  /* Number of used colours */
    LE32_CONST(BMP_NUMCOLORS),  /* Number of important colours */

#if LCD_DEPTH == 1
#ifdef HAVE_NEGATIVE_LCD
    BMP_COLOR(LCD_BL_DARKCOLOR),
    BMP_COLOR(LCD_BL_BRIGHTCOLOR),
#ifdef HAVE_LCD_SPLIT
    BMP_COLOR(LCD_BL_DARKCOLOR_2),
    BMP_COLOR(LCD_BL_BRIGHTCOLOR_2),
#endif
#else /* positive display */
    BMP_COLOR(LCD_BL_BRIGHTCOLOR),
    BMP_COLOR(LCD_BL_DARKCOLOR),
#endif /* positive display */
#elif LCD_DEPTH == 2
    BMP_COLOR(LCD_BL_BRIGHTCOLOR),
    BMP_COLOR_MIX(LCD_BL_BRIGHTCOLOR, LCD_BL_DARKCOLOR, 1, 3),
    BMP_COLOR_MIX(LCD_BL_BRIGHTCOLOR, LCD_BL_DARKCOLOR, 2, 3),
    BMP_COLOR(LCD_BL_DARKCOLOR),
#elif LCD_DEPTH == 16
    0x00, 0xf8, 0x00, 0x00,     /* red bitfield mask */
    0xe0, 0x07, 0x00, 0x00,     /* green bitfield mask */
    0x1f, 0x00, 0x00, 0x00,     /* blue bitfield mask */
#endif
};

static void (*screen_dump_hook)(int fh) = NULL;

void screen_dump(void)
{
    int fd, y;
    char filename[MAX_PATH];

    fb_data *src;
#if LCD_DEPTH == 1
    unsigned mask;
    unsigned val;
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT != HORIZONTAL_PACKING)
    int shift;
    unsigned val;
#endif
#if LCD_DEPTH <= 8
    unsigned char *dst, *dst_end;
    unsigned char linebuf[DUMP_BMP_LINESIZE];
#elif LCD_DEPTH <= 16
    unsigned short *dst, *dst_end;
    unsigned short linebuf[DUMP_BMP_LINESIZE/2];
#else /* 24bit */
    unsigned char *dst, *dst_end;
    unsigned char linebuf[DUMP_BMP_LINESIZE * 3];
#endif

#if CONFIG_RTC
    create_datetime_filename(filename, HOME_DIR, "dump ", ".bmp", false);
#else
    create_numbered_filename(filename, HOME_DIR, "dump_", ".bmp", 4
                             IF_CNFN_NUM_(, NULL));
#endif

    fd = creat(filename, 0666);
    if (fd < 0)
        return;

    if (screen_dump_hook)
    {
        screen_dump_hook(fd);
    }
    else
    {
        if(write(fd, bmpheader, sizeof(bmpheader)) != sizeof(bmpheader))
        {
            close(fd);
            return;
        }

        /* BMP image goes bottom up */
        for (y = LCD_HEIGHT - 1; y >= 0; y--)
        {
            memset(linebuf, 0, DUMP_BMP_LINESIZE);

#if defined(HAVE_LCD_SPLIT) && (LCD_SPLIT_LINES == 2)
            if (y == LCD_SPLIT_POS - 1)
            {
                write(fd, linebuf, DUMP_BMP_LINESIZE);
                write(fd, linebuf, DUMP_BMP_LINESIZE);
            }
#endif
            dst = linebuf;

#if LCD_DEPTH == 1
            dst_end = dst + LCD_WIDTH/2;
            src = FBADDR(0, y >> 3);
            mask = BIT_N(y & 7);

            do
            {
                val  = (*src++ & mask) ? 0x10 : 0;
                val |= (*src++ & mask) ? 0x01 : 0;
#ifdef HAVE_LCD_SPLIT
                if (y < LCD_SPLIT_POS)
                    val |= 0x22;
#endif
                *dst++ = val;
            }
            while (dst < dst_end);

#elif LCD_DEPTH == 2
            dst_end = dst + LCD_WIDTH/2;

#if LCD_PIXELFORMAT == HORIZONTAL_PACKING
            src = FBADDR(0, y);

            do
            {
                unsigned data = *src++;

                *dst++ = ((data >> 2) & 0x30) | ((data >> 4) & 0x03);
                *dst++ = ((data << 2) & 0x30) | (data & 0x03);
            }
            while (dst < dst_end);

#elif LCD_PIXELFORMAT == VERTICAL_PACKING
            src = FBADDR(0, y >> 2);
            shift = 2 * (y & 3);

            do
            {
                val  = ((*src++ >> shift) & 3) << 4;
                val |= ((*src++ >> shift) & 3);
                *dst++ = val;
            }
            while (dst < dst_end);

#elif LCD_PIXELFORMAT == VERTICAL_INTERLEAVED
            src = FBADDR(0, y >> 3);
            shift = y & 7;

            do
            {
                unsigned data = (*src++ >> shift) & 0x0101;

                val  = (((data >> 7) | data) & 3) << 4;
                data = (*src++ >> shift) & 0x0101;
                val |= ((data >> 7) | data) & 3;
                *dst++ = val;
            }
            while (dst < dst_end);

#endif
#elif LCD_DEPTH == 16
            dst_end = dst + LCD_WIDTH;
            src = FBADDR(0, y);

            do
            {
#if (LCD_PIXELFORMAT == RGB565SWAPPED)
                /* iPod LCD data is big endian although the CPU is not */
                *dst++ = htobe16(*src++);
#else
                *dst++ = htole16(*src++);
#endif
            }
            while (dst < dst_end);
#elif LCD_DEPTH >= 24
            dst_end = dst + LCD_WIDTH*sizeof(fb_data);
            src = FBADDR(0, y);
            do
            {
                *dst++ = src->b;
                *dst++ = src->g;
                *dst++ = src->r;
                ++src;
            }
            while (dst < dst_end);

#endif /* LCD_DEPTH */
            if(write(fd, linebuf, DUMP_BMP_LINESIZE) != DUMP_BMP_LINESIZE)
            {
                close(fd);
                return;
            }
        }
    }
    close(fd);
}

void screen_dump_set_hook(void (*hook)(int fh))
{
    screen_dump_hook = hook;
}

#ifdef HAVE_REMOTE_LCD

#define RBMP_COMPRESSION 0 /* BI_RGB */
#define RBMP_NUMCOLORS   (1 << LCD_REMOTE_DEPTH)
#define RBMP_BPP         4
#define RBMP_LINESIZE    ((LCD_REMOTE_WIDTH/2 + 3) & ~3)

#define RBMP_HEADERSIZE (54 + 4 * RBMP_NUMCOLORS)
#define RBMP_DATASIZE   (RBMP_LINESIZE * LCD_REMOTE_HEIGHT)
#define RBMP_TOTALSIZE  (RBMP_HEADERSIZE + RBMP_DATASIZE)

static const unsigned char rbmpheader[] =
{
    0x42, 0x4d,                    /* 'BM' */
    LE32_CONST(RBMP_TOTALSIZE),    /* Total file size */
    0x00, 0x00, 0x00, 0x00,        /* Reserved */
    LE32_CONST(RBMP_HEADERSIZE),   /* Offset to start of pixel data */

    0x28, 0x00, 0x00, 0x00,        /* Size of (2nd) header */
    LE32_CONST(LCD_REMOTE_WIDTH),  /* Width in pixels */
    LE32_CONST(LCD_REMOTE_HEIGHT), /* Height in pixels */
    0x01, 0x00,                    /* Number of planes (always 1) */
    LE16_CONST(RBMP_BPP),          /* Bits per pixel 1/4/8/16/24 */
    LE32_CONST(RBMP_COMPRESSION),  /* Compression mode */
    LE32_CONST(RBMP_DATASIZE),     /* Size of bitmap data */
    0xc4, 0x0e, 0x00, 0x00,        /* Horizontal resolution (pixels/meter) */
    0xc4, 0x0e, 0x00, 0x00,        /* Vertical resolution (pixels/meter) */
    LE32_CONST(RBMP_NUMCOLORS),    /* Number of used colours */
    LE32_CONST(RBMP_NUMCOLORS),    /* Number of important colours */

#if LCD_REMOTE_DEPTH == 1
    BMP_COLOR(LCD_REMOTE_BL_BRIGHTCOLOR),
    BMP_COLOR(LCD_REMOTE_BL_DARKCOLOR),
#elif LCD_REMOTE_DEPTH == 2
    BMP_COLOR(LCD_REMOTE_BL_BRIGHTCOLOR),
    BMP_COLOR_MIX(LCD_REMOTE_BL_BRIGHTCOLOR, LCD_REMOTE_BL_DARKCOLOR, 1, 3),
    BMP_COLOR_MIX(LCD_REMOTE_BL_BRIGHTCOLOR, LCD_REMOTE_BL_DARKCOLOR, 2, 3),
    BMP_COLOR(LCD_REMOTE_BL_DARKCOLOR),
#endif
};

void remote_screen_dump(void)
{
    int fd, y;
    char filename[MAX_PATH];

    fb_remote_data *src;
#if LCD_REMOTE_DEPTH == 1
    unsigned mask;
    unsigned val;
#elif LCD_REMOTE_DEPTH == 2
    int shift;
    unsigned val;
#endif
    unsigned char *dst, *dst_end;
    unsigned char linebuf[RBMP_LINESIZE];

#if CONFIG_RTC
    create_datetime_filename(filename, "", "rdump ", ".bmp", false);
#else
    create_numbered_filename(filename, "", "rdump_", ".bmp", 4
                             IF_CNFN_NUM_(, NULL));
#endif

    fd = creat(filename, 0666);
    if (fd < 0)
        return;

    write(fd, rbmpheader, sizeof(rbmpheader));

    /* BMP image goes bottom up */
    for (y = LCD_REMOTE_HEIGHT - 1; y >= 0; y--)
    {
        memset(linebuf, 0, RBMP_LINESIZE);

        dst = linebuf;

#if LCD_REMOTE_DEPTH == 1
        dst_end = dst + LCD_REMOTE_WIDTH/2;
        src = FBREMOTEADDR(0, y >> 3);
        mask = BIT_N(y & 7);

        do
        {
            val  = (*src++ & mask) ? 0x10 : 0;
            val |= (*src++ & mask) ? 0x01 : 0;
            *dst++ = val;
        }
        while (dst < dst_end);

#elif LCD_REMOTE_DEPTH == 2
        dst_end = dst + LCD_REMOTE_WIDTH/2;

#if LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED
        src = FBREMOTEADDR(0, (y >> 3));
        shift = y & 7;

        do
        {
            unsigned data = (*src++ >> shift) & 0x0101;

            val  = (((data >> 7) | data) & 3) << 4;
            data = (*src++ >> shift) & 0x0101;
            val |= ((data >> 7) | data) & 3;
            *dst++ = val;
        }
        while (dst < dst_end);

#endif
#endif /* LCD_REMOTE_DEPTH */
        write(fd, linebuf, RBMP_LINESIZE);
    }
    close(fd);
}

#endif /* HAVE_REMOTE_LCD */
