/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2010 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "debug.h"
#include "utils.h"
#include "mjpeg_decoder.h"

enum {
    STATE_READ_HEADER,
    STATE_START_DECOMPRESS,
    STATE_READ_SCANLINES,
    STATE_FINISH_DECOMPRESS
};

extern "C" {

    static void init_source(j_decompress_ptr cinfo)
    {
    }

    static boolean fill_input_buffer(j_decompress_ptr cinfo)
    {
        return FALSE;
    }

    void mjpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
    {
        MJpegDecoder *decoder = (MJpegDecoder *)cinfo;
        if (num_bytes > 0) {
            if (cinfo->src->bytes_in_buffer >= (size_t)num_bytes) {
                cinfo->src->next_input_byte += (size_t) num_bytes;
                cinfo->src->bytes_in_buffer -= (size_t) num_bytes;
            } else {
                decoder->_extra_skip = num_bytes - cinfo->src->bytes_in_buffer;
                cinfo->src->bytes_in_buffer = 0;
            }
        }
    }

    static void term_source (j_decompress_ptr cinfo)
    {
        return;
    }
}

MJpegDecoder::MJpegDecoder(int width, int height,
                           int stride,
                           uint8_t *frame,
                           bool back_compat) :
    _data(NULL)
    , _data_size(0)
    , _data_start(0)
    , _data_end(0)
    , _extra_skip(0)
    , _width(width)
    , _height(height)
    , _stride(stride)
    , _frame(frame)
    , _back_compat(back_compat)
    , _y(0)
    , _state(0)
{
    memset(&_cinfo, 0, sizeof(_cinfo));
    _cinfo.err = jpeg_std_error (&_jerr);
    jpeg_create_decompress (&_cinfo);

    _cinfo.src = &_jsrc;
    _cinfo.src->init_source = init_source;
    _cinfo.src->fill_input_buffer = fill_input_buffer;
    _cinfo.src->skip_input_data = mjpeg_skip_input_data;
    _cinfo.src->resync_to_restart = jpeg_resync_to_restart;
    _cinfo.src->term_source = term_source;

    _scanline = new uint8_t[width * 3];
}

MJpegDecoder::~MJpegDecoder()
{
    jpeg_destroy_decompress(&_cinfo);
    delete [] _scanline;
    if (_data) {
        delete [] _data;
    }
}

void MJpegDecoder::convert_scanline(void)
{
    uint32_t *row;
    uint32_t c;
    uint8_t *s;
    int x;

    ASSERT(_width % 2 == 0);
    ASSERT(_height % 2 == 0);

   row = (uint32_t *)(_frame + _y * _stride);
    s = _scanline;


    if (_back_compat) {
        /* We need to check for the old major and for backwards compat
           a) swap r and b (done)
           b) to-yuv with right values and then from-yuv with old wrong values (TODO)
        */
        for (x = 0; x < _width; x++) {
            c = s[2] << 16 | s[1] << 8 | s[0];
            s += 3;
            *row++ = c;
        }
    } else {
        for (x = 0; x < _width; x++) {
            c = s[0] << 16 | s[1] << 8 | s[2];
            s += 3;
            *row++ = c;
        }
    }
}

void MJpegDecoder::append_data(uint8_t *data, size_t length)
{
    uint8_t *new_data;
    size_t data_len;

    if (length == 0) {
        return;
    }

    if (_data_size - _data_end < length) {
        /* Can't fits in tail, need to make space */

        data_len = _data_end - _data_start;
        if (_data_size - data_len < length) {
            /* Can't fit at all, grow a bit */
            _data_size = _data_size + length * 2;
            new_data = new uint8_t[_data_size];
            memcpy (new_data, _data + _data_start, data_len);
            delete [] _data;
            _data = new_data;
        } else {
            /* Just needs to compact */
            memmove (_data, _data + _data_start, data_len);
        }
        _data_start = 0;
        _data_end = data_len;
    }

    memcpy (_data + _data_end, data, length);
    _data_end += length;
}

bool MJpegDecoder::decode_data(uint8_t *data, size_t length)
{
    bool got_picture;
    int res;

    got_picture = false;

    if (_extra_skip > 0) {
        if (_extra_skip >= length) {
            _extra_skip -= length;
            return false;
        } else {
            data += _extra_skip;
            length -= _extra_skip;
            _extra_skip = 0;
        }
    }

    if (_data_end - _data_start == 0) {
        /* No current data, pass in without copy */

        _jsrc.next_input_byte = data;
        _jsrc.bytes_in_buffer = length;
    } else {
        /* Need to combine the new and old data */
        append_data(data, length);

        _jsrc.next_input_byte = _data + _data_start;
        _jsrc.bytes_in_buffer = _data_end - _data_start;
    }

    switch (_state) {
    case STATE_READ_HEADER:
        res = jpeg_read_header(&_cinfo, TRUE);
        if (res == JPEG_SUSPENDED) {
            break;
        }

        _cinfo.do_fancy_upsampling = FALSE;
        _cinfo.do_block_smoothing = FALSE;
        _cinfo.out_color_space = JCS_RGB;

        PANIC_ON(_cinfo.image_width != _width);
        PANIC_ON(_cinfo.image_height != _height);

        _state = STATE_START_DECOMPRESS;

        /* fall through */
    case STATE_START_DECOMPRESS:
        res = jpeg_start_decompress (&_cinfo);

        if (!res) {
            break;
        }

        _state = STATE_READ_SCANLINES;

        /* fall through */
    case STATE_READ_SCANLINES:
        res = 0;
        while (_y < _height) {
            res = jpeg_read_scanlines(&_cinfo, &_scanline, 1);

            if (res == 0) {
                break;
            }

            convert_scanline();
            _y++;
        }
        if (res == 0) {
            break;
        }

        _state = STATE_FINISH_DECOMPRESS;

        /* fall through */
    case STATE_FINISH_DECOMPRESS:
        res = jpeg_finish_decompress (&_cinfo);

        if (!res) {
            break;
        }

        _y = 0;
        _state = STATE_READ_HEADER;
        got_picture = true;

        break;
    }

    if (_jsrc.next_input_byte == data) {
        /* We read directly from the user, store remaining data in
           buffer for next time */
        size_t read_size = _jsrc.next_input_byte - data;

        append_data(data + read_size, length - read_size);
    } else {
        _data_start = _jsrc.next_input_byte - _data;
        _data_end = _data_start + _jsrc.bytes_in_buffer;
    }

    return got_picture;
}
