/*
 * The copyright in this software is being made available under the 2-clauses
 * BSD License, included below. This software may be subject to other third
 * party and contributor rights, including patent rights, and no such rights
 * are granted under this license.
 *
 * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
 * Copyright (c) 2002-2014, Professor Benoit Macq
 * Copyright (c) 2003-2007, Francois-Olivier Devaux
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "openjpeg.h"
#include "cio.h"
#include "j2k.h"
#include "jp2.h"
#include "mj2.h"

static int int_ceildiv(int a, int b)
{
    return (a + b - 1) / b;
}

/**
Size of memory first allocated for MOOV box
*/
#define TEMP_BUF 10000

#define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51"

/* -------------------------------------------------------------------------- */

static int test_image(const char *fname, mj2_cparameters_t *cp)
{
    FILE *reader;
    opj_image_t *image;
    unsigned char *src;
    opj_dinfo_t *dinfo;
    opj_cio_t *cio;
    opj_dparameters_t dparameters;
    int success;
    long src_len;

    success = 0;

    if ((reader = fopen(fname, "rb")) == NULL) {
        return success;
    }

    fseek(reader, 0, SEEK_END);
    src_len = ftell(reader);
    fseek(reader, 0, SEEK_SET);
    src = (unsigned char*) malloc(src_len);
    fread(src, 1, src_len, reader);
    fclose(reader);

    if (memcmp(src, J2K_CODESTREAM_MAGIC, 4) != 0) {
        free(src);
        return success;
    }
    memset(&dparameters, 0, sizeof(opj_dparameters_t));

    opj_set_default_decoder_parameters(&dparameters);

    dinfo = opj_create_decompress(CODEC_J2K);

    opj_setup_decoder(dinfo, &dparameters);

    cio = opj_cio_open((opj_common_ptr)dinfo, src, src_len);

    image = opj_decode(dinfo, cio);

    free(src);
    cio->buffer = NULL;
    opj_cio_close(cio);

    if (image == NULL) {
        goto fin;
    }

    cp->numcomps = image->numcomps;
    cp->w = image->comps[0].w;
    cp->h = image->comps[0].h;
    cp->prec = image->comps[0].prec;

    if (image->numcomps > 2) {
        if ((image->comps[0].dx == 1)
                && (image->comps[1].dx == 2)
                && (image->comps[2].dx == 2)
                && (image->comps[0].dy == 1)
                && (image->comps[1].dy == 2)
                && (image->comps[2].dy == 2)) { /* horizontal and vertical*/
            /*   Y420*/
            cp->enumcs = ENUMCS_SYCC;
            cp->CbCr_subsampling_dx = 2;
            cp->CbCr_subsampling_dy = 2;
        } else if ((image->comps[0].dx == 1)
                   && (image->comps[1].dx == 2)
                   && (image->comps[2].dx == 2)
                   && (image->comps[0].dy == 1)
                   && (image->comps[1].dy == 1)
                   && (image->comps[2].dy == 1)) { /* horizontal only*/
            /*   Y422*/
            cp->enumcs = ENUMCS_SYCC;
            cp->CbCr_subsampling_dx = 2;
            cp->CbCr_subsampling_dy = 1;
        } else if ((image->comps[0].dx == 1)
                   && (image->comps[1].dx == 1)
                   && (image->comps[2].dx == 1)
                   && (image->comps[0].dy == 1)
                   && (image->comps[1].dy == 1)
                   && (image->comps[2].dy == 1)) {
            /*   Y444 or RGB */

            if (image->color_space ==  CLRSPC_SRGB) {
                cp->enumcs = ENUMCS_SRGB;

                /*    cp->CbCr_subsampling_dx = 0; */
                /*    cp->CbCr_subsampling_dy = 0; */
            } else {
                cp->enumcs = ENUMCS_SYCC;

                cp->CbCr_subsampling_dx = 1;
                cp->CbCr_subsampling_dy = 1;
            }
        } else {
            goto fin;
        }
    } else {
        cp->enumcs = ENUMCS_GRAY;
        /*  cp->CbCr_subsampling_dx = 0; */
        /*  cp->CbCr_subsampling_dy = 0; */
    }
    if (image->icc_profile_buf) {
        cp->meth = 2;
        free(image->icc_profile_buf);
        image->icc_profile_buf = NULL;
    } else {
        cp->meth = 1;
    }

    success = 1;
fin:
    if (dinfo) {
        opj_destroy_decompress(dinfo);
    }

    if (image) {
        opj_image_destroy(image);
    }

    return success;
}

/**
sample error callback expecting a FILE* client object
*/
static void error_callback(const char *msg, void *client_data)
{
    FILE *stream = (FILE*)client_data;
    fprintf(stream, "[ERROR] %s", msg);
}
/**
sample warning callback expecting a FILE* client object
*/
static void warning_callback(const char *msg, void *client_data)
{
    FILE *stream = (FILE*)client_data;
    fprintf(stream, "[WARNING] %s", msg);
}
/**
sample debug callback expecting a FILE* client object
*/
static void info_callback(const char *msg, void *client_data)
{
    FILE *stream = (FILE*)client_data;
    fprintf(stream, "[INFO] %s", msg);
}

/* -------------------------------------------------------------------------- */



static void read_siz_marker(FILE *file, opj_image_t *image)
{
    int len, i;
    char buf, buf2[2];
    unsigned char *siz_buffer;
    opj_cio_t *cio;

    fseek(file, 0, SEEK_SET);
    do {
        fread(&buf, 1, 1, file);
        if (buf == (char)0xff) {
            fread(&buf, 1, 1, file);
        }
    } while (!(buf == (char)0x51));

    fread(buf2, 2, 1, file);  /* Lsiz                */
    len = ((buf2[0]) << 8) + buf2[1];

    siz_buffer = (unsigned char*) malloc(len * sizeof(unsigned char));
    fread(siz_buffer, len, 1, file);
    cio = opj_cio_open(NULL, siz_buffer, len);

    cio_read(cio, 2);         /* Rsiz (capabilities) */
    image->x1 = cio_read(cio, 4); /* Xsiz                */
    image->y1 = cio_read(cio, 4); /* Ysiz                */
    image->x0 = cio_read(cio, 4); /* X0siz               */
    image->y0 = cio_read(cio, 4); /* Y0siz               */
    cio_skip(cio, 16);            /* XTsiz, YTsiz, XT0siz, YT0siz        */

    image->numcomps = cio_read(cio, 2);   /* Csiz                */
    image->comps =
        (opj_image_comp_t *) malloc(image->numcomps * sizeof(opj_image_comp_t));

    for (i = 0; i < image->numcomps; i++) {
        int tmp;
        tmp = cio_read(cio, 1);     /* Ssiz_i          */
        image->comps[i].prec = (tmp & 0x7f) + 1;
        image->comps[i].sgnd = tmp >> 7;
        image->comps[i].dx = cio_read(cio, 1);  /* XRsiz_i         */
        image->comps[i].dy = cio_read(cio, 1);  /* YRsiz_i         */
        image->comps[i].resno_decoded = 0;  /* number of resolution decoded */
        image->comps[i].factor = 0; /* reducing factor by component */
    }
    fseek(file, 0, SEEK_SET);
    opj_cio_close(cio);
    free(siz_buffer);
}

static void setparams(opj_mj2_t *movie, opj_image_t *image)
{
    int i, depth_0, depth, sign;

    movie->tk[0].w = int_ceildiv(image->x1 - image->x0, image->comps[0].dx);
    movie->tk[0].h = int_ceildiv(image->y1 - image->y0, image->comps[0].dy);
    mj2_init_stdmovie(movie);

    movie->tk[0].depth = image->comps[0].prec;

    if (image->numcomps == 3) {
        if ((image->comps[0].dx == 1)
                && (image->comps[1].dx == 1)
                && (image->comps[2].dx == 1)) {
            movie->tk[0].CbCr_subsampling_dx = 1;
        } else if ((image->comps[0].dx == 1)
                   && (image->comps[1].dx == 2)
                   && (image->comps[2].dx == 2)) {
            movie->tk[0].CbCr_subsampling_dx = 2;
        } else {
            fprintf(stderr, "Image component sizes are incoherent\n");
        }

        if ((image->comps[0].dy == 1)
                && (image->comps[1].dy == 1)
                && (image->comps[2].dy == 1)) {
            movie->tk[0].CbCr_subsampling_dy = 1;
        } else if ((image->comps[0].dy == 1)
                   && (image->comps[1].dy == 2)
                   && (image->comps[2].dy == 2)) {
            movie->tk[0].CbCr_subsampling_dy = 2;
        } else {
            fprintf(stderr, "Image component sizes are incoherent\n");
        }
    }

    movie->tk[0].sample_rate = 25;

    movie->tk[0].jp2_struct.numcomps = image->numcomps;   /* NC */

    /* Init Standard jp2 structure */

    movie->tk[0].jp2_struct.comps =
        (opj_jp2_comps_t *) malloc(movie->tk[0].jp2_struct.numcomps * sizeof(
                                       opj_jp2_comps_t));
    movie->tk[0].jp2_struct.precedence = 0;   /* PRECEDENCE*/
    movie->tk[0].jp2_struct.approx = 0;   /* APPROX*/
    movie->tk[0].jp2_struct.brand = JP2_JP2;  /* BR         */
    movie->tk[0].jp2_struct.minversion = 0;   /* MinV       */
    movie->tk[0].jp2_struct.numcl = 1;
    movie->tk[0].jp2_struct.cl = (unsigned int *) malloc(
                                     movie->tk[0].jp2_struct.numcl * sizeof(int));
    movie->tk[0].jp2_struct.cl[0] = JP2_JP2;  /* CL0 : JP2  */
    movie->tk[0].jp2_struct.C = 7;      /* C : Always 7*/
    movie->tk[0].jp2_struct.UnkC =
        0;      /* UnkC, colorspace specified in colr box*/
    movie->tk[0].jp2_struct.IPR = 0;      /* IPR, no intellectual property*/
    movie->tk[0].jp2_struct.w = int_ceildiv(image->x1 - image->x0,
                                            image->comps[0].dx);
    movie->tk[0].jp2_struct.h = int_ceildiv(image->y1 - image->y0,
                                            image->comps[0].dy);

    depth_0 = image->comps[0].prec - 1;
    sign = image->comps[0].sgnd;
    movie->tk[0].jp2_struct.bpc = depth_0 + (sign << 7);

    for (i = 1; i < image->numcomps; i++) {
        depth = image->comps[i].prec - 1;
        sign = image->comps[i].sgnd;
        if (depth_0 != depth) {
            movie->tk[0].jp2_struct.bpc = 255;
        }
    }

    for (i = 0; i < image->numcomps; i++)
        movie->tk[0].jp2_struct.comps[i].bpcc =
            image->comps[i].prec - 1 + (image->comps[i].sgnd << 7);

    if ((image->numcomps == 1 || image->numcomps == 3)
            && (movie->tk[0].jp2_struct.bpc != 255)) {
        movie->tk[0].jp2_struct.meth = 1;
    } else {
        movie->tk[0].jp2_struct.meth = 2;
    }

    if (image->numcomps == 1) {
        movie->tk[0].jp2_struct.enumcs = 17;    /* Grayscale */
    }

    else if ((image->comps[0].dx == 1)
             && (image->comps[1].dx == 1)
             && (image->comps[2].dx == 1)
             && (image->comps[0].dy == 1)
             && (image->comps[1].dy == 1)
             && (image->comps[2].dy == 1)) {
        movie->tk[0].jp2_struct.enumcs = 16;    /* RGB */
    }

    else if ((image->comps[0].dx == 1)
             && (image->comps[1].dx == 2)
             && (image->comps[2].dx == 2)
             && (image->comps[0].dy == 1)
             && (image->comps[1].dy == 2)
             && (image->comps[2].dy == 2)) {
        movie->tk[0].jp2_struct.enumcs = 18;    /* YUV */
    }

    else {
        movie->tk[0].jp2_struct.enumcs = 0;    /* Unknown profile */
    }
}

int main(int argc, char *argv[])
{
    opj_cinfo_t* cinfo;
    opj_event_mgr_t event_mgr;      /* event manager */
    unsigned int snum;
    opj_mj2_t *movie;
    mj2_sample_t *sample;
    unsigned char* frame_codestream;
    FILE *mj2file, *j2kfile;
    char *j2kfilename;
    unsigned char *buf;
    int offset, mdat_initpos;
    opj_image_t img;
    opj_cio_t *cio;
    mj2_cparameters_t parameters;

    if (argc != 3) {
        printf("Usage: %s source_location mj2_filename\n", argv[0]);
        printf("Example: %s input/input output.mj2\n", argv[0]);
        return 1;
    }

    mj2file = fopen(argv[2], "wb");

    if (!mj2file) {
        fprintf(stderr, "failed to open %s for writing\n", argv[2]);
        return 1;
    }
    memset(&img, 0, sizeof(opj_image_t));
    /*
    configure the event callbacks (not required)
    setting of each callback is optional
    */
    memset(&event_mgr, 0, sizeof(opj_event_mgr_t));
    event_mgr.error_handler = error_callback;
    event_mgr.warning_handler = warning_callback;
    event_mgr.info_handler = info_callback;

    /* get a MJ2 decompressor handle */
    cinfo = mj2_create_compress();

    /* catch events using our callbacks and give a local context */
    opj_set_event_mgr((opj_common_ptr)cinfo, &event_mgr, stderr);

    /* setup the decoder encoding parameters using user parameters */
    memset(&parameters, 0, sizeof(mj2_cparameters_t));
    movie = (opj_mj2_t*) cinfo->mj2_handle;

    j2kfilename = (char*)malloc(strlen(argv[1]) + 12);/* max. '%6d' */
    sprintf(j2kfilename, "%s_00001.j2k", argv[1]);

    if (test_image(j2kfilename, &parameters) == 0) {
        goto fin;
    }

    parameters.frame_rate = 25; /* DEFAULT */

    mj2_setup_encoder(movie, &parameters);


    /* Writing JP, FTYP and MDAT boxes
    Assuming that the JP and FTYP boxes won't be longer than 300 bytes */

    buf = (unsigned char*) malloc(300 * sizeof(unsigned char));
    cio = opj_cio_open(movie->cinfo, buf, 300);
    mj2_write_jp(cio);
    mj2_write_ftyp(movie, cio);
    mdat_initpos = cio_tell(cio);
    cio_skip(cio, 4);
    cio_write(cio, MJ2_MDAT, 4);
    fwrite(buf, cio_tell(cio), 1, mj2file);
    free(buf);

    /* Insert each j2k codestream in a JP2C box */
    snum = 0;
    offset = 0;
    while (1) {
        mj2_sample_t * new_sample;
        mj2_chunk_t * new_chunk;
        sample = &movie->tk[0].sample[snum];
        sprintf(j2kfilename, "%s_%05d.j2k", argv[1], snum);
        j2kfile = fopen(j2kfilename, "rb");
        if (!j2kfile) {
            if (snum == 0) { /* Could not open a single codestream */
                fprintf(stderr, "failed to open %s for reading\n", j2kfilename);
                return 1;
            } else {        /* Tried to open a inexistent codestream */
                fprintf(stdout, "%d frames are being added to the MJ2 file\n", snum);
                break;
            }
        }

        /* Calculating offset for samples and chunks */
        offset += cio_tell(cio);
        sample->offset = offset;
        movie->tk[0].chunk[snum].offset =
            offset;  /* There will be one sample per chunk */

        /* Calculating sample size */
        fseek(j2kfile, 0, SEEK_END);
        sample->sample_size = ftell(j2kfile) +
                              8; /* Sample size is codestream + JP2C box header */
        fseek(j2kfile, 0, SEEK_SET);

        /* Reading siz marker of j2k image for the first codestream */
        if (snum == 0) {
            read_siz_marker(j2kfile, &img);
        }

        /* Writing JP2C box header */
        frame_codestream = (unsigned char*) malloc(sample->sample_size + 8);
        cio = opj_cio_open(movie->cinfo, frame_codestream, sample->sample_size);
        cio_write(cio, sample->sample_size, 4); /* Sample size */
        cio_write(cio, JP2_JP2C, 4); /* JP2C */

        /* Writing codestream from J2K file to MJ2 file */
        fread(frame_codestream + 8, sample->sample_size - 8, 1, j2kfile);
        fwrite(frame_codestream, sample->sample_size, 1, mj2file);
        cio_skip(cio, sample->sample_size - 8);

        /* Ending loop */
        fclose(j2kfile);
        snum++;
        new_sample = (mj2_sample_t*)
                     realloc(movie->tk[0].sample, (snum + 1) * sizeof(mj2_sample_t));
        new_chunk = (mj2_chunk_t*)
                    realloc(movie->tk[0].chunk, (snum + 1) * sizeof(mj2_chunk_t));
        if (new_sample && new_chunk) {
            movie->tk[0].sample = new_sample;
            movie->tk[0].chunk = new_chunk;
        } else {
            fprintf(stderr, "Failed to allocate enough memory to read %s\n", j2kfilename);
            return 1;
        }
        free(frame_codestream);
    }

    /* Writing the MDAT box length in header */
    offset += cio_tell(cio);
    buf = (unsigned char*) malloc(4 * sizeof(unsigned char));
    cio = opj_cio_open(movie->cinfo, buf, 4);
    cio_write(cio, offset - mdat_initpos, 4);
    fseek(mj2file, (long)mdat_initpos, SEEK_SET);
    fwrite(buf, 4, 1, mj2file);
    fseek(mj2file, 0, SEEK_END);
    free(buf);

    /* Setting movie parameters */
    movie->tk[0].num_samples = snum;
    movie->tk[0].num_chunks = snum;
    setparams(movie, &img);

    /* Writing MOOV box */
    buf = (unsigned char*) malloc((TEMP_BUF + snum * 20) * sizeof(unsigned char));
    cio = opj_cio_open(movie->cinfo, buf, (TEMP_BUF + snum * 20));
    mj2_write_moov(movie, cio);
    fwrite(buf, cio_tell(cio), 1, mj2file);

    /* Ending program */
    free(img.comps);
    opj_cio_close(cio);

fin:
    fclose(mj2file);
    mj2_destroy_compress(movie);
    free(j2kfilename);

    return 0;
}
