/*
 * $Id$
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Peter Schlaile <peter [at] schlaile [dot] de> 2011
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL LICENSE BLOCK *****
*/

#include "IMB_indexer.h"
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include <time.h>

typedef struct indexer_dv_bitstream {
	unsigned char* buffer;
	int bit_pos;
} indexer_dv_bitstream;

static indexer_dv_bitstream bitstream_new(unsigned char* buffer_) 
{
	indexer_dv_bitstream rv;

	rv.buffer = buffer_;
	rv.bit_pos = 0;

	return rv;
}

static unsigned long bitstream_get_bits(indexer_dv_bitstream * This, int num) 
{
	int byte_pos = This->bit_pos >> 3;
	unsigned long i = 
		This->buffer[byte_pos] | (This->buffer[byte_pos + 1] << 8) |
		(This->buffer[byte_pos + 2] << 16) | 
		(This->buffer[byte_pos + 3] << 24);
	int rval = (i >> (This->bit_pos & 0x7)) & ((1 << num) - 1);
	This->bit_pos += num;
	return rval;
}

static int parse_num(indexer_dv_bitstream * b, int numbits) {
	return bitstream_get_bits(b, numbits);
}

static int parse_bcd(indexer_dv_bitstream * b, int n) 
{
	char s[256];
	char * p = s + (n+3)/4;

	*p-- = 0;

	while (n > 4) {
		char a;
		int v = bitstream_get_bits(b, 4);

		n -= 4;
		a = '0' + v;

		if (a > '9') {
			bitstream_get_bits(b, n);
			return -1;
		}

		*p-- = a;
	}
	if (n) {
		char a;
		int v = bitstream_get_bits(b, n);
		a = '0' + v;
		if (a > '9') {
			return -1;
		}
		*p-- = a;
	}

	return atol(s);
}

typedef struct indexer_dv_context
{
	int rec_curr_frame;
	int rec_curr_second;
	int rec_curr_minute;
	int rec_curr_hour;

	int rec_curr_day;
	int rec_curr_month;
	int rec_curr_year;

	char got_record_date;
	char got_record_time;

	time_t ref_time_read;
	time_t ref_time_read_new;
	int curr_frame;

	time_t gap_start;
	int gap_frame;

	int frameno_offset;

	anim_index_entry backbuffer[31];
	int fsize;

	anim_index_builder * idx;
} indexer_dv_context;

static void parse_packet(indexer_dv_context * This, unsigned char * p)
{
	indexer_dv_bitstream b;
	int type = p[0];

	b = bitstream_new(p + 1);

	switch (type) {
		case 0x62: // Record date
			parse_num(&b, 8);
			This->rec_curr_day = parse_bcd(&b, 6);
			parse_num(&b, 2);
			This->rec_curr_month = parse_bcd(&b, 5);
			parse_num(&b, 3);
			This->rec_curr_year = parse_bcd(&b, 8);
			if (This->rec_curr_year < 25) {
				This->rec_curr_year += 2000;
			} else {
				This->rec_curr_year += 1900;
			}
			This->got_record_date = 1;
			break;
		case 0x63: // Record time
			This->rec_curr_frame = parse_bcd(&b, 6);
			parse_num(&b, 2);
			This->rec_curr_second = parse_bcd(&b, 7);
			parse_num(&b, 1);
			This->rec_curr_minute = parse_bcd(&b, 7);
			parse_num(&b, 1);
			This->rec_curr_hour = parse_bcd(&b, 6);
			This->got_record_time = 1;
			break;
	}
}

static void parse_header_block(indexer_dv_context * This, unsigned char* target)
{
	int i;
	for (i = 3; i < 80; i += 5) {
		if (target[i] != 0xff) {
			parse_packet(This, target + i);
		}
	}
}

static void parse_subcode_blocks(
        indexer_dv_context * This, unsigned char* target)
{
	int i,j;

	for (j = 0; j < 2; j++) {
		for (i = 3; i < 80; i += 5) {
			if (target[i] != 0xff) {
				parse_packet(This, target + i);
			}
		}
	}
}

static void parse_vaux_blocks(
        indexer_dv_context * This, unsigned char* target)
{
	int i,j;

	for (j = 0; j < 3; j++) {
		for (i = 3; i < 80; i += 5) {
			if (target[i] != 0xff) {
				parse_packet(This, target + i);
			}
		}
		target += 80;
	}
}

static void parse_audio_headers(
        indexer_dv_context * This, unsigned char* target)
{
	int i;

	for(i = 0; i < 9; i++) {
		if (target[3] != 0xff) {
			parse_packet(This, target + 3);
		}
		target += 16 * 80;
	}
}

static void parse_frame(indexer_dv_context * This, 
                        unsigned char * framebuffer, int isPAL)
{
	int numDIFseq = isPAL ? 12 : 10;
	unsigned char* target = framebuffer;
	int ds;

	for (ds = 0; ds < numDIFseq; ds++) {
		parse_header_block(This, target);
		target +=   1 * 80;
		parse_subcode_blocks(This, target);
		target +=   2 * 80;
		parse_vaux_blocks(This, target);
		target +=   3 * 80;
		parse_audio_headers(This, target);
		target += 144 * 80;
	}
}

static void inc_frame(int * frame, time_t * t, int isPAL)
{
	if ((isPAL && *frame >= 25) || (!isPAL && *frame >= 30)) {
		fprintf(stderr, "Ouchie: inc_frame: invalid_frameno: %d\n",
		        *frame);
	}
	(*frame)++;
	if (isPAL && *frame >= 25) {
		(*t)++;
		*frame = 0;
	} else if (!isPAL && *frame >= 30) {
		(*t)++;
		*frame = 0;
	}
}

static void write_index(indexer_dv_context * This, anim_index_entry * entry)
{
	IMB_index_builder_add_entry(
		This->idx, entry->frameno + This->frameno_offset, 
		entry->seek_pos, entry->seek_pos_dts, entry->pts);
}

static void fill_gap(indexer_dv_context * This, int isPAL)
{
	int i;

	for (i = 0; i < This->fsize; i++) {
		if (This->gap_start == This->ref_time_read &&
		        This->gap_frame == This->curr_frame) {
			fprintf(stderr,
			        "indexer_dv::fill_gap: "
			        "can't seek backwards !\n");
			break;
		}
		inc_frame(&This->gap_frame, &This->gap_start, isPAL);
	}

	while (This->gap_start != This->ref_time_read ||
	       This->gap_frame != This->curr_frame) {
		inc_frame(&This->gap_frame, &This->gap_start, isPAL);
		This->frameno_offset++;
	}

	for (i = 0; i < This->fsize; i++) {
		write_index(This, This->backbuffer + i);
	}
	This->fsize = 0;
}

static void proc_frame(indexer_dv_context * This,
                       unsigned char* UNUSED(framebuffer), int isPAL)
{
	struct tm recDate;
	time_t t;

	if (!This->got_record_date || !This->got_record_time) {
		return;
	}

	recDate.tm_sec = This->rec_curr_second;
	recDate.tm_min = This->rec_curr_minute;
	recDate.tm_hour = This->rec_curr_hour;
	recDate.tm_mday = This->rec_curr_day;
	recDate.tm_mon = This->rec_curr_month - 1;
	recDate.tm_year = This->rec_curr_year - 1900;
	recDate.tm_wday = -1;
	recDate.tm_yday = -1;
	recDate.tm_isdst = -1;
	
	t = mktime(&recDate);
	if (t == -1) {
		return;
	}

	This->ref_time_read_new = t;

	if (This->ref_time_read < 0) {
		This->ref_time_read = This->ref_time_read_new;
		This->curr_frame = 0;
	} else {
		if (This->ref_time_read_new - This->ref_time_read == 1) {
			This->curr_frame = 0;
			This->ref_time_read = This->ref_time_read_new;
			if (This->gap_frame >= 0) {
				fill_gap(This, isPAL);
				This->gap_frame = -1;
			}
		} else if (This->ref_time_read_new  == This->ref_time_read) {
			// do nothing
		} else {
			This->gap_start = This->ref_time_read;
			This->gap_frame = This->curr_frame;
			This->ref_time_read = This->ref_time_read_new;
			This->curr_frame = -1;
		}
	}
}

static void indexer_dv_proc_frame(anim_index_builder * idx, 
                                  unsigned char * buffer,
                                  int UNUSED(data_size),
                                  struct anim_index_entry * entry)
{
	int isPAL;
	
	indexer_dv_context * This = (indexer_dv_context *) idx->private_data;

	isPAL = (buffer[3] & 0x80);

	This->got_record_date = FALSE;
	This->got_record_time = FALSE;

	parse_frame(This, buffer, isPAL);
	proc_frame(This, buffer, isPAL);

	if (This->curr_frame >= 0) {
		write_index(This, entry);
		inc_frame(&This->curr_frame, &This->ref_time_read, isPAL);
	} else {
		This->backbuffer[This->fsize++] = *entry;
		if (This->fsize >= 31) {
			int i;

			fprintf(stderr, "indexer_dv::indexer_dv_proc_frame: "
			        "backbuffer overrun, emergency flush");

			for (i = 0; i < This->fsize; i++) {
				write_index(This, This->backbuffer+i);
			}
			This->fsize = 0;
		}
	}
}

static void indexer_dv_delete(anim_index_builder * idx)
{
	int i = 0;
	indexer_dv_context * This = (indexer_dv_context *) idx->private_data;

	for (i = 0; i < This->fsize; i++) {
		write_index(This, This->backbuffer+i);
	}

	MEM_freeN(This);
}

void IMB_indexer_dv_new(anim_index_builder * idx)
{
	indexer_dv_context * rv = MEM_callocN(
	            sizeof(indexer_dv_context), "index_dv builder context");

	rv->ref_time_read = -1;
	rv->curr_frame = -1;
	rv->gap_frame = -1;
	rv->idx = idx;
	
	idx->private_data = rv;
	idx->proc_frame = indexer_dv_proc_frame;
	idx->delete_priv_data = indexer_dv_delete;
}
