/*
 * 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 "audio-recorder-utils.h"
#include "../inc/audio-recorder-view.h"
#include "../../player/inc/audio-player-view.h"
#include "../../common/inc/main-app.h"
#include "../../common/inc/radio-popup.h"

#include <assert.h>
#include <stdarg.h>
#include <Elementary.h>
#include <Ecore.h>
#include <recorder.h>
#include <stdio.h>
#include <string.h>
#include <efl_extension.h>

#define MAX_LABEL_LEN 256
#define MAX_TIME 20
#define TIMER_UPD_PERIOD 1.0
#define PREPROC_FILE_BUF_SIZE 512

#define STR_SETTINGS "Settings"
#define STR_STOP "Stop"
#define STR_START "Start"
#define STR_SEL_QUALITY "Select QUALITY"
#define STR_PREPROC "Audio Pre-Processing"
#define STR_CODEC "Codec"
#define STR_QUALITY "Quality"
#define STR_FORMAT "Format"
#define STR_SIZE "Size"
#define STR_TIME "Time"
#define STR_KB "KB"
#define STR_SEC "sec"
#define STR_LOW "Low"
#define STR_MEDIUM "Medium"
#define STR_HIGH "High"
#define STR_ENABLE "Enable"
#define STR_DISABLE "Disable"

struct _audio_recorder_view
{
    app_data *app;
    Elm_Object_Item *frame_item;
    Evas_Object *navi;
    Evas_Object *layout;
    Evas_Object *toolbar;
    Evas_Object *progressbar;
    Evas_Object *ctxpopup;

    audio_player_view *player_view;

    Evas_Object *res_label;
    Evas_Object *codec_label;
    Evas_Object *quality_label;
    Evas_Object *format_label;
    Evas_Object *size_label;
    Evas_Object *time_label;

    Elm_Object_Item *ctrl_item;
    Elm_Object_Item *settings_item;

    recorder_h recorder;
    recorder_audio_codec_e *codec_list;
    int codec_list_len;
    char file_path[PATH_MAX];
    recorder_audio_codec_e codec;
    recorder_file_format_e file_format;
    FILE *preproc_file;
};

static void _destroy(audio_recorder_view *view);
static void _push(audio_recorder_view *view);
static Evas_Object *_main_layout_add(audio_recorder_view *view, Evas_Object *parent);
static Evas_Object *_toolbar_add(audio_recorder_view *view, Evas_Object *parent);

static void _quality_set(audio_recorder_view *view, audio_recording_quality_e quality);
static void _codec_set(audio_recorder_view *view, recorder_audio_codec_e codec);
static void _preproc_set(audio_recorder_view *view, bool preproc);

static void _recorder_create(audio_recorder_view *view);
static void _recorder_destroy(audio_recorder_view *view);
static void _recorder_start(audio_recorder_view *view);
static void _recorder_stop(audio_recorder_view *view);
static bool _recorder_is_recording(audio_recorder_view *view);
static void _recorder_preproc_set(audio_recorder_view *view, bool preproc);
static void _recorder_apply_settings(audio_recorder_view *view);

static void _get_supported_codec_list(audio_recorder_view *view);

static Evas_Object *_label_add(Evas_Object *parent);
static void _label_text_set(Evas_Object *label, const char *text);
static void _label_res_set(audio_recorder_view *view, const char *text);
static void _label_codec_set(audio_recorder_view *view, const char *text);
static void _label_quality_set(audio_recorder_view *view, const char *text);
static void _label_format_set(audio_recorder_view *view, const char *text);
static void _label_size_set(audio_recorder_view *view, long long size);
static void _label_time_set(audio_recorder_view *view, int sec);

static void _navigate_to_audio_player_view(audio_recorder_view *view);

static void _show_ctx_poopup_menu(audio_recorder_view *view);
static void _show_quality_poopup_menu(audio_recorder_view *view);
static void _show_preproc_poopup_menu(audio_recorder_view *view);

static Evas_Object *_info_box_add(audio_recorder_view *view, Evas_Object *parent);
static Evas_Object *_image_add(Evas_Object *parent);
static Evas_Object *_progressbar_add(Evas_Object *parent);

static void _on_recorder_audio_stream_cb(void* stream, int size, audio_sample_type_e format, int channel, unsigned int timestamp, void *user_data);
static void _on_recording_status_cb(unsigned long long elapsed_time, unsigned long long file_size, void *user_data);
static void _on_recording_limit_reached_cb(recorder_recording_limit_type_e type, void *user_data);

static void _on_quality_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info);
static void _on_preproc_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info);
static void _on_settings_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info);
static void _on_ctrl_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info);
static void _on_main_layout_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _quality_popup_closed_cb(radio_popup *obj,bool result, void *user_data);
static void _preproc_popup_closed_cb(radio_popup *obj,bool result, void *user_data);

audio_recorder_view *audio_recorder_view_add(app_data *app, Evas_Object *navi)
{
    RETVM_IF(!app, NULL, "app_data is null");
    RETVM_IF(!app->win, NULL, "win is null");

    audio_recorder_view *view = calloc(1, sizeof(audio_recorder_view));

    if(view)
    {
        _recorder_create(view);
        _get_supported_codec_list(view);

        view->navi = navi;
        view->app = app;
        view->layout = _main_layout_add(view, navi);

        if(view->layout)
        {
            evas_object_event_callback_add(view->layout, EVAS_CALLBACK_DEL, _on_main_layout_del_cb, view);
            _push(view);
        }
        else
        {
            _destroy(view);
            return NULL;
        }
    }

    return view;
}

static void _navigate_to_audio_player_view(audio_recorder_view *view)
{
    view->player_view = audio_player_view_add(view->app, view->navi, view->file_path);
}

static void _recorder_create(audio_recorder_view *view)
{
    if(recorder_create_audiorecorder(&view->recorder) == RECORDER_ERROR_NONE)
    {
        recorder_set_recording_status_cb(view->recorder, _on_recording_status_cb, view);
        recorder_set_recording_limit_reached_cb(view->recorder, _on_recording_limit_reached_cb, view);

        recorder_attr_set_audio_channel(view->recorder, 1);
        recorder_attr_set_audio_device(view->recorder, RECORDER_AUDIO_DEVICE_MIC);
        recorder_attr_set_time_limit(view->recorder, MAX_TIME);

    }
}

static void _get_supported_codec_list(audio_recorder_view *view)
{
    if(!view->codec_list)
    {
        view->codec_list = audio_recorder_get_supported_encoder(view->recorder, &view->codec_list_len);
    }
}

static void _codec_set(audio_recorder_view *view, recorder_audio_codec_e codec)
{
    char file_name[NAME_MAX] = {'\0'};
    const char *codec_str = audio_recorder_codec_to_str(codec);
    view->codec = codec;
    view->file_format = audio_recorder_get_file_format_by_codec(view->recorder, codec);
    const char *file_ext = audio_recorder_file_format_to_str(view->file_format);

    char *data_path = app_get_data_path();
    snprintf(file_name, NAME_MAX, "record.%s", file_ext);
    snprintf(view->file_path, PATH_MAX, "%s%s", data_path, file_name);
    free(data_path);

    _label_res_set(view, file_name);
    _label_format_set(view, file_ext);
    _label_codec_set(view, codec_str);
}

static void _quality_set(audio_recorder_view *view, audio_recording_quality_e quality)
{
    const char *str = "";
    switch(quality)
    {
        case AQ_LOW:
            str = STR_LOW;
            break;

        case AQ_MEDIUM:
            str = STR_MEDIUM;
            break;

        case AQ_HIGH:
            str = STR_HIGH;
            break;

        default:
            assert(false);
            return;
            break;
    }

    audio_recorder_save_quality(quality);
    _label_quality_set(view, str);
}

static void _preproc_set(audio_recorder_view *view, bool preproc)
{
    audio_recorder_save_preprocessing(preproc);
}

static void _recorder_apply_settings(audio_recorder_view *view)
{
    if(view->recorder)
    {
        bool preproc = audio_recorder_read_preprocessing();
        audio_recording_quality_e quality = audio_recorder_read_quality();

        recorder_set_filename(view->recorder, view->file_path);
        recorder_set_file_format(view->recorder, view->file_format);
        recorder_set_audio_encoder(view->recorder, view->codec);

        audio_recorder_set_quality(view->recorder, quality);
        _recorder_preproc_set(view, preproc);
    }
}

static void _recorder_preproc_set(audio_recorder_view *view, bool preproc)
{
    if(preproc)
    {
        recorder_audio_codec_e codec = RECORDER_AUDIO_CODEC_DISABLE;
        recorder_get_audio_encoder(view->recorder, &codec);

        if(codec != RECORDER_AUDIO_CODEC_DISABLE)
        {
            if(view->preproc_file)
            {
                fclose(view->preproc_file);
            }

            const char *file_name = audio_recorder_get_preproc_file_name_by_codec(codec);
            if(file_name)
            {
                view->preproc_file = fopen(file_name, "r");
                assert(view->preproc_file);
            }

            recorder_set_audio_stream_cb(view->recorder, _on_recorder_audio_stream_cb, view);
        }
    }
    else
    {
        recorder_unset_audio_stream_cb(view->recorder);
    }
}

static void _recorder_destroy(audio_recorder_view *view)
{
    if(view->recorder)
    {
        recorder_cancel(view->recorder);
        recorder_unprepare(view->recorder);
        recorder_destroy(view->recorder);
        view->recorder = NULL;
    }
}

static void _recorder_start(audio_recorder_view *view)
{
    if(view->recorder)
    {
        _recorder_apply_settings(view);
        recorder_prepare(view->recorder);
        recorder_start(view->recorder);
        if(_recorder_is_recording(view))
        {
            elm_object_item_text_set(view->ctrl_item, STR_STOP);
            elm_object_item_disabled_set(view->settings_item, EINA_TRUE);
        }
    }
}

static void _recorder_stop(audio_recorder_view *view)
{
    if(view->recorder)
    {
        recorder_commit(view->recorder);
        if(!_recorder_is_recording(view))
        {
            elm_object_item_text_set(view->ctrl_item, STR_START);
            elm_object_item_disabled_set(view->settings_item, EINA_FALSE);
            recorder_unprepare(view->recorder);

            _label_size_set(view, 0);
            _label_time_set(view, 0);
            elm_progressbar_value_set(view->progressbar, 0.0);
        }
    }
}

static bool _recorder_is_recording(audio_recorder_view *view)
{
    recorder_state_e state = RECORDER_STATE_NONE;
    recorder_get_state(view->recorder, &state);
    return state == RECORDER_STATE_RECORDING;
}

static void _destroy(audio_recorder_view *view)
{
    app_reset_cbs(view->app);
    _recorder_destroy(view);

    if(view->preproc_file)
    {
        fclose(view->preproc_file);
    }

    free(view->codec_list);
    free(view);
}

static void _push(audio_recorder_view *view)
{
    view->frame_item = elm_naviframe_item_push(view->navi, "Audio recorder", NULL, NULL, view->layout, NULL);

    view->toolbar = _toolbar_add(view, view->navi);
    elm_object_item_part_content_set(view->frame_item, "toolbar", view->toolbar);
}

static Evas_Object *_main_layout_add(audio_recorder_view *view, Evas_Object *parent)
{
    Evas_Object *layout = elm_layout_add(parent);
    view->layout = layout;

    elm_layout_file_set(layout, AUDIORECORDEREDJ, "audio_recorder");

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

    Evas_Object *image = _image_add(view->layout);
    Evas_Object *info_box = _info_box_add(view, view->layout);
    view->progressbar = _progressbar_add(view->layout);

    elm_object_part_content_set(layout, "image", image);
    elm_object_part_content_set(layout, "progress_bar", view->progressbar);
    elm_object_part_content_set(layout, "text_area", info_box);

    _label_size_set(view, 0);
    _label_time_set(view, 0);

    bool preproc = audio_recorder_read_preprocessing();
    audio_recording_quality_e quality = audio_recorder_read_quality();

    recorder_audio_codec_e codec = view->codec_list_len ? view->codec_list[0] : RECORDER_AUDIO_CODEC_PCM;

    _codec_set(view, codec);
    _quality_set(view, quality);
    _preproc_set(view, preproc);

    return layout;
}

static void _show_ctx_poopup_menu(audio_recorder_view *view)
{
    if(!view->ctxpopup)
    {
        Evas_Object *popup = elm_ctxpopup_add(view->app->win->win);
        elm_ctxpopup_horizontal_set(popup, EINA_FALSE);
        eext_object_event_callback_add(popup, EEXT_CALLBACK_BACK, eext_ctxpopup_back_cb, view);

        elm_ctxpopup_item_append(popup, STR_QUALITY, NULL, _on_quality_btn_pressed_cb, view);
        elm_ctxpopup_item_append(popup, STR_PREPROC, NULL, _on_preproc_btn_pressed_cb, view);

        Evas_Coord x = 0;
        Evas_Coord y = 0;
        Evas_Coord w = 0;
        Evas_Coord h = 0;
        evas_object_geometry_get(view->toolbar, &x, &y, &w, &h);

        evas_object_move(popup, 0, y);
        view->ctxpopup = popup;
    }

    evas_object_show(view->ctxpopup);
}

static void _show_quality_poopup_menu(audio_recorder_view *view)
{
    radio_popup *popup = radio_popup_add(view->app->win->win, ELM_POPUP_ORIENT_CENTER, STR_SEL_QUALITY);
    radio_popup_callback_set(popup, NULL, _quality_popup_closed_cb, view);

    radio_popup_item_add(popup, STR_LOW, AQ_LOW);
    radio_popup_item_add(popup, STR_MEDIUM, AQ_MEDIUM);
    radio_popup_item_add(popup, STR_HIGH, AQ_HIGH);

    audio_recording_quality_e quality = audio_recorder_read_quality();
    radio_popup_select_item(popup, quality);

    radio_popup_show(popup);
}

static void _show_preproc_poopup_menu(audio_recorder_view *view)
{
    radio_popup *popup = radio_popup_add(view->app->win->win, ELM_POPUP_ORIENT_CENTER, STR_PREPROC);
    radio_popup_callback_set(popup, NULL, _preproc_popup_closed_cb, view);

    radio_popup_item_add(popup, STR_ENABLE, true);
    radio_popup_item_add(popup, STR_DISABLE, false);

    bool preproc_res = audio_recorder_read_preprocessing();
    radio_popup_select_item(popup, preproc_res);

    radio_popup_show(popup);
}

static Evas_Object *_label_add(Evas_Object *parent)
{
    Evas_Object *obj = elm_label_add(parent);
    evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(obj, 0.0, 0.0);
    evas_object_show(obj);

    return obj;
}

static void _label_text_set(Evas_Object *label, const char *text)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s", text);
    elm_object_text_set(label, buf);
}

static void _label_res_set(audio_recorder_view *view, const char *text)
{
    _label_text_set(view->res_label, text);
}

static void _label_codec_set(audio_recorder_view *view, const char *text)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %s", STR_CODEC, text);
    _label_text_set(view->codec_label, buf);
}

static void _label_quality_set(audio_recorder_view *view, const char *text)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %s", STR_QUALITY, text);
    _label_text_set(view->quality_label, buf);
}

static void _label_format_set(audio_recorder_view *view, const char *text)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %s", STR_FORMAT, text);
    _label_text_set(view->format_label, buf);
}

static void _label_size_set(audio_recorder_view *view, long long size)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %lld %s ", STR_SIZE, size, STR_KB);
    _label_text_set(view->size_label, buf);
}

static void _label_time_set(audio_recorder_view *view, int sec)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %d/%d %s ", STR_TIME, sec, MAX_TIME, STR_SEC);
    _label_text_set(view->time_label, buf);
}

static Evas_Object *_info_box_add(audio_recorder_view *view, Evas_Object *parent)
{
    Evas_Object *box = elm_box_add(parent);
    elm_box_horizontal_set(box, false);
    evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(box, 0.0, 0.0);
    evas_object_show(box);

    view->res_label = _label_add(box);
    view->codec_label = _label_add(box);
    view->quality_label = _label_add(box);
    view->format_label = _label_add(box);
    view->size_label = _label_add(box);
    view->time_label = _label_add(box);

    elm_box_pack_end(box, view->res_label);
    elm_box_pack_end(box, view->codec_label);
    elm_box_pack_end(box, view->quality_label);
    elm_box_pack_end(box, view->format_label);
    elm_box_pack_end(box, view->size_label);
    elm_box_pack_end(box, view->time_label);

    _label_res_set(view, "");
    _label_codec_set(view, "");
    _label_quality_set(view, "");
    _label_format_set(view, "");
    _label_size_set(view, 0);
    _label_time_set(view, 0);

    return box;
}

static Evas_Object *_image_add(Evas_Object *parent)
{
    Evas_Object *image = elm_image_add(parent);
    if (image)
    {
        elm_image_file_set(image, get_resource_path(MICICON), NULL);
        elm_image_no_scale_set(image, EINA_FALSE);
        elm_image_resizable_set(image, EINA_TRUE, EINA_TRUE);
        elm_image_smooth_set(image, EINA_TRUE);
        elm_image_aspect_fixed_set(image, EINA_TRUE);
        elm_image_editable_set(image, EINA_FALSE);
        evas_object_show(image);
    }
    return image;
}

static Evas_Object *_progressbar_add(Evas_Object *parent)
{
    Evas_Object *progressbar = elm_progressbar_add(parent);

    evas_object_show(progressbar);
    evas_object_size_hint_weight_set(progressbar, EVAS_HINT_EXPAND, 0);
    evas_object_size_hint_align_set(progressbar, EVAS_HINT_FILL, 0);
    elm_progressbar_horizontal_set(progressbar, EINA_TRUE);
    elm_progressbar_value_set(progressbar, 0.0);
    return progressbar;
}

static Evas_Object *_toolbar_add(audio_recorder_view *view, Evas_Object *parent)
{
    Evas_Object *toolbar = elm_toolbar_add(parent);

    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);

    view->settings_item = elm_toolbar_item_append(toolbar, NULL, STR_SETTINGS, _on_settings_btn_pressed_cb, view);
    view->ctrl_item = elm_toolbar_item_append(toolbar, NULL, STR_START, _on_ctrl_btn_pressed_cb, view);

    return toolbar;
}

static void _quality_popup_closed_cb(radio_popup *obj, bool result, void *user_data)
{
    audio_recorder_view *view = user_data;
    if(view)
    {
        if(result)
        {
            int selected_item = radio_popup_selected_item_value_get(obj);
            _quality_set(view, selected_item);
        }
        radio_popup_del(obj);
    }
}

static void _preproc_popup_closed_cb(radio_popup *obj, bool result, void *user_data)
{
    audio_recorder_view *view = user_data;
    if(view)
    {
        if(result)
        {
            int selected_item = radio_popup_selected_item_value_get(obj);
            _preproc_set(view, selected_item);
        }
        radio_popup_del(obj);
    }
}

static void _on_recorder_audio_stream_cb(void* stream, int size, audio_sample_type_e format, int channel, unsigned int timestamp, void *user_data)
{
    audio_recorder_view *view = user_data;
    if(view && view->preproc_file && !feof(view->preproc_file))
    {
        char buf[PREPROC_FILE_BUF_SIZE];
        int read_bytes = 0;

        while(read_bytes != size)
        {
            int request_bytes = MIN(PREPROC_FILE_BUF_SIZE, size - read_bytes);
            int read = fread(buf, sizeof(char), request_bytes, view->preproc_file);

            if(!read)
            {
                break;
            }

            memcpy(stream + read_bytes, buf, read);
            read_bytes += read;
        }
    }
}

static void _on_recording_status_cb(unsigned long long elapsed_time, unsigned long long file_size, void *user_data)
{
    audio_recorder_view *view = user_data;
    if(view)
    {
        double val = (elapsed_time / 1000.0) / MAX_TIME;
        elm_progressbar_value_set(view->progressbar, val);

        _label_size_set(view, (long long)file_size);
        _label_time_set(view, (int)elapsed_time / 1000);
    }
}

static void _on_recording_limit_reached_cb(recorder_recording_limit_type_e type, void *user_data)
{
    audio_recorder_view *view = user_data;
    if(view)
    {
        if(type == RECORDER_RECORDING_LIMIT_TIME)
        {
            _recorder_stop(view);
            _navigate_to_audio_player_view(view);
        }
    }
}

static void _on_settings_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_recorder_view *view = data;
    if(view)
    {
        _show_ctx_poopup_menu(view);
    }
}

static void _on_quality_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_recorder_view *view = data;
    if(view)
    {
        elm_ctxpopup_dismiss(view->ctxpopup);
        _show_quality_poopup_menu(view);
    }
}

static void _on_preproc_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_recorder_view *view = data;
    if(view)
    {
        elm_ctxpopup_dismiss(view->ctxpopup);
        _show_preproc_poopup_menu(view);
    }
}

static void _on_ctrl_btn_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_recorder_view *view = data;

    if(view)
    {
        if(_recorder_is_recording(view))
        {
            _recorder_stop(view);
            _navigate_to_audio_player_view(view);
        }
        else
        {
            _recorder_start(view);
        }
    }
}

static void _on_main_layout_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    audio_recorder_view *view = data;
    if(view)
    {
        _destroy(view);
    }
}
