/*
 * 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 optionnal
	*/
	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 inexistant 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;
}
