/*
 * 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-inout.h"
#include "../inc/audio-inout-view.h"
#include "../../common/inc/main-app.h"

#include <Elementary.h>
#include <Ecore.h>
#include <assert.h>
#include <Ecore.h>

#define MAX_LABEL_LEN 256
#define MAX_TIME 10

#define DEFAULT_SAMPLE_RATE_INDEX 0
#define STR_TITLE "Audio in-out"
#define STR_CHANNEL_TYPE "1. Channel Type"
#define STR_MONO "Mono"
#define STR_STEREO "Stereo"
#define STR_8_BITS "8 Bits"
#define STR_16_BITS "16 Bits"
#define STR_SAMPLE_TYPE "2. Sample Type"
#define STR_SAMPLE_RATE "3. Sample Rate"
#define STR_SEC "sec"
#define STR_RECORD "Record"
#define STR_PLAY "Play"
#define STR_STOP "Stop"

typedef struct
{
    Evas_Object *label;
    Evas_Object *layout;
} label_data;

struct _audio_inout_view
{
    Elm_Object_Item *frame_item;
    Evas_Object *navi;
    Evas_Object *layout;
    Evas_Object *progress;
    label_data time_left_label;
    label_data time_right_label;
    label_data sample_rate_label;
    Evas_Object *sample_rate_slider;
    Evas_Object *bitrate_toolbar;
    Evas_Object *channel_toolbar;
    app_data *app;

    Elm_Object_Item *record_item;
    Elm_Object_Item *play_item;
    Elm_Object_Item *mono_item;
    Elm_Object_Item *stereo_item;
    Elm_Object_Item *bits8_item;
    Elm_Object_Item *bits16_item;

    audio_inout *audio_in;
    audio_inout *audio_out;
    audio_param audio_par;
    audio_buffer *audio_buf;
    size_t bytes_for_process;
};

static const sample_rate_e _sample_rate_list[] =
{
    SR_8000_HZ,
    SR_14025_HZ,
    SR_22050_HZ,
    SR_32000_HZ,
    SR_44056_HZ,
    SR_44100_HZ,
    SR_47250_HZ,
    SR_48000_HZ
};

static const size_t _sample_rate_list_len = sizeof(_sample_rate_list) / sizeof(sample_rate_e);

static void _push(audio_inout_view *view);
static Evas_Object *_add_main_layout(audio_inout_view *view, Evas_Object *parent);
static Evas_Object *_add_toolbar(audio_inout_view *view, Evas_Object *parent);
static label_data _add_label(Evas_Object *parent, double h_align, double v_align);
static void _set_label_text(Evas_Object *label, const char *text);
static void _set_sample_rate_label(audio_inout_view *view, int sample_rate);
static void _set_time_label(label_data *label, int time);
static Evas_Object *_add_slider(audio_inout_view *view, Evas_Object *parent);
static Evas_Object *_add_progressbar(audio_inout_view *view, Evas_Object *parent);
static Evas_Object *_add_progressbar_label(audio_inout_view *view, Evas_Object *parent);
static Evas_Object *_add_main_box(Evas_Object *parent);
static Evas_Object *_add_channel_toolbar(audio_inout_view *view, Evas_Object *parent);
static Evas_Object *_add_bit_rate_toolbar(audio_inout_view *view, Evas_Object *parent);
static void _reset_progress(audio_inout_view *view);
static void _switch_controls(audio_inout_view *view);
static void _destroy(audio_inout_view *view);

static void _play_start(audio_inout_view *view);
static void _play_stop(audio_inout_view *view);
static void _record_start(audio_inout_view *view);
static void _record_stop(audio_inout_view *view);
static void _prepare_for_record(audio_inout_view *view);
static void _prepare_audio_buf(audio_inout_view *view);
static void _prepare_audio_param(audio_inout_view *view);
static sample_rate_e _get_sample_rate(audio_inout_view *view);

static void _audio_in_start_cb(audio_inout *inout, void *user_data);
static void _audio_in_stop_cb(audio_inout *inout, void *user_data);
static void _audio_out_start_cb(audio_inout *inout, void *user_data);
static void _audio_out_stop_cb(audio_inout *inout, void *user_data);
static void _audio_progress_cb(audio_inout *inout, size_t processed_bytes, double elapsed_time, void *user_data);

static void _on_slider_change_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 _on_record_pressed_cb(void *data, Evas_Object *obj, void *event_info);
static void _on_play_pressed_cb(void *data, Evas_Object *obj, void *event_info);

audio_inout_view *audio_inout_view_add(app_data *app, Evas_Object *navi)
{
    audio_inout_view *view = calloc(1, sizeof(audio_inout_view));

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

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

    return view;
}

static void _destroy(audio_inout_view *view)
{
    audio_set_cbs(view->audio_in, NULL, NULL, NULL, NULL);
    audio_set_cbs(view->audio_out, NULL, NULL,NULL, NULL);
    audio_destroy(view->audio_in);
    audio_destroy(view->audio_out);
    audio_buffer_unlink(view->audio_buf);
    free(view);
}

static void _play_start(audio_inout_view *view)
{
    audio_stop(view->audio_in);
    audio_stop(view->audio_out);
    audio_destroy(view->audio_out);
    view->bytes_for_process = view->audio_buf->len;
    view->audio_out = audio_output_create(&view->audio_par);
    audio_set_buffer(view->audio_out, view->audio_buf);
    audio_set_cbs(view->audio_out, _audio_out_start_cb, _audio_out_stop_cb, _audio_progress_cb, view);
    audio_start(view->audio_out);
}

static void _play_stop(audio_inout_view *view)
{
    audio_stop(view->audio_out);
}

static void _record_start(audio_inout_view *view)
{
    audio_stop(view->audio_in);
    audio_stop(view->audio_out);
    _prepare_for_record(view);
    audio_destroy(view->audio_in);
    view->bytes_for_process = audio_get_buffer_len(&view->audio_par, 1) * MAX_TIME;
    view->audio_in = audio_input_create(&view->audio_par);
    audio_set_buffer(view->audio_in, view->audio_buf);
    audio_set_cbs(view->audio_in, _audio_in_start_cb, _audio_in_stop_cb, _audio_progress_cb, view);
    audio_start(view->audio_in);
}

static void _record_stop(audio_inout_view *view)
{
    audio_stop(view->audio_in);
}

static void _prepare_for_record(audio_inout_view *view)
{
    _prepare_audio_param(view);
    _prepare_audio_buf(view);
}

static void _prepare_audio_buf(audio_inout_view *view)
{
    audio_buffer_unlink(view->audio_buf);
    view->audio_buf = audio_buffer_new(&view->audio_par, MAX_TIME);
    assert(view->audio_buf);
}

static void _prepare_audio_param(audio_inout_view *view)
{
    Elm_Object_Item *channel_item = elm_toolbar_selected_item_get(view->channel_toolbar);
    Elm_Object_Item *bitrate_item = elm_toolbar_selected_item_get(view->bitrate_toolbar);

    if(channel_item == view->mono_item)
    {
        view->audio_par.channel = AUDIO_CHANNEL_MONO;
    }
    else if(channel_item == view->stereo_item)
    {
        view->audio_par.channel = AUDIO_CHANNEL_STEREO;
    }
    else
    {
        assert(false);
    }

    if(bitrate_item == view->bits8_item)
    {
        view->audio_par.sample_type = AUDIO_SAMPLE_TYPE_U8;
    }
    else if(bitrate_item == view->bits16_item)
    {
        view->audio_par.sample_type = AUDIO_SAMPLE_TYPE_S16_LE;
    }
    else
    {
        assert(false);
    }

    view->audio_par.sample_rate = _get_sample_rate(view);
}

static sample_rate_e _get_sample_rate(audio_inout_view *view)
{
    int index = (int)elm_slider_value_get(view->sample_rate_slider) - 1;

    if(index < _sample_rate_list_len && index >= 0)
    {
        return _sample_rate_list[index];
    }
    else
    {
        assert(false);
    }

    return SR_48000_HZ;
}

static void _push(audio_inout_view *view)
{
    Elm_Object_Item *item = elm_naviframe_item_push(view->navi, STR_TITLE, NULL, NULL, view->layout, NULL);

    Evas_Object *toolbar = _add_toolbar(view, view->navi);
    elm_object_item_part_content_set(item, "toolbar", toolbar);

    view->frame_item = item;
}

static Evas_Object *_add_main_layout(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *box = _add_main_box(parent);
    view->layout = box;

    label_data channel_label = _add_label(box, 0.0, 0.5);
    _set_label_text(channel_label.label, STR_CHANNEL_TYPE);

    label_data sample_type_label = _add_label(box, 0.0, 0.5);
    _set_label_text(sample_type_label.label, STR_SAMPLE_TYPE);

    view->sample_rate_label = _add_label(box, 0.0, 0.5);
    _set_label_text(view->sample_rate_label.label, STR_SAMPLE_RATE);

    view->bitrate_toolbar = _add_bit_rate_toolbar(view, box);
    view->channel_toolbar = _add_channel_toolbar(view, box);
    view->sample_rate_slider = _add_slider(view, box);
    Evas_Object *progress = _add_progressbar(view, box);
    Evas_Object *progress_label = _add_progressbar_label(view, box);

    elm_box_pack_end(box, channel_label.layout);
    elm_box_pack_end(box, view->channel_toolbar);
    elm_box_pack_end(box, sample_type_label.layout);
    elm_box_pack_end(box, view->bitrate_toolbar);
    elm_box_pack_end(box, view->sample_rate_label.layout);
    elm_box_pack_end(box, view->sample_rate_slider);
    elm_box_pack_end(box, progress);
    elm_box_pack_end(box, progress_label);

    _set_sample_rate_label(view, _sample_rate_list[DEFAULT_SAMPLE_RATE_INDEX]);

    return box;
}

static Evas_Object *_add_main_box(Evas_Object *parent)
{
    Evas_Object *box = elm_box_add(parent);
    elm_box_horizontal_set(box, EINA_FALSE);
    evas_object_show(box);
    elm_box_align_set(box, 0.0, 0.0);
    evas_object_show(box);
    return box;
}

static Evas_Object *_add_toolbar(audio_inout_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->record_item = elm_toolbar_item_append(toolbar, NULL, STR_RECORD, _on_record_pressed_cb, view);
    view->play_item = elm_toolbar_item_append(toolbar, NULL, STR_PLAY, _on_play_pressed_cb, view);

    elm_object_item_disabled_set(view->play_item , EINA_TRUE);

    return toolbar;
}

static Evas_Object *_add_slider(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *slider = elm_slider_add(parent);

    elm_slider_horizontal_set(slider, EINA_TRUE);
    evas_object_show(slider);
    elm_slider_indicator_format_set(slider, "%1.0f");
    evas_object_size_hint_weight_set(slider, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(slider, EVAS_HINT_FILL, EVAS_HINT_FILL);
    elm_slider_min_max_set(slider, 1, _sample_rate_list_len);
    elm_slider_value_set(slider, DEFAULT_SAMPLE_RATE_INDEX);
    elm_slider_indicator_show_set(slider, EINA_TRUE);
    evas_object_smart_callback_add(slider, "changed", _on_slider_change_cb, view);

    return slider;
}

static Evas_Object *_add_progressbar(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *box = elm_box_add(parent);
    evas_object_show(box);
    elm_box_align_set(box, 0.0, 1.0);
    evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);

    view->progress = elm_progressbar_add(parent);

    evas_object_show(view->progress);
    evas_object_size_hint_weight_set(view->progress, EVAS_HINT_EXPAND, 0.0);
    evas_object_size_hint_align_set(view->progress, EVAS_HINT_FILL, 0.0);
    elm_progressbar_horizontal_set(view->progress, EINA_TRUE);
    elm_progressbar_value_set(view->progress, 0.0);

    elm_box_pack_end(box, view->progress);

    return box;
}

static Evas_Object *_add_progressbar_label(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *box = elm_box_add(parent);
    evas_object_show(box);
    elm_box_align_set(box, 0.0, 0.0);
    elm_box_horizontal_set(box, EINA_TRUE);
    evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);

    view->time_left_label =_add_label(box, 0.0, 0.0);
    view->time_right_label = _add_label(box, 1.0, 0.0);

    _set_time_label(&view->time_left_label, 0);
    _set_time_label(&view->time_right_label, MAX_TIME);

    elm_box_pack_end(box, view->time_left_label.layout);
    elm_box_pack_end(box, view->time_right_label.layout);

    return box;
}

static label_data _add_label(Evas_Object *parent, double h_align, double v_align)
{
    Evas_Object *box = elm_box_add(parent);
    evas_object_show(box);
    elm_box_align_set(box, h_align, v_align);
    evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
    evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);

    Evas_Object *label = elm_label_add(parent);

    evas_object_size_hint_weight_set(label, EVAS_HINT_EXPAND, 0.0);
    evas_object_size_hint_align_set(label, h_align, v_align);
    evas_object_show(label);

    elm_box_pack_end(box, label);

    label_data res = {.label=label, .layout=box};
    return res;
}

static Evas_Object *_add_channel_toolbar(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *toolbar = elm_toolbar_add(parent);

    elm_toolbar_shrink_mode_set(toolbar, ELM_TOOLBAR_SHRINK_EXPAND);
    elm_toolbar_select_mode_set(toolbar, ELM_OBJECT_SELECT_MODE_ALWAYS);
    elm_toolbar_transverse_expanded_set(toolbar, EINA_TRUE);
    elm_object_style_set(toolbar, "tabbar");

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

    view->mono_item = elm_toolbar_item_append(toolbar, NULL, STR_MONO, NULL, view);
    view->stereo_item = elm_toolbar_item_append(toolbar, NULL, STR_STEREO, NULL, view);

    return toolbar;
}

static Evas_Object *_add_bit_rate_toolbar(audio_inout_view *view, Evas_Object *parent)
{
    Evas_Object *toolbar = elm_toolbar_add(parent);

    elm_toolbar_shrink_mode_set(toolbar, ELM_TOOLBAR_SHRINK_EXPAND);
    elm_toolbar_select_mode_set(toolbar, ELM_OBJECT_SELECT_MODE_ALWAYS);
    elm_toolbar_transverse_expanded_set(toolbar, EINA_TRUE);
    elm_object_style_set(toolbar, "tabbar");

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

    view->bits8_item = elm_toolbar_item_append(toolbar, NULL, STR_8_BITS, NULL, view);
    view->bits16_item = elm_toolbar_item_append(toolbar, NULL, STR_16_BITS, NULL, view);

    return toolbar;
}

static void _reset_progress(audio_inout_view *view)
{
    elm_progressbar_value_set(view->progress, 0.0);
    _set_time_label(&view->time_left_label, 0);
}

static void _switch_controls(audio_inout_view *view)
{
    bool disabled = false;
    bool play = false;
    bool record = false;

    if(audio_is_started(view->audio_in))
    {
        disabled = true;
        play = true;
        record = false;
    }
    else if(audio_is_started(view->audio_out))
    {
        disabled = true;
        play = false;
        record = true;
    }

    elm_object_item_disabled_set(view->play_item, play);
    elm_object_item_disabled_set(view->record_item, record);

    elm_object_item_disabled_set(view->bits8_item, disabled);
    elm_object_item_disabled_set(view->bits16_item, disabled);

    elm_object_item_disabled_set(view->mono_item, disabled);
    elm_object_item_disabled_set(view->stereo_item, disabled);

    elm_object_disabled_set(view->sample_rate_slider, disabled);
}

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

static void _set_sample_rate_label(audio_inout_view *view, int sample_rate)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%s: %d", STR_SAMPLE_RATE, sample_rate);
    _set_label_text(view->sample_rate_label.label, buf);
}

static void _set_time_label(label_data *label, int time)
{
    char buf[MAX_LABEL_LEN] = {'\0'};
    snprintf(buf, MAX_LABEL_LEN, "%d %s", time, STR_SEC);
    elm_object_text_set(label->label, buf);
}

static void _audio_in_start_cb(audio_inout *inout, void *user_data)
{
    audio_inout_view *view = user_data;
    if(view)
    {
        _set_time_label(&view->time_right_label, MAX_TIME);
        elm_object_item_text_set(view->record_item, STR_STOP);
        _switch_controls(view);
    }
}

static void _audio_in_stop_cb(audio_inout *inout, void *user_data)
{
    audio_inout_view *view = user_data;
    if(view)
    {
        elm_object_item_text_set(view->record_item, STR_RECORD);
        _reset_progress(view);
        _switch_controls(view);
    }
}

static void _audio_out_start_cb(audio_inout *inout, void *user_data)
{
    audio_inout_view *view = user_data;
    if(view)
    {
        _set_time_label(&view->time_right_label, audio_get_duration(&view->audio_par, view->audio_buf->len));
        elm_object_item_text_set(view->play_item, STR_STOP);
        _switch_controls(view);
    }
}

static void _audio_out_stop_cb(audio_inout *inout, void *user_data)
{
    audio_inout_view *view = user_data;
    if(view)
    {
        elm_object_item_text_set(view->play_item, STR_PLAY);
        _reset_progress(view);
        _switch_controls(view);
    }
}

static void _audio_progress_cb(audio_inout *inout, size_t processed_bytes, double elapsed_time, void *user_data)
{
    audio_inout_view *view = user_data;

    if(view && audio_is_started(inout))
    {
        double progress = view->bytes_for_process > 0 ? (double)processed_bytes / view->bytes_for_process : 0.0;

        elm_progressbar_value_set(view->progress, progress);
        _set_time_label(&view->time_left_label, elapsed_time + 0.5);
    }
}

static void _on_record_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_inout_view *view = data;
    if(view)
    {
        if(audio_is_started(view->audio_in))
        {
            _record_stop(view);
        }
        else
        {
            _record_start(view);
        }
    }
}

static void _on_play_pressed_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_inout_view *view = data;
    if(view)
    {
        if(audio_is_started(view->audio_out))
        {
            _play_stop(view);

        }
        else
        {
            _play_start(view);
        }
    }
}

static void _on_slider_change_cb(void *data, Evas_Object *obj, void *event_info)
{
    audio_inout_view *view = data;
    if(view)
    {
        int hz = _get_sample_rate(view);
        _set_sample_rate_label(view, hz);
    }
}

static void _on_main_layout_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
    audio_inout_view *view = data;

    if(view)
    {
        _destroy(view);
    }
}
