/*
 * 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/color-converter-view.h"
#include "../inc/image-common.h"
#include "../inc/image-sample-utils.h"
#include "../../common/inc/ui-utils.h"
#include "../../common/inc/main-app.h"

#include <Elementary.h>
#include <image_util.h>

// *_X_POS, *_Y_POS, *_WIDTH, *_HEIGHT are virtual (relative) coords of grid widget
#define MAX_WIDTH 100
#define MAX_HEIGHT 100

#define IMAGES_BORDER 1
#define IMAGES_X_POS 0
#define IMAGES_Y_POS 0
#define IMAGES_WIDTH MAX_WIDTH
#define IMAGES_HEIGHT 30
#define IMAGE_SOURCE_X_POS IMAGES_X_POS
#define IMAGE_SOURCE_Y_POS IMAGES_Y_POS
#define IMAGE_SOURCE_WIDTH IMAGES_WIDTH
#define IMAGE_SOURCE_HEIGHT IMAGES_HEIGHT
#define IMAGE_CONVERTER_X_POS IMAGES_X_POS
#define IMAGE_CONVERTER_Y_POS (IMAGE_SOURCE_Y_POS + IMAGE_SOURCE_HEIGHT + IMAGES_BORDER)
#define IMAGE_CONVERTER_WIDTH IMAGES_WIDTH
#define IMAGE_CONVERTER_HEIGHT IMAGES_HEIGHT

#define LABEL_X_POS 0
#define LABEL_Y_POS (IMAGE_CONVERTER_Y_POS + IMAGE_CONVERTER_HEIGHT + IMAGES_BORDER)
#define LABEL_WIDTH MAX_WIDTH
#define LABEL_HEIGHT (MAX_HEIGHT - LABEL_Y_POS)

#define IMAGES_REL_FOLDER "images"
#define LABEL_TEXT_TEMPLATE "%s%s<br/>Conversion: %s => %s<br/>Time taken: %llums<br/>"\
    "Result:E_SUCCESS"

#define DEFAULT_PARAMETER 0
#define SOURCE_COLORSPACE IMAGE_UTIL_COLORSPACE_BGRA8888

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

struct _color_converter_view
{
    Elm_Object_Item *frame_item;
    Evas_Object *navi;
    Evas_Object *layout;
    Evas_Object *grid;
    Evas_Object *image_source;
    Evas_Object *image_converted;
    Evas_Object *label;
    Evas_Object *toolbar;
    app_data *app;
    Elm_Object_Item* toolbar_items[TOOLBAR_ITEM_INDEX_COUNT];
    unsigned int image_id;
    unsigned int color_id_to;
    long long decode_time;
    unsigned char* decoded_buffer;
};

typedef struct _image_config
{
    const char *name;
    unsigned int color_id_to;
    unsigned int width;
    unsigned int height;
    unsigned int color_id_from;
} image_config;

static const int toolbar_item_ids[TOOLBAR_ITEM_INDEX_COUNT] =
{
    TOOLBAR_ITEM_INDEX_PIXFMT,
    TOOLBAR_ITEM_INDEX_PREV,
    TOOLBAR_ITEM_INDEX_NEXT
};
static const char *toolbar_item_names[TOOLBAR_ITEM_INDEX_COUNT] =
{
    "RGB32",
    "Prev",
    "Next"
};
static const char *title_view = "Color converter";

static const image_config image_configs[] =
{
    {"dog_1024_960.gif", 2, DEFAULT_PARAMETER, DEFAULT_PARAMETER, DEFAULT_PARAMETER},
    {"home_640_432.png", 2, DEFAULT_PARAMETER, DEFAULT_PARAMETER, DEFAULT_PARAMETER},
    {"YUV/foreman_cif.yuv", 0, 352, 288, 2}
};
#define IMAGES_COUNT sizeof(image_configs) / sizeof(*image_configs)

static const image_util_colorspace_e colorspaces[] =
{
    IMAGE_UTIL_COLORSPACE_RGB565,
    IMAGE_UTIL_COLORSPACE_BGRA8888,
    IMAGE_UTIL_COLORSPACE_I420
};
#define COLORSPACES_COUNT sizeof(colorspaces) / sizeof(*colorspaces)

static void _push(color_converter_view *view);
static void _toolbar_button_clicked_cb(void *data, Evas_Object *obj, void *event_info);
static bool _create_layout_elements(color_converter_view *view);
static void _main_layout_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static Evas_Object *_create_toolbar(color_converter_view *view);
static void _change_image(color_converter_view *view, bool to_next);
static void _on_image_changed(color_converter_view *view);
static void _change_colorspace(color_converter_view *view);
static bool _process_colorspace_convert(color_converter_view *view);
static bool _load_images(Evas_Object *image, const int image_id);
static void _update_view_controls(color_converter_view *view);
static void _update_title(color_converter_view *view);
static void _update_toolbar_buttons(color_converter_view *view);
static void _update_label(color_converter_view *view);
static const char *_colorspace_to_str(image_util_colorspace_e colorspace);

color_converter_view *color_converter_view_add(app_data *app, Evas_Object *navi)
{
    color_converter_view *view = calloc(1, sizeof(color_converter_view));

    if(view)
    {
        view->navi = navi;
        view->app = app;
        view->layout = ui_utils_layout_add(navi, _main_layout_del_cb, view);

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

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

        _push(view);
    }

    return view;
}


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

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

    Evas_Object *obj = elm_toolbar_add(view->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; ++i)
    {
        view->toolbar_items[i] = elm_toolbar_item_append(obj, NULL, toolbar_item_names[i], _toolbar_button_clicked_cb, view);
        evas_object_data_set(elm_toolbar_item_object_get(view->toolbar_items[i]), TOOLBAR_BUTTON_DATA_KEY, (void*)(&toolbar_item_ids[i]));
    }

    return obj;
}

static bool _create_layout_elements(color_converter_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 *image_source = ic_create_image(grid, image_configs[0].name);
    RETVM_IF(NULL == image_source, false, "create of layout elements failed");
    view->image_source = image_source;
    elm_grid_pack(grid, image_source, IMAGE_SOURCE_X_POS, IMAGE_SOURCE_Y_POS, IMAGE_SOURCE_WIDTH, IMAGE_SOURCE_HEIGHT);

    Evas_Object *image_converted = ic_create_image(grid, image_configs[0].name);
    RETVM_IF(NULL == image_converted, false, "create of layout elements failed");
    view->image_converted = image_converted;
    elm_grid_pack(grid, image_converted, IMAGE_CONVERTER_X_POS, IMAGE_CONVERTER_Y_POS, IMAGE_CONVERTER_WIDTH, IMAGE_CONVERTER_HEIGHT);

    Evas_Object *label = ic_create_label(grid);
    RETVM_IF(NULL == label, false, "create of layout elements failed");
    view->label = label;
    elm_grid_pack(grid, label, LABEL_X_POS, LABEL_Y_POS, LABEL_WIDTH, LABEL_HEIGHT);

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

    return true;
}

static void _main_layout_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    color_converter_view *view = data;
    if (view)
    {
        free(view->decoded_buffer);
        free(view);
    }
}

static void _toolbar_button_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
    RETM_IF(NULL == data, "error: data passed as NULL");
    RETM_IF(NULL == event_info, "event info is NULL");
    color_converter_view *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 _change_image(color_converter_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;
    }

    if (!_load_images(view->image_source, image_id)
            || _load_images(view->image_converted, image_id))
    {
        ERR("_load_image failed");
    }
    view->image_id = image_id;
    _on_image_changed(view);
    _update_view_controls(view);
}

static void _on_image_changed(color_converter_view *view)
{
    view->color_id_to = image_configs[view->image_id].color_id_to;
    _process_colorspace_convert(view);
}

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

    _process_colorspace_convert(view);
    _update_toolbar_buttons(view);
    _update_label(view);
}

static bool _process_colorspace_convert(color_converter_view *view)
{
    long long time_begin = get_ticks();
    RETVM_IF(NULL == view, false, "view is NULL");
    RETVM_IF(NULL == view->image_source, false, "image is NULL");
    Evas_Object* evas_image = elm_image_object_get(view->image_source);
    RETVM_IF(NULL == evas_image, false, "evas image is NULL");

    free(view->decoded_buffer);
    view->decoded_buffer = 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 = SOURCE_COLORSPACE;
    image_util_colorspace_e dest_colorspace = colorspaces[view->color_id_to];
    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");

    view->decoded_buffer = malloc(dest_size);
    RETVM_IF(NULL == view->decoded_buffer, false, "malloc destination buffer error %d", dest_size);

    error = image_sample_util_convert_colorspace(view->decoded_buffer, dest_colorspace,
                                          src_buff, w, h, src_colorspace);
    RETVM_IF(IMAGE_UTIL_ERROR_NONE != error, false, "image_sample_util_convert_colorspace error");
    view->decode_time = get_ticks() - time_begin;
    return true;
}

static bool _load_images(Evas_Object *image, const int image_id)
{
    unsigned int colorspace_id = image_configs[image_id].color_id_from;
    return image_sample_util_load_image(image, image_configs[image_id].name, colorspaces[colorspace_id],
                                        image_configs[image_id].width, image_configs[image_id].height);
}

static void _update_view_controls(color_converter_view *view)
{
    RETM_IF(NULL == view, "view is NULL");
    _update_title(view);
    _update_toolbar_buttons(view);
    _update_label(view);
}

static void _update_title(color_converter_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(color_converter_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->color_id_to + 1 < COLORSPACES_COUNT ? view->color_id_to + 1 : 0);
    elm_object_item_text_set(view->toolbar_items[TOOLBAR_ITEM_INDEX_PIXFMT],
               _colorspace_to_str(colorspaces[next_colorspace_id]));
}

static void _update_label(color_converter_view *view)
{
    RETM_IF(NULL == view, "view is NULL");

    const char *image_name = image_configs[view->image_id].name;
    char buff[LABEL_MAX_LEN] = {'\0'};
    unsigned int color_id_to = view->color_id_to;
    const char *image_source_format = _colorspace_to_str(SOURCE_COLORSPACE);
    const char *image_dest_format = _colorspace_to_str(colorspaces[color_id_to]);
    snprintf(buff, LABEL_MAX_LEN, LABEL_TEXT_TEMPLATE,
            LABEL_REL_PATH, image_name, image_source_format, image_dest_format,
            view->decode_time);
    elm_object_text_set(view->label, buff);
}

static const char *_colorspace_to_str(image_util_colorspace_e colorspace)
{
    const char *result = NULL;
    switch (colorspace)
    {
        case IMAGE_UTIL_COLORSPACE_BGRA8888:
            result = "RGB32";
            break;
        case IMAGE_UTIL_COLORSPACE_RGB565:
            result = "RGB16";
            break;
        case IMAGE_UTIL_COLORSPACE_I420:
            result = "YUV420";
            break;
        default:
            ERR("Not supported colorspace");
            break;
    }
    return result;
}
