/* this file is part of atril, a mate document viewer
 *
 *  Copyright (C) 2014 Avishkar Gupta
 *  Based on ev-view.c, also a part of atril, a mate document viewer.
 *
 * Atril 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.
 *
 * Atril 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.
 */

#include "config.h"

#if ENABLE_EPUB
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <stdlib.h>

#include <webkit2/webkit2.h>

#include "ev-web-view.h"
#include "ev-document-model.h"
#include "ev-jobs.h"

#define EV_WEB_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_WEB_VIEW, EvWebViewClass))
#define EV_IS_WEB_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_WEB_VIEW))
#define EV_WEB_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_WEB_VIEW, EvWebViewClass))

 typedef enum {
 	EV_WEB_VIEW_FIND_NEXT,
 	EV_WEB_VIEW_FIND_PREV
 } EvWebViewFindDirection;

typedef struct _SearchParams {
	gboolean case_sensitive;
	gchar*   search_string;
	EvWebViewFindDirection direction;
	gboolean search_jump;
	gint     on_result;
	guint   *results;
}SearchParams;

struct _EvWebView
{
	WebKitWebView web_view;
	EvDocument *document;
	EvDocumentModel *model;
	gint current_page;
	gboolean inverted_stylesheet ;
	gboolean fullscreen;
	SearchParams *search;
	WebKitFindController *findcontroller;
	WebKitFindOptions findoptions;
	gdouble zoom_level;
	gchar *hlink;
};

struct _EvWebViewClass 
{
	WebKitWebViewClass base_class;
};

/*** Callbacks ***/
static void       ev_web_view_change_page                   (EvWebView          *webview,
						          							 gint                new_page);

static void       ev_web_view_page_changed_cb               (EvDocumentModel     *model,
														      gint                 old_page,
															  gint                 new_page,
														      EvWebView           *webview);
/*** GObject ***/
static void       ev_web_view_dispose                       (GObject             *object);

static void       ev_web_view_finalize						(GObject             *object);
static void       ev_web_view_class_init                    (EvWebViewClass      *klass);
static void       ev_web_view_init                          (EvWebView           *webview);

G_DEFINE_TYPE (EvWebView, ev_web_view, WEBKIT_TYPE_WEB_VIEW)

static void
web_view_update_range_and_current_page (EvWebView *webview)
{
	g_return_if_fail(EV_IS_WEB_VIEW(webview));
	
	if (ev_document_get_n_pages (webview->document) <= 0)
		return;

	ev_document_model_set_page(webview->model, 0);
	webview->current_page = 0;
	EvPage *webpage = ev_document_get_page(webview->document,0);
	webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webpage->backend_page);
}

static void
ev_web_view_dispose (GObject *object)
{
	EvWebView *webview = EV_WEB_VIEW (object);

	if (webview->document) {
		g_object_unref(webview->document);
		webview->document = NULL ;
	}

	if (webview->model) {
		g_object_unref(webview->model);
		webview->model = NULL;
	}
	
	if (webview->hlink) {
		g_free(webview->hlink);
		webview->hlink = NULL;
	}

	if (webview->search) {
		g_free(webview->search);
		webview->search = NULL;
	}

	G_OBJECT_CLASS (ev_web_view_parent_class)->dispose (object);
}

static void
ev_web_view_class_init (EvWebViewClass *klass)
{
	G_OBJECT_CLASS(klass)->finalize = ev_web_view_finalize;
	G_OBJECT_CLASS(klass)->dispose = ev_web_view_dispose;
}

static void
ev_web_view_init (EvWebView *webview)
{
	gtk_widget_set_can_focus (GTK_WIDGET (webview), TRUE);

	gtk_widget_set_has_window (GTK_WIDGET (webview), TRUE);

	webview->current_page = 0;

	webview->search = g_new0(SearchParams, 1);
	webview->search->search_string = NULL;

	webview->search->on_result = -1 ;
	webview->search->results = NULL;
	webview->search->search_jump = TRUE ;
	
	webview->fullscreen = FALSE;
	webview->inverted_stylesheet = FALSE;
	webview->hlink = NULL;
}

static void
ev_web_view_finalize (GObject *object)
{
	G_OBJECT_CLASS(ev_web_view_parent_class)->finalize(object);
}

/*** Callbacks ***/

static void
ev_web_view_change_page (EvWebView *webview,
		         gint    new_page)
{
	g_return_if_fail(EV_IS_WEB_VIEW(webview));
	
	EvDocumentClass *klass = EV_DOCUMENT_GET_CLASS(webview->document);

	webview->current_page = new_page;
	ev_document_model_set_page(webview->model,new_page);
	webkit_find_controller_search_finish(webview->findcontroller);
	if (webview->hlink) {
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webview->hlink);
		g_free(webview->hlink);
		webview->hlink = NULL;
	}
	else {
		EvPage *page = klass->get_page(webview->document,new_page);
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)page->backend_page);
	}
}

static void
ev_web_view_page_changed_cb (EvDocumentModel *model,
			     gint             old_page,
			     gint     	      new_page,
			     EvWebView       *webview)
{
	if (!webview->document)
		return;

	if (webview->current_page != new_page) {
		ev_web_view_change_page (webview, new_page);
	} else {
		webkit_web_view_reload (WEBKIT_WEB_VIEW(webview));
	}
}

GtkWidget*
ev_web_view_new (void)
{
	GtkWidget *webview;

	webview = g_object_new (EV_TYPE_WEB_VIEW, NULL);

	EV_WEB_VIEW(webview)->findcontroller = webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW(webview));
	EV_WEB_VIEW(webview)->findoptions = webkit_find_controller_get_options (EV_WEB_VIEW(webview)->findcontroller);

	EV_WEB_VIEW(webview)->zoom_level = 1.0;

	EV_WEB_VIEW(webview)->findoptions |= WEBKIT_FIND_OPTIONS_NONE;

	return webview;
}

static void
ev_web_view_document_changed_cb (EvDocumentModel *model,
  			         GParamSpec      *pspec,
			         EvWebView       *webview)
{
	g_return_if_fail(EV_IS_WEB_VIEW(webview));
	
	EvDocument *document = ev_document_model_get_document (model);

	if (document != webview->document) {

		if (webview->document )
			g_object_unref(webview->document);

		webview->document = document ;

		if(webview->document) {
			g_object_ref(webview->document);
		}
		webview->inverted_stylesheet = FALSE;
		gint current_page = ev_document_model_get_page(model);
		
		ev_web_view_change_page (webview, current_page);
		
	}
}       

static void
ev_web_view_inverted_colors_changed_cb (EvDocumentModel *model,
				        GParamSpec      *pspec,
				        EvWebView       *webview)
{
	EvDocument *document = ev_document_model_get_document(model);
	
	if (ev_document_model_get_inverted_colors(model) == TRUE) {
		if (document == NULL) {
			ev_document_model_set_inverted_colors(model,FALSE);
			return;
		}		
		if (webview->inverted_stylesheet == FALSE) {
			ev_document_check_add_night_sheet(document);
			webview->inverted_stylesheet = TRUE;
		}
		ev_document_toggle_night_mode(document,TRUE);
		webkit_web_view_reload(WEBKIT_WEB_VIEW(webview));
	}
	else {
		if (document != NULL) {
			ev_document_toggle_night_mode(document,FALSE);
			webkit_web_view_reload(WEBKIT_WEB_VIEW(webview));
		}
	}
}

void
ev_web_view_set_model (EvWebView          *webview,
		       EvDocumentModel    *model)
{
	g_return_if_fail (EV_IS_WEB_VIEW (webview));
	g_return_if_fail (EV_IS_DOCUMENT_MODEL (model));

	if (model == webview->model)
		return;

	if (webview->model) {
		g_signal_handlers_disconnect_by_func (webview->model,
						      ev_web_view_document_changed_cb,
						      webview);
		g_signal_handlers_disconnect_by_func (webview->model,
						      ev_web_view_page_changed_cb,
						      webview);
		g_object_unref (webview->model);
	}
	webview->model = g_object_ref (model);

	/* Initialize webview from model */
	webview->fullscreen = ev_document_model_get_fullscreen (webview->model);
	webview->document = ev_document_model_get_document(webview->model);

	ev_web_view_document_changed_cb (webview->model, NULL, webview);

	g_signal_connect (webview->model, "notify::document",
			  G_CALLBACK (ev_web_view_document_changed_cb),
			  webview);
	g_signal_connect (webview->model, "notify::inverted-colors",
			  G_CALLBACK (ev_web_view_inverted_colors_changed_cb),
			  webview);
	g_signal_connect (webview->model,"page-changed",
			  G_CALLBACK(ev_web_view_page_changed_cb),
			  webview);
}

void
ev_web_view_reload_page (EvWebView         *webview,
                         gint               page)
{
	webkit_web_view_reload (WEBKIT_WEB_VIEW(webview));
}

void
ev_web_view_reload (EvWebView *webview)
{
	web_view_update_range_and_current_page (webview);
}


gboolean
ev_web_view_next_page (EvWebView *webview)
{
	int page, n_pages;

	g_return_val_if_fail (EV_IS_WEB_VIEW (webview), FALSE);
	
	if (!webview->document)
		return FALSE;

	page = ev_document_model_get_page (webview->model);
	n_pages = ev_document_get_n_pages (webview->document);

	page = page + 1;

	if (page < n_pages) {
		ev_document_model_set_page (webview->model, page);
		EvPage *webpage = ev_document_get_page(webview->document,page);
		webview->current_page = page ;
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webpage->backend_page);
		return TRUE;
	} else if (page == n_pages) {
		ev_document_model_set_page (webview->model, page - 1);
		EvPage *webpage = ev_document_get_page(webview->document,page);
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webpage->backend_page);
		return TRUE;
	} else {
		return FALSE;
	}
}

gboolean
ev_web_view_previous_page (EvWebView *webview)
{
	int page;

	g_return_val_if_fail (EV_IS_WEB_VIEW (webview), FALSE);

	if (!webview->document)
		return FALSE;

	page = ev_document_model_get_page (webview->model);

	page = page - 1 ;

	if (page >= 0) {
		ev_document_model_set_page (webview->model, page);
		EvPage *webpage = ev_document_get_page(webview->document,page);
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webpage->backend_page);
		return TRUE;
	} else if (page == -1) {
		ev_document_model_set_page (webview->model, 0);
		EvPage *webpage = ev_document_get_page(webview->document,page);
		webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),(gchar*)webpage->backend_page);
		return TRUE;
	} else {	
		return FALSE;
	}
}
		
void 
ev_web_view_handle_link(EvWebView *webview,EvLink *link) 
{
	EvLinkAction *action = NULL;
	EvLinkDest *dest = NULL;
	EvLinkDestType dest_type ;
	action = ev_link_get_action(link);

	if (action == NULL)
		return;

	dest = ev_link_action_get_dest(action);
	
	if (dest == NULL)
		return;

	dest_type = ev_link_dest_get_dest_type(dest);
	
	switch(dest_type) {
		case EV_LINK_DEST_TYPE_PAGE: {
			ev_document_model_set_page(webview->model,ev_link_dest_get_page(dest));
			break;
		}

		case EV_LINK_DEST_TYPE_PAGE_LABEL: {
			const gchar *text = ev_link_dest_get_page_label (dest);
			gint page = atoi(text);

			if (page <= ev_document_get_n_pages(webview->document) && page > 0) {
				ev_document_model_set_page(webview->model,page-1);
			}
			break;
		}
		case EV_LINK_DEST_TYPE_HLINK: {
			const gchar *uri = ev_link_dest_get_named_dest(dest);
			ev_document_model_set_page(webview->model,ev_link_dest_get_page(dest));
			webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview),uri);
			break;
			
		default:return;
		}
	}
}
/* Searching */

static void
results_counted_cb(WebKitFindController *findcontroller,
                   guint match_count,
                   EvWebView *webview)
{
	if (match_count > 0 && webview->search->on_result < match_count) {
		webkit_find_controller_search(findcontroller,
			                          webview->search->search_string,
			                          webview->findoptions,
			                          match_count);
		webview->search->search_jump = FALSE;
	}
}

/*
 * Jump to find results once we have changed the page in the webview.
 */
static void
jump_to_find_results(EvWebView *webview,
                     WebKitLoadEvent load_event,
                     gpointer data)
{
	if ( load_event != WEBKIT_LOAD_FINISHED) {
		return;
	}

	if (!webview->search->search_string) {
		return;
	}

	if (webview->search->direction == EV_WEB_VIEW_FIND_NEXT) {
		webview->findoptions &= ~WEBKIT_FIND_OPTIONS_BACKWARDS;
		webview->findoptions &= ~WEBKIT_FIND_OPTIONS_WRAP_AROUND;
	}
	else {
		webview->findoptions |= WEBKIT_FIND_OPTIONS_BACKWARDS;
		webview->findoptions |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
	}

	webkit_find_controller_count_matches (webview->findcontroller,
	                                      webview->search->search_string,
	                                      webview->findoptions,
	                                      G_MAXUINT);
	webview->search->search_jump = FALSE;
}

static gint
ev_web_view_find_get_n_results (EvWebView *webview, gint page)
{
	return webview->search->results[page];
}

/**
 * jump_to_find_page
 * @webview: #EvWebView instance
 * @direction: Direction to look
 * @shift: Shift from current page
 *
 * Jumps to the first page that has occurences of searched word.
 * Uses a direction where to look and a shift from current page. 
**/
static void
jump_to_find_page (EvWebView *webview, EvWebViewFindDirection direction, gint shift)
{
	int n_pages, i;

	n_pages = ev_document_get_n_pages (webview->document);

	for (i = 0; i < n_pages; i++) {
		int page;

		if (direction == EV_WEB_VIEW_FIND_NEXT)
			page = webview->current_page + i;
		else
			page = webview->current_page - i;		
		page += shift;

		if (page >= n_pages) {
			page = page - n_pages;
		} else if (page < 0) 
			page = page + n_pages;

		if (page == webview->current_page && ev_web_view_find_get_n_results(webview,page) > 0) {
			if (direction == EV_WEB_VIEW_FIND_PREV) {
				webview->findoptions |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
				webview->findoptions |= WEBKIT_FIND_OPTIONS_BACKWARDS;
			}
			else {
				if (webview->search->search_jump)
					webview->findoptions |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
				else
					webview->findoptions &= ~WEBKIT_FIND_OPTIONS_WRAP_AROUND;
				
				webview->findoptions &= ~WEBKIT_FIND_OPTIONS_BACKWARDS;
			}

			webkit_find_controller_search (webview->findcontroller,
			                               webview->search->search_string,
			                               webview->findoptions,
			                               /*Highlight all the results.*/
			                               G_MAXUINT);
			webview->search->search_jump = FALSE;
			break;
		}

		if (ev_web_view_find_get_n_results (webview, page) > 0) {
			webview->search->direction = direction;
			webkit_find_controller_search_finish(webview->findcontroller);
			ev_document_model_set_page (webview->model, page);
			break;
		}
	}
}

void
ev_web_view_find_changed (EvWebView *webview, guint *results, gchar *text,gboolean case_sensitive)
{
	webview->search->results = results; 
	webview->search->on_result = 0;
	webview->search->search_string = g_strdup(text);
	webview->search->case_sensitive = case_sensitive;
		if (webview->search->search_jump == TRUE) {
		if (!case_sensitive) {
			webview->findoptions |=	 WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
		}
		else {
			webview->findoptions &= ~WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
		}
		jump_to_find_page (webview, EV_WEB_VIEW_FIND_NEXT, 0);
	}
}	

void
ev_web_view_find_next (EvWebView *webview)
{
	gint n_results;

	n_results = ev_web_view_find_get_n_results (webview, webview->current_page);
	webview->search->on_result++;

	if (webview->search->on_result >= n_results) {
		webview->search->on_result = 0;
		jump_to_find_page (webview, EV_WEB_VIEW_FIND_NEXT, 1);
	} 
	else {
		webkit_find_controller_search_next(webview->findcontroller);
	}
}

void
ev_web_view_find_previous (EvWebView *webview)
{
	webview->search->on_result--;

	if (webview->search->on_result < 0) {
		jump_to_find_page (webview, EV_WEB_VIEW_FIND_PREV, -1);
		webview->search->on_result = MAX (0, ev_web_view_find_get_n_results (webview, webview->current_page) - 1);
	} else {
		webkit_find_controller_search_previous(webview->findcontroller);
	}
}

void
ev_web_view_find_search_changed (EvWebView *webview)
{
	/* search string has changed, focus on new search result */
	if (webview->search->search_string) {
		g_free(webview->search->search_string);
		webview->search->search_string = NULL;
	}

	webkit_find_controller_search_finish(webview->findcontroller);
	webview->search->search_jump = TRUE;
}

void
ev_web_view_find_cancel (EvWebView *webview)
{
	webkit_find_controller_search_finish (webview->findcontroller);
}

void 
ev_web_view_set_handler(EvWebView *webview,gboolean visible)
{
	if (visible) {
		g_signal_connect(webview,
		                 "load-changed",
		                 G_CALLBACK(jump_to_find_results),
		                 NULL);
		g_signal_connect(webview->findcontroller,
		                 "counted-matches",
		                 G_CALLBACK(results_counted_cb),
		                 webview);
	}
	else {
		g_signal_handlers_disconnect_by_func(webview,
											 jump_to_find_results,
											 NULL);		
		g_signal_handlers_disconnect_by_func(webview,
		                                     results_counted_cb,
		                                     NULL);
	}
}

/* Selection and copying*/
void
ev_web_view_select_all(EvWebView *webview)
{
	webkit_web_view_execute_editing_command(WEBKIT_WEB_VIEW(webview),
	                                        WEBKIT_EDITING_COMMAND_SELECT_ALL);
}

static void
copy_text_cb(WebKitWebView *webview,
             GAsyncResult *res,
             gpointer data)
{
	gboolean okay_to_copy = webkit_web_view_can_execute_editing_command_finish (WEBKIT_WEB_VIEW(webview),
	                                                                            res,
	                                                                            NULL);

	if (okay_to_copy) {
		webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW(webview),
		                                         WEBKIT_EDITING_COMMAND_COPY);
	}
}

void
ev_web_view_copy(EvWebView *webview)
{
	webkit_web_view_can_execute_editing_command(WEBKIT_WEB_VIEW(webview),
	                                            WEBKIT_EDITING_COMMAND_COPY,
	                                            NULL,
	                                            (GAsyncReadyCallback)copy_text_cb,
	                                            NULL);
}

/*Zoom control*/
gboolean
ev_web_view_zoom_in(EvWebView *webview)
{
	webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW(webview),
	                                (webview->zoom_level+= 0.1));
	return TRUE;
}

gboolean
ev_web_view_zoom_out(EvWebView *webview)
{
	if (webview->zoom_level == 1)
		return FALSE;

	webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW(webview),
	                                (webview->zoom_level -= 0.1));
	return TRUE;
}

/**
 * ev_web_view_disconnect_handlers
 * @webview : #EvWebView instance
 * 
 * This function call will disconnect all model signal handlers from the webview, to ensure smooth operation of the Atril-view.
 * Equivalent to function  ev_view_disconnect_handlers in ev-view.c
 */
void
ev_web_view_disconnect_handlers(EvWebView *webview)
{
	g_signal_handlers_disconnect_by_func(webview->model,
	                                     ev_web_view_document_changed_cb,
	                                     webview);
	g_signal_handlers_disconnect_by_func(webview->model,
	                                     ev_web_view_inverted_colors_changed_cb,
	                                     webview);
	g_signal_handlers_disconnect_by_func(webview->model,
	                                     ev_web_view_page_changed_cb,
	                                     webview);
}
#endif