/*****************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _// __ \_/ ___\|  |/ /| __ \ / __ \  \/  /
 *   Jukebox    |    |   ( (__) )  \___|    ( | \_\ ( (__) )    (
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2008 Alexander Papst
 *
 * 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 "plugin.h"
#include "lib/pluginlib_bmp.h"
#include "ppm_decoder.h"
#include "../imageviewer.h"

static int ppm_read_magic_number(int fd)
{
    unsigned char i1, i2;
    if(rb->read(fd, &i1, 1) < 1 || rb->read(fd, &i2, 1) < 1)
    {
        ppm_error( "Error reading magic number from ppm image stream. "\
                   "Most often, this means your input file is empty." );
        return PLUGIN_ERROR;
    }
    return i1 * 256 + i2;
}

static int ppm_getc(int fd)
{
    unsigned char ch;

    if (rb->read(fd, &ch, 1) < 1) {
        ppm_error("EOF. Read error reading a byte");
        return PLUGIN_ERROR;
    }

    if (ch == '#') {
        do {
            if (rb->read(fd, &ch, 1) < 1) {
                ppm_error("EOF. Read error reading a byte");
                return PLUGIN_ERROR;
            }
        } while (ch != '\n' && ch != '\r');
    }
    return (int)ch;
}

static int ppm_getuint(int fd)
{
    int ch;
    int i;
    int digitVal;

    do {
        ch = ppm_getc(fd);
    } while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');

    if (ch == PLUGIN_ERROR) return PLUGIN_ERROR;
    if (ch < '0' || ch > '9') {
        ppm_error("Junk (%c) in file where an integer should be.", ch);
        return PLUGIN_ERROR;
    }

    i = 0;

    do {
        digitVal = ch - '0';

        if (i > INT_MAX/10 - digitVal) {
            ppm_error("ASCII decimal integer in file is "\
                      "too large to be processed.");
            return PLUGIN_ERROR;
        }

        i = i * 10 + digitVal;
        ch = ppm_getc(fd);

    } while (ch >= '0' && ch <= '9');
    if (ch == PLUGIN_ERROR) return PLUGIN_ERROR;

    return i;
}

static int ppm_getrawbyte(int fd)
{
    unsigned char by;

    if (rb->read(fd, &by, 1) < 1) {
        ppm_error("EOF. Read error while reading a one-byte sample.");
        return PLUGIN_ERROR;
    }

    return (int)by;
}

static int ppm_getrawsample(int fd, int const maxval)
{
    if (maxval < 256) {
        /* The sample is just one byte. Read it. */
        return(ppm_getrawbyte(fd));
    } else {
        /* The sample is two bytes. Read both. */
        unsigned char byte_pair[2];

        if (rb->read(fd, byte_pair, 2) < 2) {
            ppm_error("EOF. Read error while reading a long sample.");
            return PLUGIN_ERROR;
        }
        return((byte_pair[0]<<8) | byte_pair[1]);
    }
}

/* Read from the file header dimensions as well as max
 * int value used
 */
static int read_ppm_init_rest(int fd, struct ppm_info *ppm)
{
    /* Read size. */
    ppm->x = ppm_getuint(fd);
    ppm->y = ppm_getuint(fd);

#ifdef HAVE_LCD_COLOR
    ppm->native_img_size = ppm->x * ppm->y * FB_DATA_SZ;
#endif

    if (ppm->native_img_size > ppm->buf_size) {
        return PLUGIN_OUTOFMEM;
    }

    /* Read maxval. */
    ppm->maxval = ppm_getuint(fd);

    if (ppm->maxval > PPM_OVERALLMAXVAL) {
        ppm_error("maxval of input image (%u) is too large. "\
                  "The maximum allowed by the PPM is %u.",
                  ppm->maxval, PPM_OVERALLMAXVAL);
        return PLUGIN_ERROR;
    }
    if (ppm->maxval == 0) {
        ppm_error("maxval of input image is zero.");
        return PLUGIN_ERROR;
    }
    return PLUGIN_OK;
}

static int read_ppm_init(int fd, struct ppm_info *ppm)
{
    /* Check magic number. */
    ppm->format = ppm_read_magic_number( fd );

    if (ppm->format == PLUGIN_ERROR) return PLUGIN_ERROR;
    switch (ppm->format) {
        case PPM_FORMAT:
        case RPPM_FORMAT:
            return read_ppm_init_rest(fd, ppm);

        default:
            ppm_error( "Bad magic number - not a ppm or rppm file." );
            return PLUGIN_ERROR;
    }
    return PLUGIN_OK;
}

static int read_ppm_row(int fd, struct ppm_info *ppm, int row)
{
    int col;
    int r, g, b;
#ifdef HAVE_LCD_COLOR
#if LCD_STRIDEFORMAT == VERTICAL_STRIDE
    fb_data *dst = (fb_data *) ppm->buf + row;
    const int stride = ppm->x;
#else
    fb_data *dst = (fb_data *) ppm->buf + ppm->x*row;
    const int stride = 1;
#endif
#endif /* HAVE_LCD_COLOR */
    switch (ppm->format) {
        case PPM_FORMAT:
            for (col = 0; col < ppm->x; ++col) {
                r = ppm_getuint(fd);
                g = ppm_getuint(fd);
                b = ppm_getuint(fd);

                if (r == PLUGIN_ERROR || g == PLUGIN_ERROR ||
                    b == PLUGIN_ERROR)
                {
                    return PLUGIN_ERROR;
                }
                *dst = FB_RGBPACK(
                    (255 * r)/ppm->maxval,
                    (255 * g)/ppm->maxval,
                    (255 * b)/ppm->maxval);
                dst += stride;
            }
            break;

        case RPPM_FORMAT:
            for (col = 0; col < ppm->x; ++col) {
                r = ppm_getrawsample(fd, ppm->maxval);
                g = ppm_getrawsample(fd, ppm->maxval);
                b = ppm_getrawsample(fd, ppm->maxval);

                if (r == PLUGIN_ERROR || g == PLUGIN_ERROR ||
                    b == PLUGIN_ERROR)
                {
                    return PLUGIN_ERROR;
                }
                *dst = FB_RGBPACK(
                    (255 * r)/ppm->maxval,
                    (255 * g)/ppm->maxval,
                    (255 * b)/ppm->maxval);
                dst += stride;
            }
            break;

        default:
            ppm_error("What?!");
            return PLUGIN_ERROR;
    }
    return PLUGIN_OK;
}

/* public */
int read_ppm(int fd, struct ppm_info *ppm)
{
    int row, ret;

    ret = read_ppm_init(fd, ppm);
    if(ret != PLUGIN_OK) {
        return ret;
    }

    for (row = 0; row < ppm->y; ++row) {
        ret = read_ppm_row(fd, ppm, row);
        if(ret != PLUGIN_OK) {
            return ret;
        }
        iv->cb_progress(row, ppm->y);
    }
    return PLUGIN_OK;
}
