/*******************************************************************
                         GOP list module
 *******************************************************************/

#include "endian.h"

#define GOP_LIST_C
#include "gop_list.h"

typedef struct {
    int     index;
	__int64 start;
	__int64 count;

	__int64 offset;

	void   *prev;
	void   *next;
} GOP_ENTRY;


GOP_LIST *new_gop_list(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *opt, int field_order);
void delete_gop_list(void *p);
GOP find_gop_with_gop_list(void *p, __int64 frame);
int store_gop_list(GOP_LIST *in, char *filepath);
GOP_LIST *load_gop_list(char *filepath);

static void release_gop_entries(GOP_ENTRY *last);


GOP_LIST *new_gop_list(VIDEO_STREAM *in, READ_PICTURE_HEADER_OPTION *pic_opt, int field_order)
{
	GOP_LIST *r;

	int code;
	
	int broken_link;
	int temporal_count;
	int intra_flag;

	int i;

	__int64 offset;
	__int64 frame;
	
	PICTURE_HEADER pic;
	
	GOP_ENTRY *p;
	GOP_ENTRY *c;

	/* stream rewind */
	video_stream_seek(in, 0, SEEK_SET);

	p = NULL;
	c = NULL;
	frame = 0;

	intra_flag = 1;
	broken_link = 1;
	/* 1st step - find initial I picture */
	while(vs_next_start_code(in)){
		code = vs_read_bits(in, 32);
		if(code == 0x100){
			
			offset = video_stream_tell(in);
			vs_erase_bits(in, 32);

			if(! read_picture_header(in, &pic, pic_opt)){
				return NULL;
			}

			temporal_count = 1;
			if(pic.has_picture_coding_extension){
				if(pic.pc.picture_structure != 3){
					if(! skip_2nd_field(in, pic_opt)){
						return NULL;
					}
				}

				if(pic.pc.repeat_first_field && (pic.pc.top_field_first == field_order)){
					temporal_count += 1;
				}
			}

			if(pic.picture_coding_type == 1){
				c = (GOP_ENTRY *)malloc(sizeof(GOP_ENTRY));
				c->index = 0;
				c->start = 0;
				c->offset = offset;
				c->prev = NULL;

				frame = temporal_count;

				break; /* goto 2nd step */
			}
			
		}else{
			vs_erase_bits(in, 32);
		}
	}

	/* 2nd step - check all rest pictures */
	while(vs_next_start_code(in)){
		code = vs_read_bits(in, 32);
		if(code == 0x100){
			offset = video_stream_tell(in);
			vs_erase_bits(in, 32);

			if(! read_picture_header(in, &pic, pic_opt)){
				release_gop_entries(c);
				return NULL;
			}

			temporal_count = 1;
			if(pic.has_picture_coding_extension){
				if(pic.pc.picture_structure != 3){
					if(! skip_2nd_field(in, pic_opt)){
						release_gop_entries(c);
						return NULL;
					}
				}

				if(pic.pc.repeat_first_field && (pic.pc.top_field_first == field_order) ){
					temporal_count += 1;
				}
			}

			switch(pic.picture_coding_type){
			case 1:
				if(intra_flag && p){
					p->count = c->start - p->start;
				}
				
				p = c;
				c = (GOP_ENTRY *)malloc(sizeof(GOP_ENTRY));
				if(c == NULL){
					release_gop_entries(p);
					return NULL;
				}
				p->next = c;
				c->prev = p;
				
				c->index = p->index + 1;
				c->start = p->start + frame;
				c->offset = offset;
					
				frame = temporal_count;
				intra_flag = 1;
				break;
			case 2:
				if(intra_flag && p){
					p->count = c->start - p->start;
				}
				frame += temporal_count;
				broken_link = 0;
				intra_flag = 0;
				break;
			case 3:
				if(intra_flag){
					if(! broken_link){
						c->start += temporal_count;
					}
				}else{
					frame += temporal_count;
				}
				break;
			default:
				release_gop_entries(c);
				return NULL;
			}
		}else if(code == 0x1b8){
			vs_erase_bits(in, 32);
			vs_erase_bits(in, 26);
			broken_link = vs_get_bits(in, 1);
		}else{
			vs_erase_bits(in, 32);
		}
	}

	r = (GOP_LIST *)calloc(1, sizeof(GOP_LIST));

	r->stream_length = in->file_length;
	r->num_of_frame = c->start + frame;
	r->num_of_gop = c->index + 1;
	r->gops = (GOP *)malloc((r->num_of_gop)*sizeof(GOP));

	for(i=r->num_of_gop-1;c;i--){
		r->gops[i].offset = c->offset;
		r->gops[i].start_frame = c->start;
		r->gops[i].frame_count = c->count;
		p = (GOP_ENTRY *)c->prev;
		free(c);
		c = p;
	}
	
	return r;
}

void delete_gop_list(void *p)
{
	GOP_LIST *w;

	w = (GOP_LIST *)p;
	
	if(w != NULL){
		if(w->gops != NULL){
			free(w->gops);
		}
		free(w);
	}
}

GOP find_gop_with_gop_list(void *p, __int64 frame)
{
	int first, last;
	int i;

	GOP_LIST *gl;
	
	static const GOP r = {
		0, 0, 0,
	};

	gl = (GOP_LIST *)p;
	
	if(frame < gl->gops[0].start_frame || frame > gl->num_of_frame-1){
		return r;
	}

	if(gl->gops[gl->num_of_gop-1].start_frame <= frame){
		return gl->gops[gl->num_of_gop-1];
	}

	first = 0;
	last = gl->num_of_gop - 1;

	i = last/2;
	
	while(1){
		if(gl->gops[i].start_frame <= frame && gl->gops[i+1].start_frame > frame){
			return gl->gops[i];
		}else if(gl->gops[i].start_frame < frame){
			first = i;
		}else{
			last = i;
		}
		i = first + (last - first)/2;
		if(first == last){
			return r;
		}
	}
}

int store_gop_list(GOP_LIST *in, char *filepath)
{
	FILE *out;

	int i;

	out = fopen(filepath, "wb");
	if(out == NULL){
		return 0;
	}

	if(! write_le_int64(in->stream_length, out) ){
		fclose(out);
		return 0;
	}
	
	if(! write_le_int64(in->num_of_frame, out) ){
		fclose(out);
		return 0;
	}

	if(! write_le_int32(in->num_of_gop, out) ){
		fclose(out);
		return 0;
	}

	for(i=0;i<in->num_of_gop;i++){
		if(! write_le_int64(in->gops[i].offset, out)){
			fclose(out);
			return 0;
		}
		if(! write_le_int64(in->gops[i].start_frame, out)){
			fclose(out);
			return 0;
		}
		if(! write_le_int64(in->gops[i].frame_count, out)){
			fclose(out);
			return 0;
		}
	}

	fclose(out);

	return 1;
}

GOP_LIST *load_gop_list(char *filepath)
{
	GOP_LIST *r;
	FILE *in;
	
	int i;

	in = fopen(filepath, "rb");
	if(in == NULL){
		return NULL;
	}
	
	r = (GOP_LIST *)calloc(1, sizeof(GOP_LIST));
	if(r == NULL){
		fclose(in);
		return NULL;
	}

	if(! read_le_int64(in, &(r->stream_length)) ){
		fclose(in);
		free(r);
		return NULL;
	}

	if(! read_le_int64(in, &(r->num_of_frame)) ){
		fclose(in);
		free(r);
		return NULL;
	}

	if(! read_le_int32(in, &(r->num_of_gop)) ){
		fclose(in);
		free(r);
		return NULL;
	}

	if(r->num_of_gop < 1){
		fclose(in);
		free(r);
		return NULL;
	}

	r->gops = (GOP *)calloc(r->num_of_gop, sizeof(GOP));
	if(r->gops == NULL){
		fclose(in);
		free(r);
		return NULL;
	}

	for(i=0;i<r->num_of_gop;i++){
		if(! read_le_int64(in, &(r->gops[i].offset)) ){
			fclose(in);
			free(r->gops);
			free(r);
			return NULL;
		}
		
		if(! read_le_int64(in, &(r->gops[i].start_frame)) ){
			fclose(in);
			free(r->gops);
			free(r);
			return NULL;
		}
		
		if(! read_le_int64(in, &(r->gops[i].frame_count)) ){
			fclose(in);
			free(r->gops);
			free(r);
			return NULL;
		}
	}

	fclose(in);
	
	return r;
}

static void release_gop_entries(GOP_ENTRY *last)
{
	GOP_ENTRY *p;
	GOP_ENTRY *c;

	c = last;

	while(c != NULL){
		p = c->prev;
		free(c);
		c = p;
	}

}
	
