/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#if defined(WIN32)
 #include <basetsd.h>
#endif
#include <setjmp.h>
#include <stdio.h>
#include <string.h>

#include "common_video/jpeg/include/jpeg.h"
#include "common_video/jpeg/data_manager.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "libyuv.h"
#include "libyuv/mjpeg_decoder.h"

extern "C" {
#if defined(USE_SYSTEM_LIBJPEG)
#include <jpeglib.h>
#else
#include "jpeglib.h"
#endif
}


namespace webrtc
{

// Error handler
struct myErrorMgr {

    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};
typedef struct myErrorMgr * myErrorPtr;

METHODDEF(void)
MyErrorExit (j_common_ptr cinfo)
{
    myErrorPtr myerr = (myErrorPtr) cinfo->err;

    // Return control to the setjmp point
    longjmp(myerr->setjmp_buffer, 1);
}

JpegEncoder::JpegEncoder()
{
   _cinfo = new jpeg_compress_struct;
    strcpy(_fileName, "Snapshot.jpg");
}

JpegEncoder::~JpegEncoder()
{
    if (_cinfo != NULL)
    {
        delete _cinfo;
        _cinfo = NULL;
    }
}


WebRtc_Word32
JpegEncoder::SetFileName(const char* fileName)
{
    if (!fileName)
    {
        return -1;
    }

    if (fileName)
    {
        strncpy(_fileName, fileName, 256);
        _fileName[256] = 0;
    }
    return 0;
}


WebRtc_Word32
JpegEncoder::Encode(const I420VideoFrame& inputImage)
{
    if (inputImage.IsZeroSize())
    {
        return -1;
    }
    if (inputImage.width() < 1 || inputImage.height() < 1)
    {
        return -1;
    }

    FILE* outFile = NULL;

    const int width = inputImage.width();
    const int height = inputImage.height();

    // Set error handler
    myErrorMgr      jerr;
    _cinfo->err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = MyErrorExit;
    // Establish the setjmp return context
    if (setjmp(jerr.setjmp_buffer))
    {
        // If we get here, the JPEG code has signaled an error.
        jpeg_destroy_compress(_cinfo);
        if (outFile != NULL)
        {
            fclose(outFile);
        }
        return -1;
    }

    if ((outFile = fopen(_fileName, "wb")) == NULL)
    {
        return -2;
    }
    // Create a compression object
    jpeg_create_compress(_cinfo);

    // Setting destination file
    jpeg_stdio_dest(_cinfo, outFile);

    // Set parameters for compression
    _cinfo->in_color_space = JCS_YCbCr;
    jpeg_set_defaults(_cinfo);

    _cinfo->image_width = width;
    _cinfo->image_height = height;
    _cinfo->input_components = 3;

    _cinfo->comp_info[0].h_samp_factor = 2;   // Y
    _cinfo->comp_info[0].v_samp_factor = 2;
    _cinfo->comp_info[1].h_samp_factor = 1;   // U
    _cinfo->comp_info[1].v_samp_factor = 1;
    _cinfo->comp_info[2].h_samp_factor = 1;   // V
    _cinfo->comp_info[2].v_samp_factor = 1;
    _cinfo->raw_data_in = TRUE;
    // Converting to a buffer
    // TODO(mikhal): This is a tmp implementation. Will update to use LibYuv
    // Encode when that becomes available.
    unsigned int length = CalcBufferSize(kI420, width, height);
    scoped_array<uint8_t> image_buffer(new uint8_t[length]);
    ExtractBuffer(inputImage, length, image_buffer.get());
    int height16 = (height + 15) & ~15;
    WebRtc_UWord8* imgPtr = image_buffer.get();

    WebRtc_UWord8* origImagePtr = NULL;
    if (height16 != height)
    {
        // Copy image to an adequate size buffer
        WebRtc_UWord32 requiredSize = CalcBufferSize(kI420, width, height16);
        origImagePtr = new WebRtc_UWord8[requiredSize];
        memset(origImagePtr, 0, requiredSize);
        memcpy(origImagePtr, image_buffer.get(), length);
        imgPtr = origImagePtr;
    }

    jpeg_start_compress(_cinfo, TRUE);

    JSAMPROW y[16],u[8],v[8];
    JSAMPARRAY data[3];

    data[0] = y;
    data[1] = u;
    data[2] = v;

    int i, j;

    for (j = 0; j < height; j += 16)
    {
        for (i = 0; i < 16; i++)
        {
            y[i] = (JSAMPLE*)imgPtr + width * (i + j);

            if (i % 2 == 0)
            {
                u[i / 2] = (JSAMPLE*) imgPtr + width * height +
                            width / 2 * ((i + j) / 2);
                v[i / 2] = (JSAMPLE*) imgPtr + width * height +
                            width * height / 4 + width / 2 * ((i + j) / 2);
            }
        }
        jpeg_write_raw_data(_cinfo, data, 16);
    }

    jpeg_finish_compress(_cinfo);
    jpeg_destroy_compress(_cinfo);

    fclose(outFile);

    if (origImagePtr != NULL)
    {
        delete [] origImagePtr;
    }

    return 0;
}

int ConvertJpegToI420(const EncodedImage& input_image,
                      I420VideoFrame* output_image) {

  if (output_image == NULL)
    return -1;
  // TODO(mikhal): Update to use latest API from LibYuv when that becomes
  // available.
  libyuv::MJpegDecoder jpeg_decoder;
  bool ret = jpeg_decoder.LoadFrame(input_image._buffer, input_image._size);
  if (ret == false)
    return -1;
  if (jpeg_decoder.GetNumComponents() == 4)
    return -2;  // not supported.
  int width = jpeg_decoder.GetWidth();
  int height = jpeg_decoder.GetHeight();
  output_image->CreateEmptyFrame(width, height, width,
                                 (width + 1) / 2, (width + 1) / 2);
  return ConvertToI420(kMJPG,
                       input_image._buffer,
                       0, 0,  // no cropping
                       width, height,
                       input_image._size,
                       kRotateNone,
                       output_image);
}


}
