/*
 *    netcam_jpeg.c
 *
 *    Module for handling JPEG decompression for network cameras.
 *
 *    This code was inspired by the original module written by
 *    Jeroen Vreeken and enhanced by several Motion project
 *    contributors, particularly Angel Carpintero and
 *    Christopher Price.
 *
 *    Copyright 2005, William M. Brack
 *    This program is published under the GNU Public license
 */

#include "rotate.h"    /* already includes motion.h */
#include <jpeglib.h>
#include <jerror.h>

/*
 * netcam_source_mgr is a locally-defined structure to contain elements
 * which are not present in the standard libjpeg (the element 'pub' is a
 * pointer to the standard information).
 */
typedef struct {
    struct jpeg_source_mgr pub;
    char           *data;
    int             length;
    JOCTET         *buffer;
    boolean         start_of_file;
} netcam_source_mgr;

typedef netcam_source_mgr *netcam_src_ptr;


/*
 * Here we declare function prototypes for all the routines involved with
 * overriding libjpeg functions.  The reason these are required is that,
 * although the standard library handles input and output with stdio,
 * we are working with "remote" data (from the camera or from a file).
 */
static void     netcam_init_source(j_decompress_ptr);
static boolean  netcam_fill_input_buffer(j_decompress_ptr);
static void     netcam_skip_input_data(j_decompress_ptr, long);
static void     netcam_term_source(j_decompress_ptr);
static void     netcam_memory_src(j_decompress_ptr, char *, int);
static void     netcam_error_exit(j_common_ptr);

static void netcam_init_source(j_decompress_ptr cinfo)
{
    /* Get our "private" structure from the libjpeg structure. */
    netcam_src_ptr  src = (netcam_src_ptr) cinfo->src;
    /*
     * Set the 'start_of_file' flag in our private structure
     * (used by my_fill_input_buffer).
     */
    src->start_of_file = TRUE;
}

static boolean netcam_fill_input_buffer(j_decompress_ptr cinfo)
{
    netcam_src_ptr  src = (netcam_src_ptr) cinfo->src;
    size_t nbytes;

    /*
     * start_of_file is set any time netcam_init_source has been called
     * since the last entry to this routine.  This would be the normal
     * path when a new image is to be processed.  It is assumed that
     * this routine will only be called once for the entire image.
     * If an unexpected call (with start_of_file FALSE) occurs, the
     * routine calls ERREXIT().
     */
    if (src->start_of_file) {
        nbytes = src->length;
        src->buffer = (JOCTET *) src->data;
    } else {
        nbytes = 2;
        MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "%s: Not enough data from netcam.");
        ERREXIT(cinfo, JERR_INPUT_EOF);
    }

    src->pub.next_input_byte = src->buffer;
    src->pub.bytes_in_buffer = nbytes;
    src->start_of_file = FALSE;

    return TRUE;
}

static void netcam_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
    netcam_src_ptr  src = (netcam_src_ptr) cinfo->src;

    if (num_bytes > 0) {
        while (num_bytes > (long) src->pub.bytes_in_buffer) {
            num_bytes -= (long) src->pub.bytes_in_buffer;
            (void) netcam_fill_input_buffer (cinfo);
        }
        src->pub.next_input_byte += (size_t) num_bytes;
        src->pub.bytes_in_buffer -= (size_t) num_bytes;
    }
}

static void netcam_term_source(j_decompress_ptr cinfo ATTRIBUTE_UNUSED)
{
}

/**
 * netcam_memory_src
 *
 *    Routine to setup for fetching data from a netcam buffer, used by the
 *    JPEG library decompression routine.
 *
 * Parameters:
 *    cinfo           pointer to the jpeg decompression object.
 *    data            pointer to the image data received from a netcam.
 *    length          total size of the image.
 *
 * Returns:             Nothing
 *
 */
static void netcam_memory_src(j_decompress_ptr cinfo, char *data, int length)
{
    netcam_src_ptr src;

    if (cinfo->src == NULL)
        cinfo->src = (struct jpeg_source_mgr *)
                     (*cinfo->mem->alloc_small)
                     ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                      sizeof (netcam_source_mgr));


    src = (netcam_src_ptr)cinfo->src;
    src->data = data;
    src->length = length;
    src->pub.init_source = netcam_init_source;
    src->pub.fill_input_buffer = netcam_fill_input_buffer;
    src->pub.skip_input_data = netcam_skip_input_data;
    src->pub.resync_to_restart = jpeg_resync_to_restart;
    src->pub.term_source = netcam_term_source;
    src->pub.bytes_in_buffer = 0;
    src->pub.next_input_byte = NULL;
}

/**
 * netcam_error_exit
 *
 *     Routine to override the libjpeg error exit routine so
 *     that we can just throw away the bad frame and continue
 *     with more data from the netcam.
 *
 * Parameters
 *
 *     cinfo           pointer to the decompression control structure.
 *
 * Returns:             does an (ugly) longjmp to get back to netcam_jpeg
 *                      code.
 *
 */
static void netcam_error_exit(j_common_ptr cinfo)
{
    /* Fetch our pre-stored pointer to the netcam context. */
    netcam_context_ptr netcam = cinfo->client_data;
    /* Output the message associated with the error. */
    (*cinfo->err->output_message)(cinfo);
    /* Set flag to show the decompression had errors. */
    netcam->jpeg_error |= 1;
    /* Need to "cleanup" the aborted decompression. */
    jpeg_destroy (cinfo);

    MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: netcam->jpeg_error %d",
               netcam->jpeg_error);

    /* Jump back to wherever we started. */
    longjmp(netcam->setjmp_buffer, 1);
}

/**
 * netcam_output_message
 *
 *     Routine to override the libjpeg error message output routine.
 *     We do this so that we can output our module and thread i.d.,
 *     as well as put the message to the motion log.
 *
 * Parameters
 *
 *     cinfo           pointer to the decompression control structure.
 *
 * Returns              Nothing
 *
 */
static void netcam_output_message(j_common_ptr cinfo)
{
    char buffer[JMSG_LENGTH_MAX];

    /* Fetch our pre-stored pointer to the netcam context. */
    netcam_context_ptr netcam = cinfo->client_data;

    /*
     * While experimenting with a "appro" netcam it was discovered
     * that the jpeg data produced by the camera caused warning
     * messages from libjpeg (JWRN_EXTRANEOUS_DATA).  The following
     * code is to assure that specific warning is ignored.
     *
     * NOTE: It's likely that we will discover other error message
     * codes which we want to ignore.  In that case, we should have
     * some sort of table-lookup to decide which messages we really
     * care about.
     */
    if ((cinfo->err->msg_code != JWRN_EXTRANEOUS_DATA) &&
        (cinfo->err->msg_code == JWRN_NOT_SEQUENTIAL) && (!netcam->netcam_tolerant_check))
        netcam->jpeg_error |= 2;    /* Set flag to show problem */

    /*
     * Format the message according to library standards.
     * Write it out to the motion log.
     */
     (*cinfo->err->format_message)(cinfo, buffer);
     MOTION_LOG(DBG, TYPE_NETCAM, NO_ERRNO, "%s: %s", buffer);

}

/**
 * netcam_init_jpeg
 *
 *     Initialises the JPEG library prior to doing a
 *     decompression.
 *
 * Parameters:
 *     netcam          pointer to netcam_context.
 *     cinfo           pointer to JPEG decompression context.
 *
 * Returns:           Error code.
 */
static int netcam_init_jpeg(netcam_context_ptr netcam, j_decompress_ptr cinfo)
{
    netcam_buff_ptr buff;

    /*
     * First we check whether a new image has arrived.  If not, we
     * setup to wait for 1/2 a frame time.  This will (hopefully)
     * help in synchronizing the camera frames with the motion main
     * loop.
     */
    pthread_mutex_lock(&netcam->mutex);

    if (netcam->imgcnt_last == netcam->imgcnt) {    /* Need to wait */
        struct timespec waittime;
        struct timeval curtime;
        int retcode;

        /*
         * We calculate the delay time (representing the desired frame
         * rate).  This delay time is in *nanoseconds*.
         * We will wait 0.5 seconds which gives a practical minimum
         * framerate of 2 which is desired for the motion_loop to
         * function.
         */
        gettimeofday(&curtime, NULL);
        curtime.tv_usec += 500000;

        if (curtime.tv_usec > 1000000) {
            curtime.tv_usec -= 1000000;
            curtime.tv_sec++;
        }

        waittime.tv_sec = curtime.tv_sec;
        waittime.tv_nsec = 1000L * curtime.tv_usec;

        do {
            retcode = pthread_cond_timedwait(&netcam->pic_ready,
                                             &netcam->mutex, &waittime);
        } while (retcode == EINTR);

        if (retcode) {    /* We assume a non-zero reply is ETIMEOUT */
            pthread_mutex_unlock(&netcam->mutex);

            MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO,
                       "%s: no new pic, no signal rcvd");

            return NETCAM_GENERAL_ERROR | NETCAM_NOTHING_NEW_ERROR;
        }

        MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO,
                   "%s: ***new pic delay successful***");
    }

    netcam->imgcnt_last = netcam->imgcnt;

    /* Set latest buffer as "current". */
    buff = netcam->latest;
    netcam->latest = netcam->jpegbuf;
    netcam->jpegbuf = buff;
    pthread_mutex_unlock(&netcam->mutex);

    /* Clear any error flag from previous work. */
    netcam->jpeg_error = 0;

    buff = netcam->jpegbuf;
    /*
     * Prepare for the decompression.
     * Initialize the JPEG decompression object.
     */
    jpeg_create_decompress(cinfo);

    /* Set up own error exit routine. */
    cinfo->err = jpeg_std_error(&netcam->jerr);
    cinfo->client_data = netcam;
    netcam->jerr.error_exit = netcam_error_exit;
    netcam->jerr.output_message = netcam_output_message;

    /* Specify the data source as our own routine. */
    netcam_memory_src(cinfo, buff->ptr, buff->used);

    /* Read file parameters (rejecting tables-only). */
    jpeg_read_header(cinfo, TRUE);

    /* Override the desired colour space. */
    cinfo->out_color_space = JCS_YCbCr;

    /* Start the decompressor. */
    jpeg_start_decompress(cinfo);

    MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: jpeg_error %d",
               netcam->jpeg_error);

    return netcam->jpeg_error;
}

/**
 * netcam_image_conv
 *
 * Parameters:
 *      netcam          pointer to netcam_context
 *      cinfo           pointer to JPEG decompression context
 *      image           pointer to buffer of destination image (yuv420)
 *
 * Returns :  netcam->jpeg_error
 */
static int netcam_image_conv(netcam_context_ptr netcam,
                               struct jpeg_decompress_struct *cinfo,
                               unsigned char *image)
{
    JSAMPARRAY      line;           /* Array of decomp data lines */
    unsigned char  *wline;          /* Will point to line[0] */
    /* Working variables */
    int             linesize, i;
    unsigned char  *upic, *vpic;
    unsigned char  *pic = image;
    unsigned char   y;              /* Switch for decoding YUV data */
    unsigned int    width, height;

    width = cinfo->output_width;
    height = cinfo->output_height;

    if (width && ((width != netcam->width) || (height != netcam->height))) {
        MOTION_LOG(WRN, TYPE_NETCAM, NO_ERRNO,
                   "%s: JPEG image size %dx%d, JPEG was %dx%d",
                    netcam->width, netcam->height, width, height);
        jpeg_destroy_decompress(cinfo);
        netcam->jpeg_error |= 4;
        return netcam->jpeg_error;
    }
    /* Set the output pointers (these come from YUV411P definition. */
    upic = pic + width * height;
    vpic = upic + (width * height) / 4;


    /* YCbCr format will give us one byte each for YUV. */
    linesize = cinfo->output_width * 3;

    /* Allocate space for one line. */
    line = (cinfo->mem->alloc_sarray)((j_common_ptr) cinfo, JPOOL_IMAGE,
                                       cinfo->output_width * cinfo->output_components, 1);

    wline = line[0];
    y = 0;

    while (cinfo->output_scanline < height) {
        jpeg_read_scanlines(cinfo, line, 1);

        for (i = 0; i < linesize; i += 3) {
            pic[i / 3] = wline[i];
            if (i & 1) {
                upic[(i / 3) / 2] = wline[i + 1];
                vpic[(i / 3) / 2] = wline[i + 2];
            }
        }

        pic += linesize / 3;

        if (y++ & 1) {
            upic += width / 2;
            vpic += width / 2;
        }
    }

    jpeg_finish_decompress(cinfo);
    jpeg_destroy_decompress(cinfo);

    if (netcam->cnt->rotate_data.degrees > 0)
        /* Rotate as specified */
        rotate_map(netcam->cnt, image);

    MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: jpeg_error %d",
               netcam->jpeg_error);

    return netcam->jpeg_error;
}

/**
 * netcam_proc_jpeg
 *
 *    Routine to decode an image received from a netcam into a YUV420P buffer
 *    suitable for processing by motion.
 *
 * Parameters:
 *    netcam    pointer to the netcam_context structure.
 *     image    pointer to a buffer for the returned image.
 *
 * Returns:
 *
 *      0         Success
 *      non-zero  error code from other routines
 *                (e.g. netcam_init_jpeg or netcam_image_conv)
 *                or just NETCAM_GENERAL_ERROR
 */
int netcam_proc_jpeg(netcam_context_ptr netcam, unsigned char *image)
{
    struct jpeg_decompress_struct cinfo;    /* Decompression control struct. */
    int retval = 0;                         /* Value returned to caller. */
    int ret;                                /* Working var. */

    /*
     * This routine is only called from the main thread.
     * We need to "protect" the "latest" image while we
     * decompress it.  netcam_init_jpeg uses
     * netcam->mutex to do this.
     */
    MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: processing jpeg image"
               " - content length %d", netcam->latest->content_length);

    ret = netcam_init_jpeg(netcam, &cinfo);

    if (ret != 0) {
        MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: ret %d", ret);
        return ret;
    }

    /*
     * Do a sanity check on dimensions
     * If dimensions have changed we throw an
     * error message that will cause
     * restart of Motion.
     */
    if (netcam->width) {    /* 0 means not yet init'ed */
        if ((cinfo.output_width != netcam->width) ||
            (cinfo.output_height != netcam->height)) {
            retval = NETCAM_RESTART_ERROR;
            MOTION_LOG(ERR, TYPE_NETCAM, NO_ERRNO, "%s: Camera width/height mismatch "
                       "with JPEG image - expected %dx%d, JPEG %dx%d",
                       " retval %d", netcam->width, netcam->height,
                       cinfo.output_width, cinfo.output_height, retval);
            return retval;
        }
    }

    /* Do the conversion */
    ret = netcam_image_conv(netcam, &cinfo, image);

    if (ret != 0) {
        retval |= NETCAM_JPEG_CONV_ERROR;
        MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: ret %d retval %d",
                   ret, retval);
    }

    return retval;
}

/**
  * netcam_fix_jpeg_header
  *
  *    Routine to decode an image received from a netcam into a YUV420P buffer
  *    suitable for processing by motion.
  *
  * Parameters:
  *    netcam    pointer to the netcam_context structure
  *
  * Returns:  Nothing
  *
  */
void netcam_fix_jpeg_header(netcam_context_ptr netcam)
{
    char *ptr_buffer;

    ptr_buffer = memmem(netcam->receiving->ptr, netcam->receiving->used, "\xff\xd8", 2);

    if (ptr_buffer != NULL) {
        size_t soi_position = 0;

        soi_position = ptr_buffer - netcam->receiving->ptr;

        if (soi_position > 0) {
            memmove(netcam->receiving->ptr, netcam->receiving->ptr + soi_position,
                    netcam->receiving->used - soi_position);
            netcam->receiving->used -= soi_position;
        }

        // if (debug_level > CAMERA_INFO)
        //    motion_log(LOG_INFO, 0, "%s: SOI found , position %d",
        //               __FUNCTION__, soi_position);
    }
}


/**
 * netcam_get_dimensions
 *
 *    This function gets the height and width of the JPEG image
 *    located in the supplied netcam_image_buffer.
 *
 * Parameters
 *
 *    netcam     pointer to the netcam context.
 *
 * Returns:   Nothing, but fills in width and height into context.
 *
 */
void netcam_get_dimensions(netcam_context_ptr netcam)
{
    struct jpeg_decompress_struct cinfo; /* Decompression control struct. */
    int ret;

    ret = netcam_init_jpeg(netcam, &cinfo);

    netcam->width = cinfo.output_width;
    netcam->height = cinfo.output_height;
    netcam->JFIF_marker = cinfo.saw_JFIF_marker;

    jpeg_destroy_decompress(&cinfo);

    MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "%s: JFIF_marker %s PRESENT ret %d",
               netcam->JFIF_marker ? "IS" : "NOT", ret);
}
