/*
 * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "config.h"
#include "../inc/image-common.h"
#include "../inc/image-viewer-view.h"
#include "../../common/inc/ui-utils.h"
#include "../../common/inc/main-app.h"
#include "../inc/image-sample-utils.h"
#include <image_util.h>
#include <Elementary.h>


// *_X_POS, *_Y_POS, *_WIDTH, *_HEIGHT are virtual (relative) coords of grid widget
#define MAX_WIDTH 100
#define MAX_HEIGHT 100
#define IMAGE_X_POS 0
#define IMAGE_Y_POS 0
#define IMAGE_WIDTH MAX_WIDTH
#define IMAGE_HEIGHT 40
#define SLIDER_X_POS 0
#define SLIDER_Y_POS (IMAGE_Y_POS + IMAGE_HEIGHT)
#define SLIDER_WIDTH MAX_WIDTH
#define SLIDER_HEIGHT 15
#define TEXT_X_POS 0
#define TEXT_Y_POS (SLIDER_Y_POS + SLIDER_HEIGHT)
#define TEXT_WIDTH MAX_WIDTH
#define TEXT_HEIGHT (MAX_HEIGHT - TEXT_Y_POS)

#define SLIDER_MIN 1.0
#define SLIDER_MAX 100.0
#define DEFAULT_SCALE 50.0

#define SCALE_FACTOR_STRING_LEN 120
#define SCALE_FACTOR_FITTED_STRING "Scaled to: %d x %d"
#define SCALE_FACTOR_NOT_FITTED_STRING "Decoded region: (%d, %d), %d x %d"

#define TEXT_MAX_LEN 500
#define TEXT_TEMPLATE "%s%s<br/>Image Dimension: %u x %u<br/>%s<br/>Format:%s<br/>PixelFormat:%s<br/>"\
    "Time to decode: %llums<br/>Time to resize: %llums<br/>Result:E_SUCCESS"

#define COLOSPACE_NAME_LEN 6
#define DEFAULT_COLORSPACE_ID 0

enum
{
    TOOLBAR_ITEM_INDEX_PIXFMT = 0,
    TOOLBAR_ITEM_INDEX_PREV,
    TOOLBAR_ITEM_INDEX_NEXT,
    TOOLBAR_ITEM_INDEX_COUNT_MAX
} TOOLBAR_ITEM_INDEXS;

struct _scale_factor_params
{
    bool fitted;
    Evas_Coord_Size scaled_size;
    Evas_Coord_Rectangle decoded_region;
};
typedef struct _scale_factor_params scale_factor_params;

typedef struct _colorspace_config
{
    image_util_colorspace_e format;
    const char name[COLOSPACE_NAME_LEN];
} colorspace_config;

struct _image_viewer_view
{
    Elm_Object_Item *frame_item;
    Evas_Object *navi;
    Evas_Object *layout;
    Evas_Object *grid;
    Evas_Object *image_scroller;
    Evas_Object *image;
    Evas_Object *slider;
    Evas_Object *text_widget;
    Evas_Object *toolbar;
    Elm_Object_Item* toolbar_items[TOOLBAR_ITEM_INDEX_COUNT_MAX];
    unsigned int image_id;
    app_data *app;
    Evas_Coord_Size image_original_size;
    scale_factor_params scale_factor;
    unsigned int colorspace_id;
    long long image_open_time;
    long long decode_time;
    long long resize_time;
};

static const int toolbar_item_ids[TOOLBAR_ITEM_INDEX_COUNT_MAX] = {TOOLBAR_ITEM_INDEX_PIXFMT, TOOLBAR_ITEM_INDEX_PREV, TOOLBAR_ITEM_INDEX_NEXT};

static const colorspace_config colorspace_configs[] =
{
    {IMAGE_UTIL_COLORSPACE_BGRA8888, "RGB32"},
    {IMAGE_UTIL_COLORSPACE_RGB565, "RGB16"}
};
#define COLORSPACES_COUNT sizeof(colorspace_configs) / sizeof(*colorspace_configs)

static const char *toolbar_item_names[TOOLBAR_ITEM_INDEX_COUNT_MAX] = { "RGB16", "Prev", "Next" };
static const char *image_names[] = { "dog_1024_960.gif" , "giraffe_800_1179.jpg", "home_640_432.png",
                                     "pebble_689_517.jpg", "plane_1024_768.png", "squee.gif", "zebra_1024_680.bmp"};
#define IMAGES_COUNT sizeof(image_names) / sizeof(*image_names)

static const char *title_view = "Image viewer";


static void _push(image_viewer_view *this);
static void _toolbar_button_clicked_cb(void *data, Evas_Object *obj, void *event_info);
static void _slider_delay_changed_cb(void *data, Evas_Object *obj, void *event_info);
static bool _create_layout_elements(image_viewer_view *view);
static Evas_Object * _create_text_widget(Evas_Object *parent);
static Evas_Object *_create_toolbar(image_viewer_view *this);
static void _change_image(image_viewer_view *view, bool to_next);
static void _change_colorspace(image_viewer_view *view);
static void _on_image_changed(image_viewer_view *view);
static void _on_image_scroller_resize(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _update_view_controls(image_viewer_view *view);
static void _update_title(image_viewer_view *view);
static void _update_text_widget(image_viewer_view *view);
static void _update_toolbar_buttons(image_viewer_view *view);
static void _update_scale(image_viewer_view *view);
static void _update_scale_factor_params(image_viewer_view *view);
static bool _process_colorspace_convert(image_viewer_view *view);

image_viewer_view *image_viewer_view_add(app_data *app, Evas_Object *navi)
{
    image_viewer_view *this = calloc(1, sizeof(image_viewer_view));

    if(this)
    {
        this->navi = navi;
        this->app = app;
        this->layout = ui_utils_layout_add(navi, ic_main_layout_del_cb, this);

        if (NULL == this->layout)
        {
            free(this);
            return NULL;
        }

        if (!_create_layout_elements(this))
        {
            evas_object_del(this->layout);
            return NULL;
        }

        _push(this);
    }

    return this;
}

static void _push(image_viewer_view *this)
{
    Elm_Object_Item *item = elm_naviframe_item_push(this->navi, NULL, NULL, NULL, this->layout, NULL);
    elm_object_item_part_content_set(elm_naviframe_top_item_get(this->navi), "toolbar", this->toolbar);
    this->frame_item = item;
    _update_view_controls(this);
}

static Evas_Object *_create_toolbar(image_viewer_view *this)
{
    RETVM_IF(NULL == this, NULL, "create of toolbar failed");

    Evas_Object *obj = elm_toolbar_add(this->navi);
    RETVM_IF(NULL == obj, NULL, "create of toolbar failed");

    elm_object_style_set(obj, "default");
    elm_toolbar_shrink_mode_set(obj, ELM_TOOLBAR_SHRINK_EXPAND);
    elm_toolbar_transverse_expanded_set(obj, EINA_TRUE);
    elm_toolbar_select_mode_set(obj, ELM_OBJECT_SELECT_MODE_NONE);
    evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);

    unsigned int i;
    for (i = 0; i < TOOLBAR_ITEM_INDEX_COUNT_MAX; ++i)
    {
        this->toolbar_items[i] = elm_toolbar_item_append(obj, NULL, toolbar_item_names[i], _toolbar_button_clicked_cb, this);
        evas_object_data_set(elm_toolbar_item_object_get(this->toolbar_items[i]), TOOLBAR_BUTTON_DATA_KEY, (void*)(&toolbar_item_ids[i]));
    }

    return obj;
}


static bool _create_layout_elements(image_viewer_view *view)
{
    RETVM_IF(NULL == view, false, "create of layout elements failed");
    Evas_Object *grid = ic_create_grid(view->layout, MAX_WIDTH, MAX_HEIGHT);
    RETVM_IF(NULL == grid, false, "create of layout elements failed");
    view->grid = grid;

    Evas_Object *slider = ic_create_slider(view->grid, SLIDER_MIN, SLIDER_MAX);
    RETVM_IF(NULL == slider, false, "create of layout elements failed");
    view->slider = slider;
    elm_slider_value_set(slider, DEFAULT_SCALE);
    evas_object_smart_callback_add(slider, "delay,changed", _slider_delay_changed_cb, view);
    elm_grid_pack(grid, slider, SLIDER_X_POS, SLIDER_Y_POS, SLIDER_WIDTH, SLIDER_HEIGHT);

    Evas_Object *image_scroller = ic_create_scroller(grid);
    RETVM_IF(NULL == image_scroller, false, "create of layout elements failed");
    view->image_scroller = image_scroller;
    evas_object_event_callback_add(image_scroller, EVAS_CALLBACK_RESIZE, _on_image_scroller_resize, view);
    elm_grid_pack(grid, image_scroller, IMAGE_X_POS, IMAGE_Y_POS, IMAGE_WIDTH, IMAGE_HEIGHT);

    long long time_begin = get_ticks();
    Evas_Object *image = ic_create_image(image_scroller, image_names[0]);
    RETVM_IF(NULL == image, false, "create of layout elements failed");
    view->image = image;
    view->image_open_time = get_ticks() - time_begin;
    elm_object_content_set(image_scroller, image);

    Evas_Object *text_widget = _create_text_widget(grid);
    RETVM_IF(NULL == text_widget, false, "create of layout elements failed");
    view->text_widget = text_widget;
    elm_grid_pack(grid, text_widget, TEXT_X_POS, TEXT_Y_POS, TEXT_WIDTH, TEXT_HEIGHT);

    Evas_Object *toolbar = _create_toolbar(view);
    RETVM_IF(NULL == toolbar, false, "create of layout elements failed");
    view->toolbar = toolbar;

    return true;
}

static Evas_Object *_create_text_widget(Evas_Object *parent)
{
    Evas_Object *entry = elm_entry_add(parent);
    if (entry)
    {
        elm_entry_editable_set(entry, EINA_FALSE);
        elm_entry_scrollable_set(entry, EINA_TRUE);
        elm_entry_line_wrap_set(entry, ELM_WRAP_WORD);
        evas_object_show(entry);
    }
    return entry;
}

static void _toolbar_button_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
    RETM_IF(NULL == data, "data is NULL");
    RETM_IF(NULL == event_info, "event info is NULL");
    image_viewer_view *view = (image_viewer_view*)data;

    void *toolbar_item_data = evas_object_data_get(elm_toolbar_item_object_get(event_info), TOOLBAR_BUTTON_DATA_KEY);
    RETM_IF(NULL == toolbar_item_data, "Toolbar item data is NULL");
    int i = *((int*)toolbar_item_data);
    switch (i)
    {
        case TOOLBAR_ITEM_INDEX_PIXFMT:
            _change_colorspace(view);
            break;
        case TOOLBAR_ITEM_INDEX_PREV:
            _change_image(view, false);
            break;
        case TOOLBAR_ITEM_INDEX_NEXT:
            _change_image(view, true);
            break;
        default:
            ERR("Not supported button");
            break;
    }
}

static void _slider_delay_changed_cb(void *data, Evas_Object *obj, void *event_info)
{
    RETM_IF(NULL == data, "data is NULL");
    image_viewer_view *view = data;
    _update_scale(view);
    _update_text_widget(view);
}

static void _change_image(image_viewer_view *view, bool to_next)
{
    RETM_IF(NULL == view, "view is NULL");
    unsigned int image_id = view->image_id;
    RETM_IF(to_next && image_id + 1 >= IMAGES_COUNT, "Out of image container");
    RETM_IF(!to_next && image_id == 0, "Out of image container");

    if (to_next)
    {
        ++image_id;
    }
    else
    {
        --image_id;
    }

    long long time_begin = get_ticks();
    if (!ic_load_image(view->image, image_names[image_id]))
    {
        ERR("error with elm_image_file_set, image %s", image_names[image_id]);
    }
    view->image_open_time = get_ticks() - time_begin;
    view->image_id = image_id;
    _on_image_changed(view);
    _update_view_controls(view);
}

static void _change_colorspace(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    if (view->colorspace_id + 1 < COLORSPACES_COUNT)
    {
        ++(view->colorspace_id);
    }
    else
    {
        view->colorspace_id = 0;
    }

    if (DEFAULT_COLORSPACE_ID == view->colorspace_id)
    {
        view->decode_time = view->image_open_time;
    }
    else
    {
        long long time_begin = get_ticks();
        _process_colorspace_convert(view);
        view->decode_time = get_ticks() - time_begin;
    }
    _update_toolbar_buttons(view);
    _update_text_widget(view);
}

static void _on_image_changed(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    elm_slider_value_set(view->slider, DEFAULT_SCALE);
    Evas_Object * evas_image = elm_image_object_get(view->image);
    int w = 0, h = 0;
    evas_object_image_size_get(evas_image, &w, &h);
    view->image_original_size.w = w;
    view->image_original_size.h = h;
    view->colorspace_id = 0;
    view->decode_time = view->image_open_time;
}

static void _on_image_scroller_resize(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    RETM_IF(NULL == data, "data is NULL");
    image_viewer_view *view = data;
    _on_image_changed(view);
    _update_scale(view);
    _update_text_widget(view);
}

static void _update_view_controls(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    _update_title(view);
    _update_toolbar_buttons(view);
    _update_scale(view);
    _update_text_widget(view);
}

static void _update_title(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    char buf[TITLE_MAX_LEN] = {'\0'};
    snprintf(buf, sizeof(buf), "%s (%d/%d)", title_view, view->image_id + 1, IMAGES_COUNT);
    elm_object_item_text_set(view->frame_item, buf);
}

static void _update_toolbar_buttons(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    Eina_Bool prev_button_dis = view->image_id == 0;
    Eina_Bool next_button_dis = view->image_id + 1 >= IMAGES_COUNT;
    elm_object_item_disabled_set(view->toolbar_items[TOOLBAR_ITEM_INDEX_PREV], prev_button_dis);
    elm_object_item_disabled_set(view->toolbar_items[TOOLBAR_ITEM_INDEX_NEXT], next_button_dis);
    unsigned int next_colorspace_id = (view->colorspace_id + 1 < COLORSPACES_COUNT ? view->colorspace_id + 1 : 0);
    elm_object_item_text_set(view->toolbar_items[TOOLBAR_ITEM_INDEX_PIXFMT],
               colorspace_configs[next_colorspace_id].name);
}

static void _update_text_widget(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    const char *image_name = image_names[view->image_id];
    const char *image_format = filename_extenstion_get(image_names[view->image_id]);
    const char *pixel_format = colorspace_configs[view->colorspace_id].name;

    char scale_factor_string[SCALE_FACTOR_STRING_LEN] = {'\0'};
    if (view->scale_factor.fitted)
    {
        snprintf(scale_factor_string, SCALE_FACTOR_STRING_LEN, SCALE_FACTOR_FITTED_STRING,
                 view->scale_factor.scaled_size.w, view->scale_factor.scaled_size.h);
    }
    else
    {
        snprintf(scale_factor_string, SCALE_FACTOR_STRING_LEN, SCALE_FACTOR_NOT_FITTED_STRING,
                 view->scale_factor.decoded_region.x , view->scale_factor.decoded_region.y,
                 view->scale_factor.decoded_region.w , view->scale_factor.decoded_region.h);
    }

    char buf[TEXT_MAX_LEN] = {'\0'};
    snprintf(buf, TEXT_MAX_LEN, TEXT_TEMPLATE, LABEL_REL_PATH, image_name,
            view->image_original_size.w, view->image_original_size.h,
            scale_factor_string, image_format, pixel_format,
            view->decode_time, view->resize_time);

    elm_entry_entry_set(view->text_widget, buf);
    elm_scroller_region_show(view->text_widget, 0, 0, 0, 0);
}

static void _update_scale(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    Evas_Object *evas_image = elm_image_object_get(view->image);
    double scale = elm_slider_value_get(view->slider) / DEFAULT_SCALE;

    Evas_Coord_Rectangle new_geometry = {};
    bool result = ic_calc_image_new_geometry(evas_image, view->image_scroller, scale, &new_geometry);
    RETM_IF(!result, "error: on image new geometry calculation");

    long long time_begin = get_ticks();
    evas_object_image_fill_set(evas_image, 0, 0, new_geometry.w, new_geometry.h);
    evas_object_resize(evas_image, new_geometry.w, new_geometry.h);
    evas_object_move(evas_image, new_geometry.x, new_geometry.y);
    view->resize_time = get_ticks() - time_begin;

    _update_scale_factor_params(view);
}


static void _update_scale_factor_params(image_viewer_view *view)
{
    RETM_IF(NULL == view, "view is NULL");

    Evas_Coord_Rectangle obj_geometry = ic_get_geometry(elm_image_object_get(view->image));
    Evas_Coord_Rectangle container_geometry = ic_get_geometry(view->image_scroller);
    scale_factor_params scale_factor;
    scale_factor.scaled_size.w = obj_geometry.w;
    scale_factor.scaled_size.h = obj_geometry.h;

    bool horizontal_fit = (obj_geometry.x >= container_geometry.x)
            && (obj_geometry.x + obj_geometry.w <= container_geometry.x + container_geometry.w);
    bool vertical_fit = (obj_geometry.y >= container_geometry.y)
            && (obj_geometry.y + obj_geometry.h <= container_geometry.y + container_geometry.h);
    scale_factor.fitted = (horizontal_fit && vertical_fit);

    double scale = elm_slider_value_get(view->slider) / DEFAULT_SCALE;
    RETM_IF(scale <= 0.0, "incorrect scale value");

    if (horizontal_fit)
    {
        scale_factor.decoded_region.x = 0;
        scale_factor.decoded_region.w = view->image_original_size.w;
    }
    else
    {
        scale_factor.decoded_region.x = (container_geometry.x - obj_geometry.x) / scale;
        scale_factor.decoded_region.w = view->image_original_size.w - 2 * scale_factor.decoded_region.x;
    }

    if (vertical_fit)
    {
        scale_factor.decoded_region.y = 0;
        scale_factor.decoded_region.h = view->image_original_size.h;
    }
    else
    {
        scale_factor.decoded_region.y = (container_geometry.y - obj_geometry.y) / scale;
        scale_factor.decoded_region.h = view->image_original_size.h - 2 * scale_factor.decoded_region.y;
    }

    view->scale_factor = scale_factor;
}

static bool _process_colorspace_convert(image_viewer_view *view)
{
    RETVM_IF(NULL == view, false, "view is NULL");
    RETVM_IF(NULL == view->image, false, "image is NULL");
    Evas_Object* evas_image = elm_image_object_get(view->image);
    RETVM_IF(NULL == evas_image, false, "evas image is NULL");

    int w = 0, h = 0;
    evas_object_image_size_get(evas_image, &w, &h);
    RETVM_IF(0 == w * h, false, "wrong size of image: %d x %d", w, h);

    void* src_buff = evas_object_image_data_get(evas_image, EINA_FALSE);
    RETVM_IF(NULL == src_buff, false, "image source buffer is NULL");

    image_util_colorspace_e src_colorspace = IMAGE_UTIL_COLORSPACE_BGRA8888;
    image_util_colorspace_e dest_colorspace = colorspace_configs[view->colorspace_id].format;
    int error = IMAGE_UTIL_ERROR_NONE;
    unsigned int dest_size = 0;
    error = image_util_calculate_buffer_size(w , h, dest_colorspace, &dest_size);
    RETVM_IF(IMAGE_UTIL_ERROR_NONE != error, false, "image_util_calculate_buffer_size error");

    unsigned char* dest_buff = malloc(dest_size);
    RETVM_IF(NULL == dest_buff, false, "malloc destination buffer error %d", dest_size);

    error = image_sample_util_convert_colorspace(dest_buff, dest_colorspace,
                                          src_buff, w, h, src_colorspace);
    free(dest_buff);
    dest_buff = NULL;

    RETVM_IF(IMAGE_UTIL_ERROR_NONE != error, false, "image_sample_util_convert_colorspace error");
    return true;
}
