/*******************************************************************
                           DF - Decode Frame
 *******************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "mpeg_io.h"
#include "sequence_header.h"
#include "picture_header.h"
#include "timecode.h"
#include "gop_list.h"
#include "gop.h"
#include "frame.h"
#include "picture.h"
#include "mc.h"

void df(char *path, int frame);
void show_mpeg_video_info(SEQUENCE_HEADER *in, FILE *out);
void show_time(time_t t, FILE *out);

int main(int argc, char **argv)
{
	time_t t;

	if(argc < 3){
		fprintf(stderr, "Usage:\n");
		fprintf(stderr, "%s mpeg2video frame", argv[0]);
		exit(EXIT_FAILURE);
	}

	t = time(NULL);
	
	df(argv[1], atoi(argv[2]));

	fprintf(stdout, "elapsed time: ");
	show_time(time(NULL) - t, stdout);
	fprintf(stdout, "\n");

	return EXIT_SUCCESS;
}

void df(char *path, int frame)
{
    int frfn,brfn;
	MPEG_IO p;
	SEQUENCE_HEADER seq;
    BGR_CONVERSION_PARAMETER bgr_prm;
	PICTURE_HEADER pic;
	READ_PICTURE_HEADER_OPTION pic_opt;
	GOP g;
	GOP_TC gt;
	GOP_LIST gl;
	FIND_GOP fg;
	unsigned int code;
	int fc;
	int in_gop;
	MC_BUFFER buf;
	DECODE_PICTURE_PARAMETER dpp;

	if(!open_mpeg_file(path, &p)){
		fprintf(stderr, "%s - can't open\n", path);
		exit(EXIT_FAILURE);
	}

	if(!next_start_code(&p)){
		fprintf(stderr, "%s - is not MPEG file\n", path);
		exit(EXIT_FAILURE);
	}

	code = get_bits(&p, 32);
	if(code != 0x1B3){
		fprintf(stderr, "%s - is not MPEG Video file\n", path);
		fprintf(stderr, "[DEBUG] code=%d\n", code);
		exit(EXIT_FAILURE);
	}

	if(!read_sequence_header(&p, &seq)){
		fprintf(stderr, "%s - has invalid sequence header\n", path);
		exit(EXIT_FAILURE);
	}

    sequence_header_to_bgr_conversion_parameter(&seq, &bgr_prm, 1);
	sequence_header_to_read_picture_header_option(&seq, &pic_opt);
	sequence_header_to_read_slice_header_option(&seq, &(dpp.slice_option));
	sequence_header_to_read_macroblock_option(&seq, &(dpp.macroblock_option));
	sequence_header_to_read_block_option(&seq, &(dpp.block_option));
	sequence_header_to_mc_parameter(&seq, &(dpp.mc_parameter));
	buf.forward = NULL;
	buf.backward = NULL;
	buf.current = NULL;

	show_mpeg_video_info(&seq, stdout);

	gt.p = &p;
	gt.fps = &(seq.fps);
	gt.pic_opt = &pic_opt;

	if(read_gop(&gt, &g)){
		fg.arg1 = &gt;
		fg.func = gop_tc_find_gop;
	}else{
		mpeg_io_seek(&p, 0, SEEK_SET);
		create_gop_list(&p, &gl, &pic_opt);
		fg.arg1 = &gl;
		fg.func = gop_list_find_gop;
	}

	g = fg.func(fg.arg1, frame);
	if(g.frame < 0){
		fprintf(stderr, "frame: %d - out of range\n", frame);
		exit(EXIT_FAILURE);
	}

	mpeg_io_seek(&p, g.offset, SEEK_SET);

	in_gop = 0;
	fc = 0;
    frfn = 0;
    brfn = 0;
	while(next_start_code(&p)){
		code = get_bits(&p, 32);
		if(code == 0x1B3){
			read_sequence_header(&p, &seq);
            sequence_header_to_bgr_conversion_parameter(&seq, &bgr_prm, 1);
			sequence_header_to_read_picture_header_option(&seq, &pic_opt);
			sequence_header_to_read_slice_header_option(&seq, &(dpp.slice_option));
			sequence_header_to_read_macroblock_option(&seq, &(dpp.macroblock_option));
			sequence_header_to_read_block_option(&seq, &(dpp.block_option));
			sequence_header_to_mc_parameter(&seq, &(dpp.mc_parameter));
		}else if(code == 0x1B8){
			in_gop += 1;
			g.frame += fc;
			fc = 0;
		}else if(in_gop && code == 0x100){
			read_picture_header(&p, &pic, &pic_opt);
            picture_header_to_bgr_conversion_parameter(&pic, &bgr_prm);
			picture_header_to_read_macroblock_option(&pic, &(dpp.macroblock_option));
			picture_header_to_read_block_option(&pic, &(dpp.block_option));
			picture_header_to_mc_parameter(&pic, &(dpp.mc_parameter));

			fc += 1;
			if( in_gop == 1 && pic.picture_coding_type == 1 ){
				g.frame -= pic.temporal_reference;
			}

			buf.current = new_frame(seq.h_size, seq.v_size);
			if(pic.picture_coding_type != 3){
				if(buf.forward){
                    fprintf(stdout, "%d\n", frfn);
					save_frame_bmp(buf.forward, frfn, &bgr_prm);
                    delete_frame(buf.forward);
                    frfn = brfn;
					buf.forward = NULL;
				}
				buf.forward = buf.backward;
				buf.backward = buf.current;
                frfn = brfn;
                brfn = g.frame + pic.temporal_reference;
			}

			if( (pic.picture_coding_type == 3) && ( (buf.forward == NULL) || (buf.backward == NULL) ) ){
                delete_frame(buf.current);
				continue;
			}else if( (pic.picture_coding_type == 2) && (buf.forward == NULL) ){
                delete_frame(buf.current);
				continue;
			}

			decode_picture(&p, &buf, &dpp);

			if(pic.picture_coding_type == 3){
                fprintf(stdout, "%d\n", g.frame + pic.temporal_reference);
				save_frame_bmp(buf.current, g.frame + pic.temporal_reference, &bgr_prm);
                delete_frame(buf.current);
				buf.current = NULL;
            }
#if 0
			if(g.frame + pic.temporal_reference == frame){
				break;
			}
#endif
		}
	}

    if(buf.forward){
        fprintf(stdout, "%d\n", frfn);
        save_frame_bmp(buf.forward, frfn, &bgr_prm);
        delete_frame(buf.forward);
        buf.forward = NULL;
    }

    if(buf.backward){
        fprintf(stdout, "%d\n", brfn);
        save_frame_bmp(buf.backward, brfn, &bgr_prm);
        delete_frame(buf.backward);
        buf.backward = NULL;
    }
	close_mpeg_file(&p);
}

void show_mpeg_video_info(SEQUENCE_HEADER *in, FILE *out)
{
	static const char yuv[4][16] = {
		"invalid",
		"4:2:0",
		"4:2:2",
		"4:4:4",
	};
	
	fprintf(out, "%dx%d, ", in->h_size, in->v_size);
	fprintf(out, "%d.%d fps, ", in->fps.frame / in->fps.scale, ((in->fps.frame % in->fps.scale) * 1000 / in->fps.scale));
	fprintf(out, "%d bps, ", in->bit_rate * 400);
	if(in->has_sequence_extension){
		if(in->se.progressive){
			fprintf(out, "Progressive");
		}else{
			fprintf(out, "Interlace");
		}
		fprintf(out, " MPEG-2 Video");
		fprintf(out, " YUV=%s\n", yuv[in->se.chroma_format]);
	}else{
		fprintf(out, "MPEG-1 Video\n");
	}
}

void show_time(time_t t, FILE *out)
{
	int dd,hh,mm,ss;

	ss = t % 60;
	mm = t / 60 % 60;
	hh = t / 60 / 60 % 24;
	dd = t / 60 / 60 / 24;

	if(dd){
		fprintf(out, "%02d:%02d:%02d:%02d", dd,hh,mm,ss);
	}else{
		fprintf(out, "%02d:%02d:%02d", hh,mm,ss);
	}
}

