/*
 * Copyright (C) 2008 INdT - Instituto Nokia de Tecnologia
 * Copyright (C) 2009, 2010 ProFUSION embedded systems
 * Copyright (C) 2009-2014 Samsung Electronics
 * Copyright (C) 2012 Intel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "EWebKit.h"

#include "url_bar.h"
#include "url_utils.h"
#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Ecore_File.h>
#include <Ecore_Getopt.h>
#include <Ecore_X.h>
#include <Edje.h>
#include <Evas.h>
#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define DEFAULT_WIDTH      800
#define DEFAULT_HEIGHT     600
#define DEFAULT_ZOOM_INIT  1.0

#define info(format, args...)       \
    do {                            \
        if (verbose)                \
            printf(format"\n", ##args); \
    } while (0)

#define MIN_ZOOM_LEVEL 0
#define DEFAULT_ZOOM_LEVEL 5
#define MAX_ZOOM_LEVEL 13

static int currentZoomLevel = DEFAULT_ZOOM_LEVEL;
static float currentZoom = 1.0;

// the zoom values are chosen to be like in Mozilla Firefox 3
static int zoomLevels[] = {30, 50, 67, 80, 90,
                            100,
                            110, 120, 133, 150, 170, 200, 240, 300};

static int verbose = 0;

static Eina_List *windows = NULL;

static char *themePath = NULL;

typedef struct _Window_Properties {
    Eina_Bool toolbarsVisible:1;
    Eina_Bool statusbarVisible:1;
    Eina_Bool scrollbarsVisible:1;
    Eina_Bool menubarVisible:1;
} Window_Properties;

Window_Properties windowProperties = { /* Pretend we have them and they are initially visible */
    EINA_TRUE,
    EINA_TRUE,
    EINA_TRUE,
    EINA_TRUE
};

static const Ecore_Getopt options = {
    "EWebLauncher",
    "%prog [options] [url]",
    "0.0.1",
    "(C)2008 INdT (The Nokia Technology Institute)\n"
    "(C)2009, 2010 ProFUSION embedded systems\n"
    "(C)2009-2014 Samsung Electronics\n"
    "(C)2012 Intel Corporation\n",
    "GPL",
    "Test Web Browser using the Enlightenment Foundation Libraries of WebKit",
    EINA_TRUE, {
        ECORE_GETOPT_STORE_STR
            ('e', "engine", "ecore-evas engine to use."),
        ECORE_GETOPT_CALLBACK_NOARGS
            ('E', "list-engines", "list ecore-evas engines.",
             ecore_getopt_callback_ecore_evas_list_engines, NULL),
        ECORE_GETOPT_STORE_DOUBLE
            ('r', "device-pixel-ratio", "Ratio between the CSS units and device pixels."),
        ECORE_GETOPT_STORE_DEF_BOOL
            ('c', "encoding-detector", "enable/disable encoding detector", 0),
        ECORE_GETOPT_STORE_DEF_BOOL
            ('f', "flattening", "frame flattening.", 0),
        ECORE_GETOPT_STORE_DEF_BOOL
            ('F', "fullscreen", "fullscreen mode.", 0),
        ECORE_GETOPT_CALLBACK_ARGS
            ('g', "geometry", "geometry to use in x:y:w:h form.", "X:Y:W:H",
             ecore_getopt_callback_geometry_parse, NULL),
        ECORE_GETOPT_STORE_STR
            ('t', "theme", "path to read the theme file from."),
        ECORE_GETOPT_STORE_STR
            ('U', "user-agent", "custom user agent string to use."),
        ECORE_GETOPT_COUNT
            ('v', "verbose", "be more verbose."),
        ECORE_GETOPT_VERSION
            ('V', "version"),
        ECORE_GETOPT_COPYRIGHT
            ('R', "copyright"),
        ECORE_GETOPT_LICENSE
            ('L', "license"),
        ECORE_GETOPT_HELP
            ('h', "help"),
        ECORE_GETOPT_SENTINEL
    }
};

typedef struct _User_Arguments {
    char *engine;
    Eina_Bool quitOption;
    double device_pixel_ratio;
    Eina_Bool enableEncodingDetector;
    Eina_Bool isFlattening;
    Eina_Bool isFullscreen;
    Eina_Rectangle geometry;
    char *theme;
    char *userAgent;
    char *databasePath;
} User_Arguments;

typedef struct _ELauncher {
    Ecore_Evas *ee;
    Evas *evas;
    Evas_Object *browser;
    Url_Bar *url_bar;
    User_Arguments *userArgs;
} ELauncher;

static void windowDestroy(Ecore_Evas *ee);
static void closeWindow(Ecore_Evas *ee);
static int browserCreate(const char *url, User_Arguments *userArgs);
static int webInspectorCreate(ELauncher *appBrowser);
static ELauncher *windowCreate(User_Arguments *userArgs);

static ELauncher *
find_app_from_ee(Ecore_Evas *ee)
{
    Eina_List *l;
    void *data;

    EINA_LIST_FOREACH(windows, l, data)
    {
        ELauncher *app = (ELauncher *) data;
        if (app->ee == ee)
            return app;
    }
    return NULL;
}

static void
print_history(Eina_List *list, Evas *e)
{
    Eina_List *l;
    void *d;

    if (!verbose)
        return;

    printf("Session history contains:\n");

    EINA_LIST_FOREACH(list, l, d) {
        Ewk_History_Item *item = (Ewk_History_Item*)d;
        Evas_Object *icon = ewk_history_item_icon_object_add(item, e);
        char buf[PATH_MAX];
        int s = snprintf(buf, sizeof(buf), "/tmp/favicon-%s.png", ewk_history_item_uri_original_get(item));
        for (s--; s >= (int)sizeof("/tmp/favicon-"); s--) {
            if (!isalnum(buf[s]) && buf[s] != '.')
                buf[s] = '_';
        }

        evas_object_image_save(icon, buf, NULL, NULL);

        printf("* '%s' title='%s' icon='%s'\n", ewk_history_item_uri_original_get(item), ewk_history_item_title_get(item), buf);
    }
}

static int
nearest_zoom_level_get(float factor)
{
    int i, intFactor = (int)(factor * 100.0);
    for (i = 0; zoomLevels[i] <= intFactor; i++) { }
    printf("factor=%f, intFactor=%d, zoomLevels[%d]=%d, zoomLevels[%d]=%d\n",
           factor, intFactor, i-1, zoomLevels[i-1], i, zoomLevels[i]);
    if (intFactor - zoomLevels[i-1] < zoomLevels[i] - intFactor)
        return i - 1;
    return i;
}

static Eina_Bool
zoom_level_set(Evas_Object *webview, int level)
{
    float factor = ((float) zoomLevels[level]) / 100.0;
    Evas_Coord ox, oy, mx, my, cx, cy;
    evas_pointer_canvas_xy_get(evas_object_evas_get(webview), &mx, &my);
    evas_object_geometry_get(webview, &ox, &oy, NULL, NULL);
    cx = mx - ox;
    cy = my - oy;
    return ewk_view_zoom_animated_set(webview, factor, 0.5, cx, cy);
}

static void
on_browser_ecore_evas_resize(Ecore_Evas *ee)
{
    ELauncher *app;
    Evas_Object *webview;
    int w, h;

    ecore_evas_geometry_get(ee, NULL, NULL, &w, &h);

    /* Resize URL bar */
    app = find_app_from_ee(ee);
    url_bar_width_set(app->url_bar, w);

    webview = evas_object_name_find(ecore_evas_get(ee), "browser");
    evas_object_move(webview, 0, URL_BAR_HEIGHT);
    evas_object_resize(webview, w, h - URL_BAR_HEIGHT);
}

static void
on_inspector_ecore_evas_resize(Ecore_Evas *ee)
{
    Evas_Object *webview;
    int w, h;

    ecore_evas_geometry_get(ee, NULL, NULL, &w, &h);

    webview = evas_object_name_find(ecore_evas_get(ee), "inspector");
    evas_object_move(webview, 0, 0);
    evas_object_resize(webview, w, h);
}

static void
title_set(Ecore_Evas *ee, const Ewk_Text_With_Direction *title, int progress)
{
    const char *appname = "EFL Test Launcher";
    const char *separator = " - ";
    char label[4096];
    int size;

    if (!title || !title->string || !strcmp(title->string, "")) {
        ecore_evas_title_set(ee, appname);
        return;
    }

    if (progress < 100)
        size = snprintf(label, sizeof(label), "%s (%d%%)%s%s", title->string, progress, separator, appname);
    else
        size = snprintf(label, sizeof(label), "%s %s%s", title->string, separator, appname);

    if (size >= (int)sizeof(label))
        return;

    ecore_evas_title_set(ee, label);
}

static void
on_title_changed(void *user_data, Evas_Object *webview, void *event_info)
{
    ELauncher *app = (ELauncher *)user_data;
    const Ewk_Text_With_Direction *title = (const Ewk_Text_With_Direction *)event_info;

    title_set(app->ee, title, 100);
}

static void
on_progress(void *user_data, Evas_Object *webview, void *event_info)
{
    ELauncher *app = (ELauncher *)user_data;
    double *progress = (double *)event_info;

    title_set(app->ee, ewk_view_title_get(app->browser), *progress * 100);
}

static void
on_load_finished(void *user_data, Evas_Object *webview, void *event_info)
{
    const Ewk_Frame_Load_Error *err = (const Ewk_Frame_Load_Error *)event_info;

    if (!err)
        info("Succeeded loading page.");
    else if (err->is_cancellation)
        info("Load was cancelled.");
    else
        info("Failed loading page: %d %s \"%s\", url=%s",
             err->code, err->domain, err->description, err->failing_url);

    currentZoom = ewk_view_zoom_get(webview);
    currentZoomLevel = nearest_zoom_level_get(currentZoom);
    info("WebCore Zoom=%f, currentZoomLevel=%d", currentZoom, currentZoomLevel);
}

static void
on_load_error(void *user_data, Evas_Object *webview, void *event_info)
{
    const Ewk_Frame_Load_Error *err = (const Ewk_Frame_Load_Error *)event_info;
    char message[1024];
    snprintf(message, 1024, "<html><body><div style=\"color:#ff0000\">ERROR!</div><br><div>Code: %d<br>Domain: %s<br>Description: %s<br>URL: %s</div></body</html>",
             err->code, err->domain, err->description, err->failing_url);
    ewk_frame_contents_set(err->frame, message, 0, "text/html", "UTF-8", err->failing_url);
}

static void
on_toolbars_visible_set(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    if (*visible) {
        info("Toolbars visible changed: show");
        windowProperties.toolbarsVisible = EINA_TRUE;
    } else {
        info("Toolbars visible changed: hide");
        windowProperties.toolbarsVisible = EINA_FALSE;
    }
}

static void
on_toolbars_visible_get(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    *visible = windowProperties.toolbarsVisible;
}

static void
on_statusbar_visible_set(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    if (*visible) {
        info("Statusbar visible changed: show");
        windowProperties.statusbarVisible = EINA_TRUE;
    } else {
        info("Statusbar visible changed: hide");
        windowProperties.statusbarVisible = EINA_FALSE;
    }
}

static void
on_statusbar_visible_get(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    *visible = windowProperties.statusbarVisible;
}

static void
on_scrollbars_visible_set(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    if (*visible) {
        info("Scrollbars visible changed: show");
        windowProperties.scrollbarsVisible = EINA_TRUE;
    } else {
        info("Scrollbars visible changed: hide");
        windowProperties.scrollbarsVisible = EINA_FALSE;
    }
}

static void
on_scrollbars_visible_get(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    *visible = windowProperties.scrollbarsVisible;
}

static void
on_menubar_visible_set(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    if (*visible) {
        info("Menubar visible changed: show");
        windowProperties.menubarVisible = EINA_TRUE;
    } else {
        info("Menubar visible changed: hide");
        windowProperties.menubarVisible = EINA_FALSE;
    }
}

static void
on_menubar_visible_get(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool *visible = (Eina_Bool *)event_info;
    *visible = windowProperties.menubarVisible;
}

static void
on_tooltip_text_set(void* user_data, Evas_Object* webview, void* event_info)
{
    const char *text = (const char *)event_info;
    info("Tooltip is set: %s", text);
}

static void
on_tooltip_text_unset(void* user_data, Evas_Object* webview, void* event_info)
{
    info("Tooltip is unset");
}

static void
on_inputmethod_changed(void* user_data, Evas_Object* webview, void* event_info)
{
    Eina_Bool active = (Eina_Bool)(long)event_info;
    unsigned int imh;
    info("Keyboard changed: %d", active);

    if (!active)
        return;

    imh = ewk_view_imh_get(webview);
    info("    Keyboard flags: %#.2x", imh);

}

static void
on_url_changed(void* user_data, Evas_Object* webview, void* event_info)
{
    ELauncher *app = (ELauncher *)user_data;
    url_bar_url_set(app->url_bar, ewk_view_uri_get(app->browser));
}

static void
on_mouse_down(void* data, Evas* e, Evas_Object* webview, void* event_info)
{
    Evas_Event_Mouse_Down *ev = (Evas_Event_Mouse_Down *)event_info;

    if (ev->button == 1)
        evas_object_focus_set(webview, EINA_TRUE);
    else if (ev->button == 2)
        evas_object_focus_set(webview, !evas_object_focus_get(webview));
}

static void
on_focus_out(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    info("the webview lost keyboard focus");
}

static void
on_focus_in(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    info("the webview gained keyboard focus");
}

static void
on_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    Evas_Event_Key_Down *ev = (Evas_Event_Key_Down*) event_info;
    ELauncher *app = data;
    static const char *encodings[] = {
        "ISO-8859-1",
        "UTF-8",
        NULL
    };
    static int currentEncoding = -1;
    const Evas_Modifier *mod = evas_key_modifier_get(e);
    Eina_Bool ctrlPressed = evas_key_modifier_is_set(mod, "Control");
    Eina_Bool altPressed = evas_key_modifier_is_set(mod, "Alt");

    if (!strcmp(ev->key, "Escape")) {
        closeWindow(app->ee);
    } else if (!strcmp(ev->key, "Left") && altPressed) {
        info("Back (Alt+Left) was pressed");
        if (ewk_view_back_possible(obj)) {
            Ewk_History *history = ewk_view_history_get(obj);
            Eina_List *list = ewk_history_back_list_get(history);
            print_history(list, e);
            ewk_history_item_list_free(list);
            ewk_view_back(obj);
        } else
            info("Back ignored: No back history");
    } else if (!strcmp(ev->key, "Right") && altPressed) {
        info("Forward (Alt+Right) was pressed");
        if (ewk_view_forward_possible(obj)) {
            Ewk_History *history = ewk_view_history_get(obj);
            Eina_List *list = ewk_history_forward_list_get(history);
            print_history(list, e);
            ewk_history_item_list_free(list);
            ewk_view_forward(obj);
        } else
            info("Forward ignored: No forward history");
    } else if (!strcmp(ev->key, "F3")) {
        currentEncoding++;
        currentEncoding %= (sizeof(encodings) / sizeof(encodings[0]));
        info("Set encoding (F3) pressed. New encoding to %s", encodings[currentEncoding]);
        ewk_view_setting_encoding_custom_set(obj, encodings[currentEncoding]);
    } else if (!strcmp(ev->key, "F4")) {
        Evas_Object *frame = ewk_view_frame_main_get(obj);
        Evas_Coord x, y;
        Ewk_Hit_Test *ht;

        evas_pointer_canvas_xy_get(evas_object_evas_get(obj), &x, &y);
        ht = ewk_frame_hit_test_new(frame, x, y);
        if (!ht)
            printf("No hit test returned for point %d,%d\n", x, y);
        else {
            printf("Hit test for point %d,%d\n"
                   "  pos=%3d,%3d\n"
                   "  bounding_box=%d,%d + %dx%d\n"
                   "  title='%s'\n"
                   "  alternate_text='%s'\n"
                   "  frame=%p (%s)\n"
                   "  link {\n"
                   "    text='%s'\n"
                   "    url='%s'\n"
                   "    title='%s'\n"
                   "    target frame=%p (%s)\n"
                   "  }\n"
                   "context:\n"
                   "%s"
                   "%s"
                   "%s"
                   "%s"
                   "%s\n",
                   x, y,
                   ht->x, ht->y,
                   ht->bounding_box.x, ht->bounding_box.y, ht->bounding_box.w, ht->bounding_box.h,
                   ht->title.string,
                   ht->alternate_text,
                   ht->frame, evas_object_name_get(ht->frame),
                   ht->link.text,
                   ht->link.url,
                   ht->link.title,
                   ht->link.target_frame, evas_object_name_get(ht->link.target_frame),
                   ht->context & EWK_HIT_TEST_RESULT_CONTEXT_LINK ? "  LINK\n" : "",
                   ht->context & EWK_HIT_TEST_RESULT_CONTEXT_IMAGE ? "  IMAGE\n" : "",
                   ht->context & EWK_HIT_TEST_RESULT_CONTEXT_MEDIA ? "   MEDIA\n" : "",
                   ht->context & EWK_HIT_TEST_RESULT_CONTEXT_SELECTION ? "  SELECTION\n" : "",
                   ht->context & EWK_HIT_TEST_RESULT_CONTEXT_EDITABLE ? "  EDITABLE" : "");
            ewk_frame_hit_test_free(ht);
        }

    } else if (!strcmp(ev->key, "F5")) {
        info("Reload (F5) was pressed, reloading.");
        ewk_view_reload(obj);
    } else if (!strcmp(ev->key, "F6")) {
        info("Stop (F6) was pressed, stop loading.");
        ewk_view_stop(obj);
    } else if (!strcmp(ev->key, "F12")) {
        Eina_Bool status = ewk_view_setting_spatial_navigation_get(obj);
        ewk_view_setting_spatial_navigation_set(obj, !status);
        info("Command::keyboard navigation toggle");
    } else if ((!strcmp(ev->key, "minus") || !strcmp(ev->key, "KP_Subtract")) && ctrlPressed) {
        if (currentZoomLevel > MIN_ZOOM_LEVEL && zoom_level_set(obj, currentZoomLevel - 1))
            currentZoomLevel--;
        info("Zoom out (Ctrl + '-') was pressed, zoom level became %.2f", zoomLevels[currentZoomLevel] / 100.0);
    } else if ((!strcmp(ev->key, "equal") || !strcmp(ev->key, "KP_Add")) && ctrlPressed) {
        if (currentZoomLevel < MAX_ZOOM_LEVEL && zoom_level_set(obj, currentZoomLevel + 1))
            currentZoomLevel++;
        info("Zoom in (Ctrl + '+') was pressed, zoom level became %.2f", zoomLevels[currentZoomLevel] / 100.0);
    } else if (!strcmp(ev->key, "0") && ctrlPressed) {
        if (zoom_level_set(obj, DEFAULT_ZOOM_LEVEL))
            currentZoomLevel = DEFAULT_ZOOM_LEVEL;
        info("Zoom to default (Ctrl + '0') was pressed, zoom level became %.2f", zoomLevels[currentZoomLevel] / 100.0);
    } else if (!strcmp(ev->key, "n") && ctrlPressed) {
        info("Create new window (Ctrl+n) was pressed.");
        browserCreate("http://www.google.com", app->userArgs);
    } else if (!strcmp(ev->key, "d") && ctrlPressed) {
        info("Render suspended");
        ewk_view_disable_render(obj);
    } else if (!strcmp(ev->key, "e") && ctrlPressed) {
        info("Render resumed");
        ewk_view_enable_render(obj);
    } else if (!strcmp(ev->key, "s") && ctrlPressed) {
        Evas_Object *frame = ewk_view_frame_main_get(obj);
        Ewk_Security_Origin *origin = ewk_frame_security_origin_get(frame);
        printf("Security origin information:\n"
               "  protocol=%s\n"
               "  host=%s\n"
               "  port=%d\n"
               "  web database quota=%" PRIu64 "\n",
               ewk_security_origin_protocol_get(origin),
               ewk_security_origin_host_get(origin),
               ewk_security_origin_port_get(origin),
               ewk_security_origin_web_database_quota_get(origin));

        Eina_List *databaseList = ewk_security_origin_web_database_get_all(origin);
        Eina_List *listIterator = 0;
        Ewk_Web_Database *database;
        EINA_LIST_FOREACH(databaseList, listIterator, database)
            printf("Database information:\n"
                   "  name=%s\n"
                   "  display name=%s\n"
                   "  filename=%s\n"
                   "  expected size=%" PRIu64 "\n"
                   "  size=%" PRIu64 "\n",
                   ewk_web_database_name_get(database),
                   ewk_web_database_display_name_get(database),
                   ewk_web_database_filename_get(database),
                   ewk_web_database_expected_size_get(database),
                   ewk_web_database_size_get(database));

        ewk_security_origin_free(origin);
        ewk_web_database_list_free(databaseList);
    } else if (!strcmp(ev->key, "i") && ctrlPressed) {
        Evas_Object *inspector_view = ewk_view_inspector_view_get(obj);
        if (inspector_view) {
            info("Web Inspector close");
            ewk_view_inspector_close(obj);
        } else {
            info("Web Inspector show");
            ewk_view_inspector_show(obj);
        }
    }
}

static void
on_browser_del(void *data, Evas *evas, Evas_Object *browser, void *event)
{
    ELauncher *app = (ELauncher*) data;

    evas_object_event_callback_del(app->browser, EVAS_CALLBACK_KEY_DOWN, on_key_down);
    evas_object_event_callback_del(app->browser, EVAS_CALLBACK_MOUSE_DOWN, on_mouse_down);
    evas_object_event_callback_del(app->browser, EVAS_CALLBACK_FOCUS_IN, on_focus_in);
    evas_object_event_callback_del(app->browser, EVAS_CALLBACK_FOCUS_OUT, on_focus_out);
    evas_object_event_callback_del(app->browser, EVAS_CALLBACK_DEL, on_browser_del);
}

static void
on_inspector_view_create(void *user_data, Evas_Object *webview, void *event_info)
{
    ELauncher *app_browser = (ELauncher *)user_data;

    webInspectorCreate(app_browser);
}

static void
on_inspector_view_close(void *user_data, Evas_Object *webview, void *event_info)
{
    Eina_List *l;
    void *app;
    ELauncher *app_browser = (ELauncher *)user_data;
    Evas_Object *inspector_view = (Evas_Object *)event_info;

    ewk_view_inspector_view_set(app_browser->browser, NULL);

    EINA_LIST_FOREACH(windows, l, app)
        if (((ELauncher *)app)->browser == inspector_view)
            break;

    windows = eina_list_remove(windows, app);
    windowDestroy(((ELauncher *)app)->ee);
    free(app);
}

static void
on_inspector_view_destroyed(Ecore_Evas *ee)
{
    ELauncher *app;

    app = find_app_from_ee(ee);
    evas_object_smart_callback_call(app->browser, "inspector,view,destroy", NULL);
}

static int
quit(Eina_Bool success, const char *msg)
{
    edje_shutdown();
    ecore_evas_shutdown();
    ecore_file_shutdown();

    if (msg)
        fputs(msg, (success) ? stdout : stderr);

    if (themePath) {
        free(themePath);
        themePath = NULL;
    }

    if (!success)
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

static int
browserCreate(const char *url, User_Arguments *userArgs)
{
    ELauncher *appBrowser = windowCreate(userArgs);
    if (!appBrowser)
        return quit(EINA_FALSE, "ERROR: could not create a browser window\n");

    ecore_evas_title_set(appBrowser->ee, "EFL Test Launcher");
    ecore_evas_callback_resize_set(appBrowser->ee, on_browser_ecore_evas_resize);
    ecore_evas_callback_delete_request_set(appBrowser->ee, closeWindow);

    evas_object_name_set(appBrowser->browser, "browser");

    evas_object_smart_callback_add(appBrowser->browser, "inputmethod,changed", on_inputmethod_changed, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "inspector,view,close", on_inspector_view_close, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "inspector,view,create", on_inspector_view_create, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "load,error", on_load_error, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "load,finished", on_load_finished, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "load,progress", on_progress, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "menubar,visible,get", on_menubar_visible_get, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "menubar,visible,set", on_menubar_visible_set, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "scrollbars,visible,get", on_scrollbars_visible_get, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "scrollbars,visible,set", on_scrollbars_visible_set, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "statusbar,visible,get", on_statusbar_visible_get, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "statusbar,visible,set", on_statusbar_visible_set, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "title,changed", on_title_changed, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "toolbars,visible,get", on_toolbars_visible_get, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "toolbars,visible,set", on_toolbars_visible_set, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "tooltip,text,set", on_tooltip_text_set, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "tooltip,text,unset", on_tooltip_text_unset, appBrowser);
    evas_object_smart_callback_add(appBrowser->browser, "uri,changed", on_url_changed, appBrowser);

    evas_object_event_callback_add(appBrowser->browser, EVAS_CALLBACK_DEL, on_browser_del, appBrowser);
    evas_object_event_callback_add(appBrowser->browser, EVAS_CALLBACK_FOCUS_IN, on_focus_in, appBrowser);
    evas_object_event_callback_add(appBrowser->browser, EVAS_CALLBACK_FOCUS_OUT, on_focus_out, appBrowser);
    evas_object_event_callback_add(appBrowser->browser, EVAS_CALLBACK_KEY_DOWN, on_key_down, appBrowser);
    evas_object_event_callback_add(appBrowser->browser, EVAS_CALLBACK_MOUSE_DOWN, on_mouse_down, appBrowser);

    ewk_view_setting_enable_developer_extras_set(appBrowser->browser, EINA_TRUE);

    appBrowser->url_bar = url_bar_add(appBrowser->browser, DEFAULT_WIDTH);

    evas_object_move(appBrowser->browser, 0, URL_BAR_HEIGHT);
    evas_object_resize(appBrowser->browser, userArgs->geometry.w, userArgs->geometry.h - URL_BAR_HEIGHT);

    ewk_view_uri_set(appBrowser->browser, url);

    evas_object_show(appBrowser->browser);
    ecore_evas_show(appBrowser->ee);

    evas_object_focus_set(appBrowser->browser, EINA_TRUE);

    return 1;
}

static int
webInspectorCreate(ELauncher *appBrowser)
{
    ELauncher *appInspector = windowCreate(appBrowser->userArgs);
    if (!appInspector)
        return quit(EINA_FALSE, "ERROR: could not create an inspector window\n");

    ecore_evas_title_set(appInspector->ee, "Web Inspector");
    ecore_evas_callback_resize_set(appInspector->ee, on_inspector_ecore_evas_resize);
    ecore_evas_callback_delete_request_set(appInspector->ee, on_inspector_view_destroyed);

    evas_object_name_set(appInspector->browser, "inspector");

    evas_object_move(appInspector->browser, 0, 0);
    evas_object_resize(appInspector->browser, appInspector->userArgs->geometry.w, appInspector->userArgs->geometry.h);

    evas_object_show(appInspector->browser);
    ecore_evas_show(appInspector->ee);

    evas_object_focus_set(appInspector->browser, EINA_TRUE);

    ewk_view_inspector_view_set(appBrowser->browser, appInspector->browser);

    return 1;
}

static ELauncher *
windowCreate(User_Arguments *userArgs)
{
    ELauncher *app = (ELauncher *)malloc(sizeof(ELauncher));
    if (!app) {
        quit(EINA_FALSE, "ERROR: could not create an ELauncher\n");
        return NULL;
    }

#if defined(HAVE_ECORE_X)
    if (userArgs->engine)
#endif
        app->ee = ecore_evas_new(userArgs->engine, 0, 0, userArgs->geometry.w, userArgs->geometry.h, NULL);
#if defined(HAVE_ECORE_X)
    else {
        const char* engine = "opengl_x11";
        app->ee = ecore_evas_new(engine, 0, 0, userArgs->geometry.w, userArgs->geometry.h, NULL);
    }
#endif
    if (!app->ee) {
        quit(EINA_FALSE, "ERROR: could not construct evas-ecore\n");
        return NULL;
    }

    if (userArgs->isFullscreen)
        ecore_evas_fullscreen_set(app->ee, EINA_TRUE);

    app->evas = ecore_evas_get(app->ee);
    if (!app->evas) {
        quit(EINA_FALSE, "ERROR: could not get evas from evas-ecore\n");
        return NULL;
    }

    app->browser = ewk_view_add(app->evas);

    ewk_view_theme_set(app->browser, themePath);
    if (userArgs->userAgent)
        ewk_view_setting_user_agent_set(app->browser, userArgs->userAgent);

    ewk_view_setting_local_storage_database_path_set(app->browser, userArgs->databasePath);
    ewk_view_setting_enable_frame_flattening_set(app->browser, userArgs->isFlattening);
    ewk_view_setting_encoding_detector_set(app->browser, userArgs->enableEncodingDetector);
    ewk_view_device_pixel_ratio_set(app->browser, userArgs->device_pixel_ratio);

    app->userArgs = userArgs;
    app->url_bar = NULL;

    windows = eina_list_append(windows, app);

    return app;
}

static void
windowDestroy(Ecore_Evas *ee)
{
    ecore_evas_free(ee);
    if (!eina_list_count(windows))
        ecore_main_loop_quit();
}

static void
closeWindow(Ecore_Evas *ee)
{
    ELauncher *app;

    app = find_app_from_ee(ee);
    ewk_view_inspector_close(app->browser);

    windows = eina_list_remove(windows, app);
    url_bar_del(app->url_bar);
    windowDestroy(ee);
    free(app);
}

static Eina_Bool
main_signal_exit(void *data, int ev_type, void *ev)
{
    ELauncher *app;
    while (windows) {
        app = (ELauncher*) eina_list_data_get(windows);
        ewk_view_inspector_close(app->browser);

        ecore_evas_free(app->ee);
        windows = eina_list_remove(windows, app);
    }
    if (!eina_list_count(windows))
        ecore_main_loop_quit();
    return EINA_TRUE;
}

static char *
findThemePath(const char *theme)
{
    const char *default_theme = TEST_THEME_DIR "/default.edj";
    char *rpath;
    struct stat st;

    if (!theme)
        theme = default_theme;

    rpath = ecore_file_realpath(theme);

    if (!strlen(rpath) || stat(rpath, &st)) {
        free(rpath);
        return NULL;
    }

    return rpath;
}

int
parseUserArguments(int argc, char *argv[], User_Arguments *userArgs)
{
    int args;

    userArgs->engine = NULL;
    userArgs->quitOption = EINA_FALSE;
    userArgs->device_pixel_ratio = 1.0;
    userArgs->enableEncodingDetector = EINA_FALSE;
    userArgs->isFlattening = EINA_FALSE;
    userArgs->isFullscreen = EINA_FALSE;
    userArgs->geometry.x = 0;
    userArgs->geometry.y = 0;
    userArgs->geometry.w = 0;
    userArgs->geometry.h = 0;
    userArgs->theme = NULL;
    userArgs->userAgent = NULL;

    Ecore_Getopt_Value values[] = {
        ECORE_GETOPT_VALUE_STR(userArgs->engine),
        ECORE_GETOPT_VALUE_BOOL(userArgs->quitOption),
        ECORE_GETOPT_VALUE_DOUBLE(userArgs->device_pixel_ratio),
        ECORE_GETOPT_VALUE_BOOL(userArgs->enableEncodingDetector),
        ECORE_GETOPT_VALUE_BOOL(userArgs->isFlattening),
        ECORE_GETOPT_VALUE_BOOL(userArgs->isFullscreen),
        ECORE_GETOPT_VALUE_PTR_CAST(userArgs->geometry),
        ECORE_GETOPT_VALUE_STR(userArgs->theme),
        ECORE_GETOPT_VALUE_STR(userArgs->userAgent),
        ECORE_GETOPT_VALUE_INT(verbose),
        ECORE_GETOPT_VALUE_BOOL(userArgs->quitOption),
        ECORE_GETOPT_VALUE_BOOL(userArgs->quitOption),
        ECORE_GETOPT_VALUE_BOOL(userArgs->quitOption),
        ECORE_GETOPT_VALUE_BOOL(userArgs->quitOption),
        ECORE_GETOPT_VALUE_NONE
    };

    ecore_app_args_set(argc, (const char**) argv);
    args = ecore_getopt_parse(&options, values, argc, argv);

    themePath = findThemePath(userArgs->theme);

    if ((userArgs->geometry.w <= 0) || (userArgs->geometry.h <= 0)) {
        userArgs->geometry.w = DEFAULT_WIDTH;
        userArgs->geometry.h = DEFAULT_HEIGHT;
    }

    return args;
}

int
main(int argc, char *argv[])
{
    const char *default_url = "http://www.google.com/";
    const char *tmp;
    const char *proxyUri;
    char path[PATH_MAX];
    int args;

    User_Arguments userArgs;

    if (!ewk_init())
        return EXIT_FAILURE;

    if (!ecore_file_init()) {
        ewk_shutdown();
        return EXIT_FAILURE;
    }

    args = parseUserArguments(argc, argv, &userArgs);
    if (args < 0)
       return quit(EINA_FALSE, "ERROR: could not parse options.\n");

    if (userArgs.quitOption)
        return quit(EINA_TRUE, NULL);

    if (!themePath)
        return quit(EINA_FALSE, "ERROR: could not find theme.\n");

    tmp = getenv("TMPDIR");
    if (!tmp)
        tmp = "/tmp";
    snprintf(path, sizeof(path), "%s/.ewebkit-%u", tmp, getuid());
    if (!ecore_file_mkpath(path))
        return quit(EINA_FALSE, "ERROR: could not create settings database directory.\n");

    userArgs.databasePath = path;

    ewk_settings_icon_database_path_set(path);
    ewk_settings_web_database_path_set(path);

    proxyUri = getenv("http_proxy");
    if (proxyUri)
        ewk_network_proxy_uri_set(proxyUri);

    if (args < argc) {
        char *url = url_from_user_input(argv[args]);
        browserCreate(url, &userArgs);
        free(url);
    } else
        browserCreate(default_url, &userArgs);

    ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT, main_signal_exit, &windows);

    ecore_main_loop_begin();

    ecore_file_shutdown();
    ewk_shutdown();

    return quit(EINA_TRUE, NULL);
}
