/*
 * 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 "../inc/gles-cube-view.h"

#include "config.h"
#include "../../common/inc/main-app.h"
#include "../../common/inc/gl-utils.h"
#include "../../common/inc/logger.h"

#include <player.h>

// Internal data structure of the gles_cube_view object
typedef struct
{
    app_data *app;
    Evas_Object *layout;
    Ecore_Timer *animation_timer;

    Elm_Object_Item *play_item;
    Elm_Object_Item *stop_item;

    color_shader shader;
    GLboolean shader_ok;
    GLboolean resize_ok;
    GLfloat angle;

    player_h player;
} gles_cube_view_data;

// Initialises allocated gles_cube_view instance
static Eina_Bool _gles_cube_view_init(gles_cube_view_data *data, app_data *app);

// Destroys and deallocates gles_cube_view object
static void _gles_cube_view_del(gles_cube_view_data *data);

// Handler for the view layout free event
static void _gles_cube_view_layout_free_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);

// Register global app. callbacks.
static void _register_app_cbs(gles_cube_view_data *view);

static Eina_Bool _gles_cube_view_timer_cb(void *data);
static void _on_pause_cb(void *user_data);
static void _on_resume_cb(void *user_data);

static void _gles_cube_view_glview_init(Evas_Object *obj);
static void _gles_cube_view_glview_del(Evas_Object *obj);
static void _gles_cube_view_glview_resize(Evas_Object *obj);
static void _gles_cube_view_glview_render(Evas_Object *obj);

static player_h _player_create();
static void _player_destroy(player_h player);
static void _player_start(player_h player);
static void _player_stop(player_h player);
static void _player_pause(player_h player);
static void _player_resume(player_h player);

#define VIEW_TITLE "Gles-cube player"

#define GLES_VIEW_PLAY "Play"
#define GLES_VIEW_STOP "Stop"

#define GLVIEW_VIEW_DATA_NAME "view_data"

#define VIEW_BG_COLOR 0.0f, 0.0f, 0.0f, 1.0f

#define ANIMATION_TIMER_INTERVAL_SEC 0.010f

#define PERSPECTIVE_FOV_DEG 40.0f
#define PERSPECTIVE_NEAR 1.0f
#define PERSPECTIVE_FAR 20.0f

static const gl_vector3 CUBE_XYZ_POSITION = {{0.0f, 0.0f, -2.5f}};
static const gl_vector3 CUBE_ROTATE_VECTOR = {{1.0f, 0.0f, 1.0f}};
#define CUBE_ROTATE_START_ANGLE_DEG -45.0f
#define CUBE_ROTATE_STEP_ANGLE_DEG -5.0f

#define CIRCLE_FULL_DEG 360.0f

static const color_vertex3d CUBE_VERTEXES[] =
{
    {{{-0.5f, -0.5f, -0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{-0.5f, -0.5f,  0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{ 0.5f, -0.5f,  0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{ 0.5f, -0.5f, -0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},

    {{{-0.5f,  0.5f, -0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{-0.5f,  0.5f,  0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{ 0.5f,  0.5f,  0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{ 0.5f,  0.5f, -0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},

    {{{-0.5f, -0.5f, -0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{-0.5f,  0.5f, -0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{ 0.5f,  0.5f, -0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{ 0.5f, -0.5f, -0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},

    {{{-0.5f, -0.5f,  0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{-0.5f,  0.5f,  0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{ 0.5f,  0.5f,  0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{ 0.5f, -0.5f,  0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},

    {{{-0.5f, -0.5f, -0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{-0.5f, -0.5f,  0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{-0.5f,  0.5f,  0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{-0.5f,  0.5f, -0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},

    {{{ 0.5f, -0.5f, -0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}},
    {{{ 0.5f, -0.5f,  0.5f}}, {{1.0f, 0.0f, 0.0f, 1.0f}}},
    {{{ 0.5f,  0.5f,  0.5f}}, {{0.0f, 1.0f, 0.0f, 1.0f}}},
    {{{ 0.5f,  0.5f, -0.5f}}, {{0.0f, 0.0f, 1.0f, 1.0f}}}
};

static const GLshort CUBE_INDICES[] =
{
     0,  2,  1,
     0,  3,  2,
     4,  5,  6,
     4,  6,  7,
     8,  9, 10,
     8, 10, 11,
    12, 15, 14,
    12, 14, 13,
    16, 17, 18,
    16, 18, 19,
    20, 23, 22,
    20, 22, 21
};

static const color_object3d CUBE_OBJECT3D =
{
        CUBE_VERTEXES, CUBE_INDICES,
        sizeof(CUBE_INDICES) / sizeof(CUBE_INDICES[0])
};

void gles_cube_view_add(app_data *app)
{
    RETM_IF(!app, "app is NULL");

    gles_cube_view_data *data = calloc(1, sizeof(gles_cube_view_data));
    RETM_IF(!data, "Failed to allocate the instance");

    data->app = app;
    Eina_Bool result = _gles_cube_view_init(data, app);
    if(result)
    {
        _register_app_cbs(data);
        data->player = _player_create();
    }
    else
    {
        ERR("Failed to create GLES Cube View");
        _gles_cube_view_del(data);
        return;
    }
}

static void _register_app_cbs(gles_cube_view_data *view)
{
    app_cbs cbs =
    {
       .pause = _on_pause_cb,
       .resume = _on_resume_cb
    };

    app_set_cbs(view->app, cbs, view);
}


static void gles_cube_view_play(void *data, Evas_Object *obj, void *event_info)
{
    RETM_IF(!data, "data is NULL");
    gles_cube_view_data *view_data = (gles_cube_view_data *)data;

    _player_start(view_data->player);
    elm_object_item_disabled_set(view_data->play_item, EINA_TRUE);
    elm_object_item_disabled_set(view_data->stop_item, EINA_FALSE);
}

static void gles_cube_view_stop(void *data, Evas_Object *obj, void *event_info)
{
    RETM_IF(!data, "data is NULL");
    gles_cube_view_data *view_data = (gles_cube_view_data *)data;

    _player_stop(view_data->player);
    elm_object_item_disabled_set(view_data->play_item, EINA_FALSE);
    elm_object_item_disabled_set(view_data->stop_item, EINA_TRUE);
}

static void gles_cube_view_add_toolbar(gles_cube_view_data *data, app_data *app, Elm_Object_Item *navi_item)
{
    Evas_Object *toolbar = elm_toolbar_add(app->navi);

    evas_object_size_hint_weight_set(toolbar, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(toolbar, EVAS_HINT_FILL, EVAS_HINT_FILL);
    evas_object_show(toolbar);

    elm_toolbar_select_mode_set(toolbar, ELM_OBJECT_SELECT_MODE_NONE);
    elm_toolbar_shrink_mode_set(toolbar, ELM_TOOLBAR_SHRINK_EXPAND);
    elm_toolbar_transverse_expanded_set(toolbar, EINA_TRUE);

    data->play_item = elm_toolbar_item_append(toolbar, NULL, GLES_VIEW_PLAY, gles_cube_view_play, data);
    data->stop_item = elm_toolbar_item_append(toolbar, NULL, GLES_VIEW_STOP, gles_cube_view_stop, data);
    elm_object_item_disabled_set(data->stop_item, EINA_TRUE);

    elm_object_item_part_content_set(navi_item, "toolbar", toolbar);
}

Eina_Bool _gles_cube_view_init(gles_cube_view_data *data, app_data *app)
{
    RETVM_IF(!app->navi, EINA_FALSE, "app->navi is NULL");

    data->angle = CUBE_ROTATE_START_ANGLE_DEG;

    Evas_Object *layout = elm_glview_add(app->navi);
    RETVM_IF(!layout, EINA_FALSE, "Failed to create elm_glview")

    data->layout = layout;
    evas_object_data_set(layout, GLVIEW_VIEW_DATA_NAME, data);
    evas_object_event_callback_add(layout, EVAS_CALLBACK_FREE, _gles_cube_view_layout_free_cb, data);

    elm_glview_mode_set(layout, ELM_GLVIEW_DEPTH);
    elm_glview_init_func_set(layout, _gles_cube_view_glview_init);
    elm_glview_del_func_set(layout, _gles_cube_view_glview_del);
    elm_glview_resize_func_set(layout, _gles_cube_view_glview_resize);
    elm_glview_render_func_set(layout, _gles_cube_view_glview_render);

    evas_object_size_hint_weight_set(layout, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_show(layout);

    Elm_Object_Item *navi_item = elm_naviframe_item_push(app->navi,VIEW_TITLE, NULL, NULL, layout, NULL);
    RETVM_IF(!navi_item, EINA_FALSE, "Failed to push naviframe item");

    gles_cube_view_add_toolbar(data, app, navi_item);

    data->animation_timer = ecore_timer_add(ANIMATION_TIMER_INTERVAL_SEC, _gles_cube_view_timer_cb, data);

    return EINA_TRUE;
}

void _gles_cube_view_del(gles_cube_view_data *data)
{
    if (data->animation_timer)
    {
        ecore_timer_del(data->animation_timer);
    }

    // Reset app. callbcks:
    app_reset_cbs(data->app);

    // Destroy audio player:
    _player_destroy(data->player);

    // Deleting layout (do not need NULL check)
    evas_object_del(data->layout);

    // Deallocation data structure
    free(data);
}

void _gles_cube_view_layout_free_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    RETM_IF(!data, "data is NULL");
    gles_cube_view_data *view_data = data;

    view_data->layout = NULL; // NULL the layout so we do not delete it twice in _gles_cube_view_del()
    _gles_cube_view_del(view_data); // Delete self when layout is deleted by parent object
}

Eina_Bool _gles_cube_view_timer_cb(void *data)
{
    RETVM_IF(!data, EINA_TRUE, "data is NULL");
    gles_cube_view_data *view_data = data;

    view_data->angle += CUBE_ROTATE_STEP_ANGLE_DEG;
    if (view_data->angle < 0.0f)
    {
        while (view_data->angle <= -CIRCLE_FULL_DEG)
        {
            view_data->angle += CIRCLE_FULL_DEG;
        }
    }
    else
    {
        while (view_data->angle >= CIRCLE_FULL_DEG)
        {
            view_data->angle -= CIRCLE_FULL_DEG;
        }
    }

    elm_glview_changed_set(view_data->layout);

    return EINA_TRUE;
}

static void _on_pause_cb(void *user_data)
{
    gles_cube_view_data *view = user_data;
    ecore_timer_freeze(view->animation_timer);
    _player_pause(view->player);
}

static void _on_resume_cb(void *user_data)
{
    gles_cube_view_data *view = user_data;
    ecore_timer_thaw(view->animation_timer);
    _player_resume(view->player);
}

void _gles_cube_view_glview_init(Evas_Object *obj)
{
    gles_cube_view_data *view = evas_object_data_get(obj, GLVIEW_VIEW_DATA_NAME);
    RETM_IF(!view, "View data is NULL");
    Evas_GL_API *api = elm_glview_gl_api_get(obj);
    RETM_IF(!api, "GL API is NULL");

    view->shader_ok = color_shader_init(&view->shader, api);
    RETM_IF(!view->shader_ok, "Shader initialisation failed");

    api->glClearColor(VIEW_BG_COLOR);
    api->glEnable(GL_DEPTH_TEST);
}

void _gles_cube_view_glview_del(Evas_Object *obj)
{
    gles_cube_view_data *view = evas_object_data_get(obj, GLVIEW_VIEW_DATA_NAME);
    RETM_IF(!view, "View data is NULL");
    Evas_GL_API *api = elm_glview_gl_api_get(obj);
    RETM_IF(!api, "GL API is NULL");

    color_shader_finalize(&view->shader, api);
}

void _gles_cube_view_glview_resize(Evas_Object *obj)
{
    gles_cube_view_data *view = evas_object_data_get(obj, GLVIEW_VIEW_DATA_NAME);
    RETM_IF(!view, "View data is NULL");
    RETM_IF(!view->shader_ok, "Shader is not initialised");
    Evas_GL_API *api = elm_glview_gl_api_get(obj);
    RETM_IF(!api, "GL API is NULL");

    Evas_Coord width = 0, height = 0;
    elm_glview_size_get(obj, &width, &height);
    RETM_IF((width == 0) || (height == 0), "Zero size.");

    api->glViewport(0, 0, width, height);

    gl_matrix4 matrix;
    gl_matrix4_load_perspective(&matrix, PERSPECTIVE_FOV_DEG,
            (GLfloat)width / (GLfloat)height,
            PERSPECTIVE_NEAR, PERSPECTIVE_FAR);

    color_shader_activate(&view->shader, api);
    color_shader_load_p(&view->shader, api, &matrix);
    color_shader_deactivate(&view->shader, api);

    view->resize_ok = GL_TRUE;
}

void _gles_cube_view_glview_render(Evas_Object *obj)
{
    gles_cube_view_data *view = evas_object_data_get(obj, GLVIEW_VIEW_DATA_NAME);
    RETM_IF(!view, "View data is NULL");
    RETM_IF(!view->resize_ok, "Viewport is not ready");
    Evas_GL_API *api = elm_glview_gl_api_get(obj);
    RETM_IF(!api, "GL API is NULL");

    gl_matrix4 matrix;
    gl_matrix4_load_identity(&matrix);
    gl_matrix4_translate(&matrix, CUBE_XYZ_POSITION);
    gl_matrix4_rotate(&matrix, view->angle, CUBE_ROTATE_VECTOR);

    api->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    color_shader_activate(&view->shader, api);
    color_shader_load_mv(&view->shader, api, &matrix);
    color_object3d_draw(&CUBE_OBJECT3D, api);
    color_shader_deactivate(&view->shader, api);
}

static player_h _player_create()
{
    player_h player;

    if(player_create(&player) == PLAYER_ERROR_NONE)
    {
        player_set_uri(player, get_resource_path(SAMPLEWAV));
        player_set_sound_type(player, SOUND_TYPE_MEDIA);
        player_set_volume(player, 1.0, 1.0);
        player_set_looping(player, true);
        player_prepare(player);
    }

    return player;
}

static void _player_destroy(player_h player)
{
    if(player)
    {
        _player_stop(player);
        player_unprepare(player);
        player_destroy(player);
    }
}

static void _player_start(player_h player)
{
    if(player)
    {
        player_state_e state;
        player_get_state(player, &state);

        if(state != PLAYER_STATE_PLAYING)
        {
            player_start(player);
        }
    }
}

static void _player_stop(player_h player)
{
    if(player)
    {
        player_state_e state;
        player_get_state(player , &state);

        if(state == PLAYER_STATE_PLAYING || state == PLAYER_STATE_PAUSED)
        {
            player_stop(player);
        }
    }
}

static void _player_pause(player_h player)
{
    if(player)
    {
        player_state_e state;
        player_get_state(player, &state);

        if(state == PLAYER_STATE_PLAYING)
        {
            player_pause(player);
        }
    }
}

static void _player_resume(player_h player)
{
    if(player)
    {
        player_state_e state;
        player_get_state(player, &state);

        if(state == PLAYER_STATE_PAUSED)
        {
            player_start(player);
        }
    }
}
