/*******************************************************************
                            GOP module
 *******************************************************************/

#include <io.h>
#include "sequence_header.h"

#define GOP_C
#include "gop.h"

int next_gop(VIDEO_STREAM *p);
int last_gop(VIDEO_STREAM *p);

READ_GOP_PARAMETER *new_read_gop_parameter(VIDEO_STREAM *stream, SEQUENCE_HEADER *seq, READ_PICTURE_HEADER_OPTION *pic_opt);
void delete_read_gop_parameter(void *p);

__int64 read_gop(READ_GOP_PARAMETER *in, GOP *out);
int skip_2nd_field(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *opt);
int count_next_b_pictures(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *opt);

__int64 count_frame(READ_GOP_PARAMETER *p);

GOP find_gop_with_timecode(void *p, __int64 frame);


int next_gop(VIDEO_STREAM *p)
{
	unsigned int code;

	while(next_start_code(p)){
		code = get_bits(p, 32);
		if(code == 0x1B8){
			return 1;
		}
	}

	return 0;
}

int last_gop(VIDEO_STREAM *p)
{
	int i;
	__int64 n;

	static const unsigned char s[4] = {
		0, 0, 1, 0xB8,
	};
	
	static const int jump_table[256] = {
		1, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5,

		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
	};

	if(p->file_length < VIDEO_STREAM_BUFFER_SIZE){
		_lseeki64(p->fd, 0, SEEK_SET);
	}else{
		_lseeki64(p->fd, - VIDEO_STREAM_BUFFER_SIZE, SEEK_END);
	}

	p->buffer_size = _read(p->fd, p->buffer, VIDEO_STREAM_BUFFER_SIZE);
	p->file_position = _telli64(p->fd) + VIDEO_STREAM_BUFFER_SPARE;
	p->current = p->buffer + p->buffer_size - 1;
	
	i = 0;
	while(i < 4){
		if(p->current - i < p->buffer){
			memcpy(p->buffer + VIDEO_STREAM_BUFFER_SIZE, p->buffer, VIDEO_STREAM_BUFFER_SPARE);
			n = _lseeki64(p->fd, - 2 * VIDEO_STREAM_BUFFER_SIZE, SEEK_CUR);
			if(n == -1){
				_lseeki64(p->fd, 0, SEEK_SET);
				p->buffer_size = _read(p->fd, p->buffer, VIDEO_STREAM_BUFFER_SIZE);
				p->current = p->buffer+VIDEO_STREAM_BUFFER_SIZE-1;
			}else{
				p->buffer_size = _read(p->fd, p->buffer, VIDEO_STREAM_BUFFER_SIZE);
				p->current = p->buffer+VIDEO_STREAM_BUFFER_SIZE+3;
			}
			i = 0;
			p->file_position = _telli64(p->fd) + VIDEO_STREAM_BUFFER_SPARE;
		}
		if(*(p->current-i) == s[3-i]){
			i++;
		}else{
			i = 0;
			p->current -= jump_table[*(p->current-4)];
		}
	}
	
	p->current -= 3;

	erase_bits(p, p->bits_rest);
	
	return 1;
}	

READ_GOP_PARAMETER *new_read_gop_parameter(VIDEO_STREAM *stream, SEQUENCE_HEADER *seq, READ_PICTURE_HEADER_OPTION *pic_opt)
{
	READ_GOP_PARAMETER *r;

	static const int rate[16] = {
		 0, 23976, 24, 25, 2997, 30, 50, 5994,
		60,     0,  0,  0,    0,  0,  0,    0,
	};

	static const int scale[16] = {
		 1,  1000,  1,  1,  100,  1,  1,  100,
		 1,     1,  1,  1,    1,  1,  1,    1,
	};

	r = (READ_GOP_PARAMETER *)calloc(1, sizeof(READ_GOP_PARAMETER));
	if(r == NULL){
		return NULL;
	}

	r->p = stream;

	r->rate = rate[seq->picture_rate];
	r->scale = scale[seq->picture_rate];

	if(seq->has_sequence_extension){
		r->rate *= (seq->se.frame_rate_ext_n + 1);
		r->scale *= (seq->se.frame_rate_ext_d + 1);
	}

	r->start_frame = 0;
	
	r->pic_opt = pic_opt;

	return r;
}

void delete_read_gop_parameter(void *p)
{
	if(p != NULL){
		free(p);
	}
}	

__int64 read_gop(READ_GOP_PARAMETER *in, GOP *out)
{
	unsigned int code;
	
	TIMECODE t;
	int frame_rate;

	int closed_gop;
	int broken_link;
	
	PICTURE_HEADER pic;

	int gop;
	int frame;

	memset(out, 0, sizeof(GOP));
	
	if(! next_gop(in->p)){
		return 0;
	}

	out->offset = video_stream_tell(in->p) - 4;

	frame_rate = (in->rate + in->scale - 1 ) / in->scale;
	read_timecode(in->p, &t);
	out->start_frame = timecode2frame(&t, frame_rate) - in->start_frame;

	closed_gop = get_bits(in->p, 1);
	broken_link = get_bits(in->p, 1);


	frame = 0;
	gop = 0;

	/* 1st step, find I picture */
	while(next_start_code(in->p)){
		code = get_bits(in->p, 32);
		if(code == 0x100){ /* picture */
			read_picture_header(in->p, &(pic), in->pic_opt);
			
			if(pic.has_picture_coding_extension && pic.pc.picture_structure != 3){ /* field picture */
				if(! skip_2nd_field(in->p, in->pic_opt)){
					/* 2nd field picture is not found */
					return 0;
				}
			}
			
			if(pic.picture_coding_type != 1){
				/* GOP start picture is not I picture */
				return 0;
			}
			frame = 0;
			break; /* goto 2nd step */
		}else if(code == 0x1B8){ /* gop */
			/* GOP has no picture */
			return 0;
		}
	}

	/* 2nd step, calc I picture temporal reference */
	while(next_start_code(in->p)){
		code = get_bits(in->p, 32);
		if(code == 0x100){
			read_picture_header(in->p, &(pic), in->pic_opt);

			if(pic.has_picture_coding_extension && pic.pc.picture_structure != 3){ /* field picture */
				if(! skip_2nd_field(in->p, in->pic_opt)){
					return 0;
				}
			}
			
			if( (pic.picture_coding_type == 3) && !broken_link ){
				frame += 1;
			}else if(pic.picture_coding_type == 2){
				broken_link = 0;
				out->start_frame += frame;
				frame = 1;
				break; /* goto 3rd step */
			}else if(pic.picture_coding_type == 1){
				/* gop has multipule I pictures */
				out->start_frame += frame;
				out->frame_count = 1 + count_next_b_pictures(in->p, in->pic_opt);
				return 0;
			}
		}else if(code == 0x1B8){
			read_timecode(in->p, &t);
			if(frame != timecode2frame(&t, frame_rate) - in->start_frame - out->start_frame - 1){
				/* timecode is not sequential */
				out->start_frame += frame;
				out->frame_count = 1 + count_next_b_pictures(in->p, in->pic_opt);
				return 0;
			}
			out->start_frame += frame;
			frame = 0;
			closed_gop = get_bits(in->p, 1);
			broken_link = get_bits(in->p, 1);
			gop = 1;
			break; /* goto 3rd step */
		}
	}

	/* 3rd step, count frame in gop */
	while(next_start_code(in->p)){
		code = get_bits(in->p, 32);
		if(code == 0x100){
			read_picture_header(in->p, &(pic), in->pic_opt);
			
			if(pic.has_picture_coding_extension && pic.pc.picture_structure != 3){ /* field picture */
				if(! skip_2nd_field(in->p, in->pic_opt)){
					return 0;
				}
			}
			
			if(pic.picture_coding_type == 1){
				if(gop){
					out->frame_count = frame + 1 + (broken_link ? 0 : count_next_b_pictures(in->p, in->pic_opt));
					return out->frame_count; 
				}
				/* gop has multiple I picture */
				out->frame_count = frame + 1 + count_next_b_pictures(in->p, in->pic_opt);
				return 0;
			}

			frame += 1;
			
		}else if(code == 0x1B8){
			read_timecode(in->p, &t);
			closed_gop = get_bits(in->p, 1);
			broken_link = get_bits(in->p, 1);
			if(frame == timecode2frame(&t, frame_rate) - in->start_frame - out->start_frame - 1){
				gop = 1;
			}else{
				gop = 0;
			}
		}
	}

	out->frame_count = frame + 1;
	return out->frame_count;
}

int skip_2nd_field(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *opt)
{
	int code;
	
	PICTURE_HEADER pic;

	while(next_start_code(in)){
		code = get_bits(in, 32);
		if(code == 0x100){
			read_picture_header(in, &pic, opt);
			if(pic.has_picture_coding_extension && (pic.pc.picture_structure != 3) ){
				return 1;
			}
			return 0;
		}
	}

	return 0;
}

int count_next_b_pictures(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *opt)
{
	int r;
	int code;

	PICTURE_HEADER pic;

	r = 0;
	while(next_start_code(in)){
		code = get_bits(in, 32);
		if(code == 0x100){
			read_picture_header(in, &pic, opt);
			
			if(pic.has_picture_coding_extension && (pic.pc.picture_structure != 3) ){
				skip_2nd_field(in, opt);
			}
			
			if(pic.picture_coding_type != 3){
				return r;
			}
			r += 1;
		}
	}

	return r;
}

__int64 count_frame(READ_GOP_PARAMETER *p)
{
	__int64 r;
	GOP g;

	video_stream_seek(p->p, 0, SEEK_SET);

	p->start_frame = 0;
	
	if(! read_gop(p, &g)){
		return -1;
	}

	p->start_frame = g.start_frame;
	
	if(video_stream_tell(p->p) == p->p->file_length){
		r = g.frame_count;
	}else{
		last_gop(p->p);
		read_gop(p, &g);
		r = g.start_frame + g.frame_count;
	}
	
	return r;
}	

GOP find_gop_with_timecode(void *p, __int64 frame)
{
	__int64 first, last, i, n;

	GOP r;

	READ_GOP_PARAMETER *gp;

	gp = (READ_GOP_PARAMETER *)p;

	first = 0;
	last = gp->p->file_length;

	while(1){
		i = first + (last - first)/2;
		
		if(i == first){
			break;
		}
		
		video_stream_seek(gp->p, i, SEEK_SET);

		n = read_gop(gp, &r);

		if( n != r.frame_count ){
			break;
		}
		
		if(n == 0){
			last = i;
            continue;
		}
		
		if(r.start_frame > frame){
			last = i;
		}else if(r.start_frame + r.frame_count > frame){
			return r;
		}else{
			first = r.offset;
		}
	}
		
	r.offset = 0;
	r.start_frame = 0;
	r.frame_count = 0;
	
	return r;		
}

