/*
 * $Id$
 *
 * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
 * Copyright (c) 2002-2014, Professor Benoit Macq
 * Copyright (c) 2010-2011, Kaori Hagihara
 * 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 <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include "box_manager.h"
#include "opj_inttypes.h"

#ifdef SERVER
#include "fcgi_stdio.h"
#define logstream FCGI_stdout
#else
#define FCGI_stdout stdout
#define FCGI_stderr stderr
#define logstream stderr
#endif /*SERVER*/

boxlist_param_t * gene_boxlist(void)
{
  boxlist_param_t *boxlist;

  boxlist = (boxlist_param_t *)malloc( sizeof(boxlist_param_t));
  
  boxlist->first = NULL;
  boxlist->last  = NULL;

  return boxlist;
}

boxlist_param_t * get_boxstructure( int fd, OPJ_OFF_T offset, OPJ_SIZE_T length)
{
  boxlist_param_t *boxlist;
  box_param_t *box;
  OPJ_OFF_T pos;
  
  boxlist = NULL;
  pos = offset;
  assert( (OPJ_OFF_T)length>=0);
  do{
    if(!(box = gene_boxbyOffset( fd, pos)))
      break;
    
    assert( (OPJ_OFF_T)box->length >= 0);
    pos += (OPJ_OFF_T)box->length;
    
    if( !boxlist)
      boxlist = gene_boxlist();
    insert_box_into_list( box, boxlist);
  }while( pos < offset+(OPJ_OFF_T)length);

  return boxlist;
}

box_param_t * gene_boxbyOffset( int fd, OPJ_OFF_T offset)
{
  Byte_t *data;
  Byte8_t boxlen;
  Byte_t headlen;
  char *boxtype;
  box_param_t *box;

  /* read LBox and TBox*/
  if(!(data = fetch_bytes( fd, offset, 8))){
    fprintf( FCGI_stderr, "Error: error in gene_boxbyOffset( %d, %" PRId64 ")\n", fd, offset);
    return NULL;
  }
  
  headlen = 8;
  boxlen = (Byte8_t)big4(data);
  boxtype = (char *)(data+4);  

  /* box type constraint*/
  if( !isalpha(boxtype[0]) || !isalpha(boxtype[1]) ||
      (!isalnum(boxtype[2])&&!isspace(boxtype[2])) ||
      (!isalpha(boxtype[3])&&!isspace(boxtype[3]))){
    free( data);
    return NULL;
  }
  
  if( boxlen == 1){
    Byte_t *data2;
    headlen = 16;
    /* read XLBox*/
    if((data2 = fetch_bytes( fd, offset+8, 8))){
      boxlen = big8(data2);
      free(data2);
    }
    else{
      fprintf( FCGI_stderr, "Error: error in gene_boxbyOffset( %d, %" PRId64 ")\n", fd, offset);
      free( data);
      return NULL;
    }
  }
  box = (box_param_t *)malloc( sizeof( box_param_t));
  box->fd = fd;
  box->offset = offset;
  box->headlen = headlen;
  box->length = boxlen;
  strncpy( box->type, boxtype, 4);
  box->next = NULL;
  free( data);
  return box;
}

box_param_t * gene_boxbyOffinStream( Byte_t *stream, OPJ_OFF_T offset)
{
  Byte8_t boxlen;
  Byte_t headlen;
  char *boxtype;
  box_param_t *box;

  /* read LBox and TBox*/
  headlen = 8;
  boxlen = (Byte8_t)big4( stream);
  boxtype = (char *)( stream+4);  

  /* box type constraint*/
  if( !isalpha(boxtype[0]) || !isalpha(boxtype[1]) ||
      (!isalnum(boxtype[2])&&!isspace(boxtype[2])) ||
      (!isalpha(boxtype[3])&&!isspace(boxtype[3]))){
    return NULL;
  }
  
  if( boxlen == 1){
    headlen = 16;
    boxlen = big8( stream+8); /* read XLBox*/
  }
  box = (box_param_t *)malloc( sizeof( box_param_t));
  box->fd = -1;
  box->offset = offset;
  box->headlen = headlen;
  box->length = boxlen;
  strncpy( box->type, boxtype, 4);
  box->next = NULL;

  return box;
}


box_param_t * gene_boxbyType( int fd, OPJ_OFF_T offset, OPJ_SIZE_T length, const char TBox[])
{
  OPJ_OFF_T pos;
  Byte_t *data;
  Byte8_t boxlen;
  Byte_t headlen;
  char *boxtype;
  box_param_t *foundbox;

  
  if( length==0){ /* set the max length*/
    if( get_filesize( fd) <= offset )
      return NULL;
    assert( get_filesize( fd) > offset );
    assert( offset >= 0 );
    length = (OPJ_SIZE_T)(get_filesize( fd) - offset);
  }

  pos = offset;
  assert( pos >= 0 );
  assert( (OPJ_OFF_T)length >= 0 );
  while( pos < offset+(OPJ_OFF_T)length-7){ /* LBox+TBox-1=7*/
    
    /* read LBox and TBox*/
    if((data = fetch_bytes( fd, pos, 8))){
      headlen = 8;
      boxlen = (Byte8_t)big4(data);
      boxtype = (char *)(data+4);

      if( boxlen == 1){
	Byte_t *data2;
	headlen = 16;
	/* read XLBox*/
	if((data2 = fetch_bytes( fd, pos+8, 8))){
	  boxlen = big8(data2);
	  free(data2);
	}
	else{
	  fprintf( FCGI_stderr, "Error: error in gene_boxbyType( %d, %" PRId64 ", %" PRId64 ", %s)\n", fd, offset, length, TBox);
	  return NULL;
	}
      }
      if( strncmp ( boxtype, TBox, 4) == 0){
	foundbox = (box_param_t *)malloc( sizeof( box_param_t));
	foundbox->fd = fd;
	foundbox->offset = pos;
	foundbox->headlen = headlen;
	foundbox->length = boxlen;
	strncpy( foundbox->type, TBox, 4);
	foundbox->next = NULL;
	free( data);
	return foundbox;
      }
      free( data);
    }
    else{
      fprintf( FCGI_stderr, "Error: error in gene_boxbyType( %d, %" PRId64 ", %" PRId64 ", %s)\n", fd, offset, length, TBox);
      return NULL;
    }
    assert( ((Byte8_t)pos+boxlen)>=(Byte8_t)pos);
    pos+= (OPJ_OFF_T)boxlen;
  }
  fprintf( FCGI_stderr, "Error: Box %s not found\n", TBox);

  return NULL;
}

box_param_t * gene_boxbyTypeinStream( Byte_t *stream, OPJ_OFF_T offset, OPJ_SIZE_T length, const char TBox[])
{
  OPJ_OFF_T pos;
  Byte_t *data;
  Byte8_t boxlen;
  Byte_t headlen;
  char *boxtype;
  box_param_t *foundbox;

  pos = offset;
  assert( pos >= 0 );
  assert( (OPJ_OFF_T)length >= 0 );
  while( pos < offset+(OPJ_OFF_T)(length)-7){ /* LBox+TBox-1=7*/
    
    /* read LBox and TBox*/
    data = stream + pos;
    headlen = 8;
    boxlen = (Byte8_t)big4(data);
    boxtype = (char *)(data+4);
   
    if( boxlen == 1){
      /* read XLBox*/
      headlen = 16;
      boxlen = big8( data+8);
    }

    if( strncmp ( boxtype, TBox, 4) == 0){
      foundbox = (box_param_t *)malloc( sizeof( box_param_t));
      foundbox->fd = -1;
      foundbox->offset = pos;
      foundbox->headlen = headlen;
      foundbox->length = boxlen;
      strncpy( foundbox->type, TBox, 4);
      foundbox->next = NULL;
      return foundbox;
    }
    assert( ((Byte8_t)pos+boxlen)>=(Byte8_t)pos);
    pos+= (OPJ_OFF_T)boxlen;
  }
  fprintf( FCGI_stderr, "Error: Box %s not found\n", TBox);
  
  return NULL;
}

box_param_t * gene_childboxbyOffset( box_param_t *superbox, OPJ_OFF_T offset)
{
  return gene_boxbyOffset( superbox->fd, get_DBoxoff( superbox)+offset);
}

box_param_t * gene_childboxbyType( box_param_t *superbox, OPJ_OFF_T offset, const char TBox[])
{
  OPJ_SIZE_T DBOXlen = get_DBoxlen(superbox);
  assert( offset >= 0 );
  if( DBOXlen < (OPJ_SIZE_T)offset )
    {
    fprintf( FCGI_stderr, "Error: Impossible happen %lu < %ld\n", DBOXlen, offset);
    return NULL;
    }
  return gene_boxbyType( superbox->fd, get_DBoxoff( superbox)+offset, DBOXlen-(OPJ_SIZE_T)offset, TBox);
}

OPJ_OFF_T get_DBoxoff( box_param_t *box)
{
  return box->offset+box->headlen;
}

OPJ_SIZE_T get_DBoxlen( box_param_t *box)
{
  return box->length - box->headlen;
}

Byte_t * fetch_headbytes( box_param_t *box)
{
  return fetch_bytes( box->fd, box->offset, box->headlen);
}

Byte_t * fetch_DBoxbytes( box_param_t *box, OPJ_OFF_T offset, OPJ_SIZE_T size)
{
  return fetch_bytes( box->fd, get_DBoxoff( box)+offset, size);
}

Byte_t fetch_DBox1byte( box_param_t *box, OPJ_OFF_T offset)
{
  return fetch_1byte( box->fd, get_DBoxoff( box)+offset);
}

Byte2_t fetch_DBox2bytebigendian( box_param_t *box, OPJ_OFF_T offset)
{
  return fetch_2bytebigendian( box->fd, get_DBoxoff( box)+offset);
}

Byte4_t fetch_DBox4bytebigendian( box_param_t *box, OPJ_OFF_T offset)
{
  return fetch_4bytebigendian( box->fd, get_DBoxoff( box)+offset);
}

Byte8_t fetch_DBox8bytebigendian( box_param_t *box, OPJ_OFF_T offset)
{
  return fetch_8bytebigendian( box->fd, get_DBoxoff( box)+offset);
}

box_param_t * search_box( const char type[], boxlist_param_t *boxlist)
{
  box_param_t *foundbox;

  foundbox = boxlist->first;
  
  while( foundbox != NULL){
    
    if( strncmp( type, foundbox->type, 4) == 0)
      return foundbox;
      
    foundbox = foundbox->next;
  }
  fprintf( FCGI_stderr, "Error: Box %s not found\n", type);

  return NULL;
}

void print_box( box_param_t *box)
{
  fprintf( logstream, "box info:\n"
	   "\t type: %.4s\n"
	   "\t offset: %" PRId64 " %#" PRIx64 "\n" 
	   "\t header length: %d\n"
     "\t length: %" PRId64 " %#" PRIx64 "\n", box->type, box->offset, 
     box->offset, box->headlen, box->length, box->length);
}

void print_allbox( boxlist_param_t *boxlist)
{
  box_param_t *ptr;

  if( !boxlist)
    return;

  ptr = boxlist->first;
  if( !ptr)
    fprintf( logstream, "no box\n");

  fprintf( logstream, "all box info: \n");
  while( ptr != NULL){
    print_box( ptr);
    ptr=ptr->next;
  }
}

void delete_box_in_list( box_param_t **box, boxlist_param_t *boxlist)
{
  box_param_t *ptr;

  if( *box == boxlist->first)
    boxlist->first = (*box)->next;
  else{
    ptr = boxlist->first;
    while( ptr->next != *box){
      ptr=ptr->next;
    }
    ptr->next = (*box)->next;
    
    if( *box == boxlist->last)
      boxlist->last = ptr;
  }
  free( *box);
}

void delete_box_in_list_by_type( const char type[], boxlist_param_t *boxlist)
{
  box_param_t *box;

  box = search_box( type, boxlist);
  delete_box_in_list( &box, boxlist);
}

void delete_boxlist( boxlist_param_t **boxlist)
{
  box_param_t *boxPtr, *boxNext;

  if(!(*boxlist))
    return;
  
  boxPtr = (*boxlist)->first;
  while( boxPtr != NULL){
    boxNext=boxPtr->next;
    free( boxPtr);
    boxPtr=boxNext;
  }
  free( *boxlist);
}

void insert_box_into_list( box_param_t *box, boxlist_param_t *boxlist)
{
  if( boxlist->first)
    boxlist->last->next = box;
  else
    boxlist->first = box;
  boxlist->last = box;
}
