/*
 *  Copyright (C) 2010 Vikram Ambrose <noel.ambrose@gmail.com> 
 *
 *  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 3 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, see <http://www.gnu.org/licenses/>.
*/


/* Compile with:
 * gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 libxine`  gtkxine.c -o gtkxine */

#include <stdio.h>
#include <string.h>

#include <xine.h>
#include <xine/xineutils.h>

#include <gdk/gdkx.h>
#include <gtk/gtk.h>


/* Some default sizes for the initial window */
#define DEFAULT_WIDTH	320
#define DEFAULT_HEIGHT	240

/* Help message string */
#define HELP_MESSAGE	"Options:\n\
-ao <ao name>\tAudio output plugin name (default = alsa).\n\
-vo <vo name>\tVideo output plugin name (default = Xv).\n"
	
/* Xine Session data structure */
typedef struct{
	/* Basic xine stuff */
	xine_t              *handle;
	xine_stream_t       *stream;
	xine_video_port_t   *vo_port;
	xine_audio_port_t   *ao_port;
	xine_event_queue_t  *event_queue;

	/* Driver strings eg. "alsa", "Xv", etc.. */
	char *vo_driver; 
	char *ao_driver;

	/* Song/Movie path */
	char *mrl;
	
	/* XLib XID integer */
	Window window;
	/* Basic data struct required for use with XINE_VISUAL_TYPE_X11  */
	x11_visual_t       vis;
	
	/* Geomtry available to xine and requested */
	int video_canhave_width;
	int video_canhave_height;
	int video_wants_width;
	int video_wants_height;
}XINE_SESSION;

/* Shuts down libxine */
int quit_xine(XINE_SESSION *s){
	printf("quit xine\n");
	xine_close(s->stream);
	xine_event_dispose_queue(s->event_queue);
	xine_dispose(s->stream);
	if(s->ao_port) xine_close_audio_driver(s->handle, s->ao_port);  

	/* The video belongs to gtk, not you, silly xine */
	//xine_close_video_driver(s->handle, s->vo_port);  

	xine_exit(s->handle);
	return 0;
}

/* GtkWindow Destroy callback */
void quit_program(GtkWidget *blah, gpointer data){
	quit_xine(data);
	printf("quit gtk\n");
	gtk_main_quit();
}

/* Just a typedef to reduce typing when passing */
typedef void (*FRAME_OUTPUT_CALLBACK)(void *data, int video_width, int video_height,
			    double video_pixel_aspect, int *dest_x, int *dest_y,
			    int    *dest_width,        int *dest_height, 
			    double *dest_pixel_aspect, int *win_x, int *win_y);

/* Used to resize the video area when the user resizes the window */
static void on_size_allocate_tadeboro_hack(GtkWidget *widget, GtkAllocation *alloc,gpointer data){
	XINE_SESSION *s = data;
	s->video_canhave_width = alloc->width;
	s->video_canhave_height = alloc->height;

	/* http://tadeboro.blogspot.com/2009/05/wrapping-adn-resizing-gtklabel.html */
	/* This is a little bit of a hack. See link above for details.
	 * We are basically telling the parent window that we want 
	 * all the space its got for us, instead of how big we really are */
//	gtk_widget_set_size_request(widget,video_wants_width,video_wants_height );
        gtk_widget_set_size_request(widget,s->video_canhave_width-2,s->video_canhave_height-2 );
}

/* Standard frame_output_cb prototype as defined in xine.h */
static void frame_output_cb(void *data, int video_width, int video_height,
			    double video_pixel_aspect, int *dest_x, int *dest_y,
			    int    *dest_width,        int *dest_height, 
			    double *dest_pixel_aspect, int *win_x, int *win_y) {
	XINE_SESSION *s = data;
	*dest_x            = 0;
	*dest_y            = 0;
	*win_x             = 0;
	*win_y             = 0;
	s->video_wants_width = video_width;
	s->video_wants_height = video_height;

	/* Here we tell xine to draw the video to make best use of
	 * the space given to it, */
	*dest_width = s->video_canhave_width;
	*dest_height = s->video_canhave_height;

	/* ... but force xine to maintaine the video's original aspect ratio 
	 * and not that of the actual GtkDrawingArea */
	//*dest_pixel_aspect = (float)video_canhave_height / (float) video_canhave_width;
	*dest_pixel_aspect = video_pixel_aspect;
}

/* Standard event listener prototype as defined in xine.h */
static void event_listener(void *user_data, const xine_event_t *event) {
	switch(event->type) { 
	case XINE_EVENT_UI_PLAYBACK_FINISHED:
		printf("playback finished\nQuiting...");	
		quit_program(NULL,user_data);
		break;
	}
}
  
/* Creates a xine session */
void initialise_xine(XINE_SESSION *s){
	char configfile[128];
	printf("initialise_xine\n");
	s->handle = xine_new();
	sprintf(configfile, "%s/%s", xine_get_homedir(), ".xine/config");
	xine_config_load(s->handle, configfile);
	xine_init(s->handle);
	s->video_canhave_width=DEFAULT_WIDTH;
	s->video_canhave_height=DEFAULT_HEIGHT;
	s->video_wants_width=50;
	s->video_wants_height=40;
}

/* Attaches the GtkDrawingArea to the xine video output */
int connect_xine_to_gtk(XINE_SESSION *s, GtkDrawingArea *drawing_area,
					FRAME_OUTPUT_CALLBACK cb,
					void *cb_user_data){
	printf("connect_xine_to_gtk\n");

	s->vis.display = GDK_WINDOW_XDISPLAY(GTK_WIDGET(drawing_area)->window);
	s->vis.screen  = GDK_SCREEN_XNUMBER(gtk_widget_get_screen(GTK_WIDGET(drawing_area)));
	s->vis.d       = GDK_WINDOW_XID(GTK_WIDGET(drawing_area)->window);
	s->window      = GDK_WINDOW_XID(GTK_WIDGET(drawing_area)->window);
	s->vis.frame_output_cb   = cb;
	s->vis.user_data         = cb_user_data;

	/* The magic one liner  
	 * Hopefully some of the boiler plate above can be accpeted into 
	 * libxine one day as part of XINE_VISUAL_TYPE_GTK */
	s->vo_port = xine_open_video_driver(s->handle,
			s->vo_driver,
			XINE_VISUAL_TYPE_X11, 
			(void *) &s->vis);

	if(s->vo_port== NULL){
		printf("I'm unable to initialize '%s' video driver. Giving up.\n", s->vo_driver);
		return 1;
	}
	xine_port_send_gui_data (s->vo_port,XINE_GUI_SEND_DRAWABLE_CHANGED,( void *) s->window);
	xine_port_send_gui_data (s->vo_port,XINE_GUI_SEND_VIDEOWIN_VISIBLE,(void *)1);
	return 0;
}

/* Opens up an audio port */
int connect_xine_to_audio(XINE_SESSION *s){
	printf("connect_xine_to_audio\n");
	s->ao_port = xine_open_audio_driver(s->handle , s->ao_driver, NULL);
	return (s->ao_port) ? 0 : 1;
}

/* Opens up a file stream and sets up the event handler */
int connect_xine_to_file(XINE_SESSION *s){
	printf("connect_xine_to_file\n");
	s->stream = xine_stream_new(s->handle, s->ao_port, s->vo_port);
	s->event_queue = xine_event_new_queue(s->stream);
	xine_event_create_listener_thread(s->event_queue, event_listener, s);
	xine_open(s->stream,s->mrl);
	return 0;
}

/* Starts stream playback */
int start_xine_playback(XINE_SESSION *s){
	printf("start_xine_playback\n");
	xine_play(s->stream, 0, 0);
	return 0;
}

/* GtkButton clicked callback function to start playback */
void on_play_clicked(GtkWidget *widget, gpointer data){
	start_xine_playback((XINE_SESSION*)data);
}

int main(int argc, char **argv) {
	int i;
	int rv;
	GtkWidget *win;
	GtkWidget *vid_win;
	GtkWidget *vbox;
	GtkWidget *play_button;
	XINE_SESSION xine;

	/* Initialise to some sane defaults */
	xine.vo_driver = "auto";
	xine.ao_driver = "auto";
	xine.mrl = NULL;

printf("==== GTK Xine Example ==== (Jan-2010)\n\
by Vikram Ambrose <noel.ambrose%cgmail.com>\n",'@');

	if(argc <= 1) {
		printf("Specify a file to play\n");
		return 1;
	}
  
	if(argc > 1) {
		for(i = 1; i < argc; i++) {
			if(!strcmp(argv[i], "-vo")) {
				xine.vo_driver = argv[++i];
			}else if(!strcmp(argv[i], "-ao")) {
				xine.ao_driver = argv[++i];
			}else if((!strcmp(argv[i], "-h")) || (!strcmp(argv[i], "--help"))) {
				printf(HELP_MESSAGE);
			return 0;
			}else {
				xine.mrl = argv[i];
			}
		}
	}else xine.mrl = argv[1];

	/* This is important. 
	 * Could be replaced with Gtk stuff if necessary changes are made 
	 * in libxine */
	if(!XInitThreads()) {
		printf("XInitThreads() failed\n");
		return 1;
	}

	/*** GTK INIT ***/
	gtk_init(0,NULL);

	/* Create the toplevel window, connect the quit signal */
	win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect (G_OBJECT (win), "destroy",
		G_CALLBACK (quit_program), &xine);

	/* Establish a minimim window size */
	GdkGeometry gdkGeometry;
        gdkGeometry.min_width=DEFAULT_WIDTH;
        gdkGeometry.min_height =DEFAULT_WIDTH;
        gtk_window_set_geometry_hints(GTK_WINDOW(win), win,&gdkGeometry, GDK_HINT_MIN_SIZE);

	/* Create our video drawing area */
	vid_win = gtk_drawing_area_new();
        g_signal_connect( G_OBJECT( vid_win ), "size-allocate",
                        G_CALLBACK( on_size_allocate_tadeboro_hack ), &xine );

	/* Create and connect the play button */
	play_button = gtk_button_new_with_label("Play");
	g_signal_connect(play_button,"clicked",G_CALLBACK(on_play_clicked),&xine);
	
	/* Add all the widgets into a veritcal box and add to main window */
	vbox = gtk_vbox_new(FALSE,0);
	gtk_box_pack_start(GTK_BOX(vbox),play_button,FALSE,FALSE,5);
	gtk_box_pack_start(GTK_BOX(vbox),vid_win,TRUE,TRUE,0);
	gtk_container_add(GTK_CONTAINER(win),vbox);


	/* Important. Must be done before giving xine our drawing area */
	gtk_widget_show_all(win);

	initialise_xine(&xine);

	rv = connect_xine_to_gtk(&xine,
			GTK_DRAWING_AREA(vid_win),
			frame_output_cb,
			&xine);

	rv = connect_xine_to_audio(&xine);
	rv = connect_xine_to_file(&xine);
	
	/* Lets Rock'n Roll */
	gtk_main();
	return 0;
}
