/*
 * Copyright Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 <math.h>
#include <string.h>
#include <stdlib.h>
#include "player.h"

#define URI_FILE_PREFIX "file://"
#define POSITION_UPDATE_PERIOD_MS (1000)  // 1000 ms

#if defined(__QNXNTO__)
#define CURLOPT_CAPATH_ENV "AAL_CAPATH"
#define CURLOPT_CAPATH_DEFAULT "/etc/ssl/certs"
#endif

static bool set_playstate(aal_handle_t handle, XAuint32 state) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;

    r = (*(ctx->play_itf))->SetPlayState(ctx->play_itf, state);
    bail_if_error(r);

    return true;
bail:
    return false;
}

static void omxal_play_callback(XAPlayItf caller, void* pContext, XAuint32 event) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)pContext;

    switch (event) {
        case XA_PLAYEVENT_HEADATEND:
            debug("XA_PLAYEVENT_HEADATEND");
            if (ctx->listener->on_stop) ctx->listener->on_stop(AAL_SUCCESS, ctx->user_data);
            break;
        case XA_PLAYEVENT_HEADSTALLED:
            debug("XA_PLAYEVENT_HEADSTALLED");
            break;
        default:
            debug("Unhandled event %d", event);
            break;
    }
}

static aal_handle_t omxal_player_create(const aal_attributes_t* attrs, aal_audio_parameters_t* params) {
    aal_omxal_player_context_t* ctx = NULL;
    XAresult r;
    int l;
#if defined(__QNXNTO__)
    char* ca_path = NULL;
#endif

    if (attrs->uri == NULL || strlen(attrs->uri) == 0) {
        debug("Invalid URI is specified");
        return NULL;
    }
    if (params != NULL) {
        debug("Explicitly specifying stream type is not supported");
        return NULL;
    }

    XADataLocator_URI source_locator = {
        .locatorType = XA_DATALOCATOR_URI,
#if defined(__OMXAL_V_1_0__)
        .URI = attrs->uri
#else
        .pURI = attrs->uri
#endif
    };
    XADataSource source = {.pLocator = &source_locator, .pFormat = NULL};

    XADataLocator_OutputMix sink_locator = {.locatorType = XA_DATALOCATOR_OUTPUTMIX, .outputMix = NULL};
    XADataSink sink = {.pLocator = &sink_locator, .pFormat = NULL};

    XAInterfaceID interface_ids[] = {XA_IID_VOLUME};
    XAboolean required_list[] = {XA_BOOLEAN_TRUE};

    /* Allocate context */
    ctx = (aal_omxal_player_context_t*)calloc(1, sizeof(aal_omxal_player_context_t));
    bail_if_null(ctx);
    ctx->file_path = NULL;

    /* Convert "file://<path>" -> "<path>" */
    l = strlen(URI_FILE_PREFIX);
    if (strncmp(URI_FILE_PREFIX, attrs->uri, l) == 0) {
        int len = strlen(attrs->uri) - l;
        ctx->file_path = (char*)calloc(len + 1, sizeof(char));
        strncpy(ctx->file_path, attrs->uri + l, len);
        debug("Update file path %s", ctx->file_path);
#if defined(__OMXAL_V_1_0__)
        source_locator.URI = ctx->file_path;
#else
        source_locator.pURI = ctx->file_path;
#endif
    }
#if defined(__OMXAL_V_1_0__)
    debug("URI=%s", source_locator.URI);
#else
    debug("URI=%s", source_locator.pURI);
#endif

    /* Create and realize OutputMix */
    r = (*engine_itf)->CreateOutputMix(engine_itf, &ctx->output_mix, 1, interface_ids, required_list);
    bail_if_error(r);
    r = (*ctx->output_mix)->Realize(ctx->output_mix, XA_BOOLEAN_FALSE);
    bail_if_error(r);

    /* Get interfaces */
    r = (*(ctx->output_mix))->GetInterface(ctx->output_mix, XA_IID_VOLUME, (void*)&(ctx->volume_itf));
    bail_if_error(r);

    sink_locator.outputMix = ctx->output_mix;

    /* Create MediaPlayer */
    r = (*engine_itf)
            ->CreateMediaPlayer(engine_itf, &ctx->player, &source, NULL, &sink, NULL, NULL, NULL, 0, NULL, NULL);
    bail_if_error(r);

    /* Get interfaces */
    r = (*(ctx->player))->GetInterface(ctx->player, XA_IID_PLAY, (void*)&(ctx->play_itf));
    bail_if_error(r);
    r = (*(ctx->player))->GetInterface(ctx->player, XA_IID_SEEK, (void*)&(ctx->seek_itf));
    bail_if_error(r);
    r = (*(ctx->player))->GetInterface(ctx->player, XA_IID_CONFIGEXTENSION, (void*)&(ctx->config_itf));
    bail_if_error(r);
    r = (*(ctx->player))->GetInterface(ctx->player, XA_IID_PREFETCHSTATUS, (void*)&(ctx->prefetch_status_itf));
    bail_if_error(r);
    r = (*(ctx->player))->GetInterface(ctx->player, XA_IID_STREAMINFORMATION, (void*)&(ctx->stream_info_itf));
    bail_if_error(r);

#if defined(__QNXNTO__)
    ca_path = getenv(CURLOPT_CAPATH_ENV);
    if (!ca_path || IS_EMPTY_STRING(ca_path)) {
        ca_path = CURLOPT_CAPATH_DEFAULT;
    }
    debug("Use CURLOPT_CAPATH: %s", ca_path);
    r = (*(ctx->config_itf))->SetConfiguration(ctx->config_itf, XAQ_CONFIGEXT_KEY_CAPATH, strlen(ca_path), ca_path);
    bail_if_error(r);
#endif

    /* Realize MediaPlayer */
    r = (*ctx->player)->Realize(ctx->player, XA_BOOLEAN_FALSE);
    bail_if_error(r);

    /* Register callback */
    r = (*(ctx->play_itf))->RegisterCallback(ctx->play_itf, omxal_play_callback, ctx);
    bail_if_error(r);
    r = (*(ctx->play_itf))->SetCallbackEventsMask(ctx->play_itf, XA_PLAYEVENT_HEADATEND | XA_PLAYEVENT_HEADSTALLED);
    bail_if_error(r);

    return ctx;
bail:
    debug("Status %d", r);
    if (ctx) free(ctx);
    return NULL;
}

static void omxal_player_play(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    bool r = set_playstate(handle, XA_PLAYSTATE_PLAYING);
    /* NOTE: No callback will be generated upon user initiated start */
    if (r && ctx->listener->on_start) ctx->listener->on_start(ctx->user_data);
}

static void omxal_player_pause(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    bool r = set_playstate(handle, XA_PLAYSTATE_PAUSED);
    /* NOTE: No callback will be generated upon user initiated pause */
    if (r && ctx->listener->on_stop) ctx->listener->on_stop(AAL_PAUSED, ctx->user_data);
}

static void omxal_player_stop(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    bool r = set_playstate(handle, XA_PLAYSTATE_STOPPED);
    /* NOTE: No callback will be generated upon user initiated stop */
    if (r && ctx->listener->on_stop) ctx->listener->on_stop(AAL_UNKNOWN, ctx->user_data);
}

static int64_t omxal_player_get_position(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;
    XAmillisecond position;

    r = (*(ctx->play_itf))->GetPosition(ctx->play_itf, &position);
    bail_if_error(r);

    return (int64_t)position;
bail:
    return 0;
}

static int64_t omxal_player_get_duration(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;
    XAmillisecond duration;

    r = (*(ctx->play_itf))->GetDuration(ctx->play_itf, &duration);
    bail_if_error(r);

    return (int64_t)duration;
bail:
    return 0;
}

static int64_t omxal_player_get_num_bytes_buffered(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;
    XAuint32 status;
    XApermille fill_level;

    r = (*(ctx->prefetch_status_itf))->GetPrefetchStatus(ctx->prefetch_status_itf, &status);
    bail_if_error(r);

    switch (status) {
        case XA_PREFETCHSTATUS_SUFFICIENTDATA:
            r = (*(ctx->prefetch_status_itf))->GetFillLevel(ctx->prefetch_status_itf, &fill_level);
            bail_if_error(r);
            break;
        case XA_PREFETCHSTATUS_UNDERFLOW:
        case XA_PREFETCHSTATUS_OVERFLOW:
        default:
            return 0;
    }

    XAAudioStreamInformation asi;
    r = (*(ctx->stream_info_itf))->QueryStreamInformation(ctx->stream_info_itf, 1, &asi);
    bail_if_error(r);

    // Currently no way to check buffered duration. Assume 1 second buffer.
    return asi.bitRate / 8 * 1 * fill_level / 1000;
bail:
    return 0;
}

static void omxal_player_seek(aal_handle_t handle, int64_t position) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;

    r = (*(ctx->seek_itf))->SetPosition(ctx->seek_itf, (XAmillisecond)position, XA_SEEKMODE_FAST);
    if (r != XA_RESULT_SUCCESS) {
        debug("Failed to seek: %d", r);
    }
}

static void omxal_player_set_volume(aal_handle_t handle, double volume) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;
    XAmillibel mB;

    XAmillibel maxLevel;
    r = (*(ctx->volume_itf))->GetMaxVolumeLevel(ctx->volume_itf, &maxLevel);
    if (r != XA_RESULT_SUCCESS) {
        debug("Failed to get max volume level: %d", r);
        return;
    }
    debug("Max volume level = %d", maxLevel);

#if defined(__QNXNTO__)
    /* QNX implementation is broken, use amp * 100 */
    mB = volume * 100;
#else
    if (volume >= 1.0f) {
        mB = maxLevel;
    } else if (volume < 0.01f) {
        mB = XA_MILLIBEL_MIN;
    } else {
        mB = maxLevel + 2000.0f * log10(volume);
        debug("Calc val %f amp -> %d mB", volume, mB);
    }
#endif

    r = (*(ctx->volume_itf))->SetVolumeLevel(ctx->volume_itf, mB);
    if (r != XA_RESULT_SUCCESS) {
        debug("Failed to set volume level: %d", r);
    }
}

static void omxal_player_set_mute(aal_handle_t handle, bool mute) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    XAresult r;

    r = (*(ctx->volume_itf))->SetMute(ctx->volume_itf, to_XAboolean(mute));
}

static void omxal_player_destroy(aal_handle_t handle) {
    aal_omxal_player_context_t* ctx = (aal_omxal_player_context_t*)handle;
    if (ctx->player) {
        (*(ctx->player))->Destroy(ctx->player);
    }
    if (ctx->output_mix) {
        (*(ctx->output_mix))->Destroy(ctx->output_mix);
    }
    if (ctx->file_path) {
        free(ctx->file_path);
    }
    free(ctx);
}

const aal_player_ops_t omxal_player_ops = {.create = omxal_player_create,
                                           .play = omxal_player_play,
                                           .pause = omxal_player_pause,
                                           .stop = omxal_player_stop,
                                           .get_position = omxal_player_get_position,
                                           .get_duration = omxal_player_get_duration,
                                           .get_num_bytes_buffered = omxal_player_get_num_bytes_buffered,
                                           .seek = omxal_player_seek,
                                           .set_volume = omxal_player_set_volume,
                                           .set_mute = omxal_player_set_mute,
                                           .write = NULL,
                                           .notify_end_of_stream = NULL,
                                           .destroy = omxal_player_destroy};
