


#include "ice_streamer.h"

#include "frost_basicstring.h"
#include "frost_mem.h"
#include "frost_str.h"
#include "frost/frost_time.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "utilities_debug/cabot_assert.h"
#include "utilities_debug/trace.h"

TRACE_IMPLEMENT("ice/ice_streamer_curl_http");

#include <curl/curl.h>

//#define  DUMP_CURL_DATA
static ice_streamer_handler_t * g_handle = NULL ;
static frost_uint64 g_offset = 0 ;
static frost_bool g_seek_enable = frost_false ;

frost_bool
ice_streamer_curl_plugin_setup (ice_streamer_handler_plugin_t *plugin)
{
    g_handle = NULL; //@@++-- 20120727 g_handle must be reset so that we can get the correct handle at second play | ilters

    ice_streamer_handler_methods_t methods;

    memset (&methods,0,sizeof(ice_streamer_handler_methods_t));
    methods.init = ice_streamer_curl_init;
    methods.destroy = ice_streamer_curl_destroy;
    methods.read = ice_streamer_curl_read;
    methods.seek = ice_streamer_curl_seek;

    ice_streamer_handler_plugin_methods_set (plugin, &methods);

    ice_streamer_handler_plugin_indata_add (plugin,
                                  ICE_STREAM_TYPE_MIMETYPE,
                                  "application/x-url",
                                  ICE_STREAM_TYPE_URL,
                                  "http://*",
                                  ICE_STREAM_TYPE_END);

    ice_streamer_handler_plugin_indata_add (plugin,
                                  ICE_STREAM_TYPE_MIMETYPE,
                                  "application/x-url",
                                  ICE_STREAM_TYPE_URL,
                                  "https://*",
                                  ICE_STREAM_TYPE_END);

    return frost_true;
}

/*
 * Member functions
 */

frost_bool
ice_streamer_curl_init (ice_streamer_handler_t * handle)
{
    g_handle = NULL; //@@++-- 20120727 g_handle must be reset so that we can get the correct handle at second play | ilters

    ice_streamer_curl_data_t *data = 0 ;
    ice_error_code_t error;
    frost_int metaint = 0;
    const char *url;
    curl_version_info_data *version;

    url = ice_streamer_handler_indata_get_str (handle, ICE_STREAM_TYPE_URL);

    frost_return_val_if_fail (handle, frost_false);

    data = frost_new0 (ice_streamer_curl_data_t, 1);

    data->broken_version = frost_false;

    data->buffer = (char*)frost_malloc (CURL_MAX_WRITE_SIZE);
    data->header = (char*)frost_malloc (2*CURL_MAX_WRITE_SIZE);
    data->header_enabled = (frost_bool)!handle->download_header_disabled ;
    data->header_allocated = CURL_MAX_WRITE_SIZE ;

    data->url = frost_strdup (url);

#ifdef  DUMP_CURL_DATA
    handle->dump_fp = fopen("/tmp/orig.bin","wb");
#endif

    /* check for broken version of curl here */
    version = curl_version_info (CURLVERSION_NOW);
    TRACE_INFO(("Using version %s of libcurl", version->version));
    if (version->version_num == 0x071001 || version->version_num == 0x071002) {
        TRACE_INFO(("**********************************************\n"));
        TRACE_INFO(("Your version of libcurl is incompatible with\n"));
        TRACE_INFO(("ICE STREAMER and you will not be able to stream shout/ice-cast\n"));
        TRACE_INFO(("radio stations. Please consider downgrade to 7.15 or\n"));
        TRACE_INFO(("upgrade to a more recent version than 7.16.2\n"));
        TRACE_INFO(("**********************************************\n"));
        data->broken_version = frost_true;
    }

    data->http_req_headers = curl_slist_append (data->http_req_headers,
                                                "Connection: Keep-Alive");
    data->http_req_headers = curl_slist_append (data->http_req_headers,
                                                    "Keep-Alive: 300");
    if (!data->broken_version && metaint == 1) {
        printf("=======> (%s,%d)\n",__FUNCTION__,__LINE__);
        data->http_req_headers = curl_slist_append (data->http_req_headers,
                                                    "Icy-MetaData: 1");
    }

    data->curl_easy = curl_easy_init ();

    curl_easy_setopt (data->curl_easy, CURLOPT_URL, data->url);
//    curl_easy_setopt (data->curl_easy, CURLOPT_HEADER, 0);
//    curl_easy_setopt (data->curl_easy, CURLOPT_HTTPGET, 1);
    curl_easy_setopt (data->curl_easy, CURLOPT_FOLLOWLOCATION, 1);
//    curl_easy_setopt (data->curl_easy, CURLOPT_AUTOREFERER, 1);
//    curl_easy_setopt (data->curl_easy, CURLOPT_FAILONERROR, 1);
    curl_easy_setopt (data->curl_easy, CURLOPT_NOPROGRESS, 0);
//    curl_easy_setopt (data->curl_easy, CURLOPT_USERAGENT,handle->user_agent );
    curl_easy_setopt (data->curl_easy, CURLOPT_WRITEHEADER, handle);
    curl_easy_setopt (data->curl_easy, CURLOPT_WRITEDATA, handle);
    curl_easy_setopt (data->curl_easy, CURLOPT_WRITEFUNCTION,
                      ice_streamer_curl_callback_write);
    curl_easy_setopt (data->curl_easy, CURLOPT_HEADERFUNCTION,
                      ice_streamer_curl_callback_header);
//    curl_easy_setopt (data->curl_easy, CURLOPT_CONNECTTIMEOUT, connecttimeout);
    curl_easy_setopt (data->curl_easy, CURLOPT_LOW_SPEED_TIME, NO_DATA_TRANSMISSION_TIMEOUT);
    curl_easy_setopt (data->curl_easy, CURLOPT_LOW_SPEED_LIMIT, NO_DATA_TRANSMISSION_SPEED_LIMIT);

    if(g_offset > 0)
    {
//        char range[64];

        printf("=======> offset: %lld,  (%s,%d)\n",g_offset ,__FUNCTION__,__LINE__);

//        sprintf(range, "%ld- \0", g_offset);

        curl_easy_setopt(data->curl_easy, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)g_offset);

//        TRACE_INFO(("############   =%s\n",range));

        g_offset = 0 ;
    }

    if (!data->broken_version) {
        printf("=======> http_200_aliases: %d (%s,%d)\n",data->http_200_aliases,__FUNCTION__,__LINE__);
        data->http_200_aliases = curl_slist_append (data->http_200_aliases,
                                                    "ICY 200 OK");
        data->http_200_aliases = curl_slist_append (data->http_200_aliases,
                                                    "ICY 402 Service Unavailabe");
        curl_easy_setopt (data->curl_easy, CURLOPT_HTTP200ALIASES,
                          data->http_200_aliases);
    }


    curl_easy_setopt (data->curl_easy, CURLOPT_NOSIGNAL, 1);
//#ifdef CABOT_TRACE
    curl_easy_setopt (data->curl_easy, CURLOPT_VERBOSE, 1);
//#endif
    curl_easy_setopt (data->curl_easy, CURLOPT_SSL_VERIFYPEER, 0L);
//    curl_easy_setopt (data->curl_easy, CURLOPT_SSL_VERIFYHOST, 0);

    if (data->http_req_headers) {
        curl_easy_setopt (data->curl_easy, CURLOPT_HTTPHEADER,
                          data->http_req_headers);
    }

    data->curl_multi = curl_multi_init ();
    
    data->curl_code = CURLM_CALL_MULTI_PERFORM;

    curl_multi_add_handle (data->curl_multi, data->curl_easy);

    ice_streamer_private_data_set(handle, data);

    /* perform initial fill to see if it contains shoutcast metadata or not */
    if (fill_buffer (handle, data, &error) <= 0) {
        /* something went wrong */
        ice_streamer_private_data_set (handle, NULL);
        ice_streamer_curl_free_data (data);
        return frost_false;
    }

    if (data->meta_offset > 0) {
        TRACE_INFO(("icy-metadata detected\n"));
// TODO:       ice_streamer_auxdata_set_int (handle, "meta_offset", data->meta_offset);

        ice_streamer_handler_outdata_type_add (handle,
                                     ICE_STREAM_TYPE_MIMETYPE,
                                     "application/x-icy-stream",
                                     ICE_STREAM_TYPE_END);
    } else {
        ice_streamer_handler_outdata_type_add (handle,
                                     ICE_STREAM_TYPE_MIMETYPE,
                                     "application/octet-stream",
                                     ICE_STREAM_TYPE_END);
    }

    g_handle = handle;

    return frost_true;
}

frost_int
fill_buffer (ice_streamer_handler_t * handle, ice_streamer_curl_data_t *data, ice_error_code_t *error)
{
    frost_int handles;

    frost_return_val_if_fail (handle, -1);
    frost_return_val_if_fail (data, -1);
    frost_return_val_if_fail (error, -1);

    while (frost_true) {
        if (data->curl_code == CURLM_OK) {
            fd_set fdread, fdwrite, fdexcp;
            struct timeval timeout;
            frost_int ret, maxfd;
            frost_long milliseconds;

            FD_ZERO (&fdread);
            FD_ZERO (&fdwrite);
            FD_ZERO (&fdexcp);

            curl_multi_fdset (data->curl_multi, &fdread, &fdwrite, &fdexcp,
                              &maxfd);
            curl_multi_timeout (data->curl_multi, &milliseconds);

            if (milliseconds <= 0) {
                milliseconds = 1000;
            }

            timeout.tv_sec = milliseconds / 1000;
            timeout.tv_usec = (milliseconds % 1000) * 1000;

            ret = select (maxfd + 1, &fdread, &fdwrite, &fdexcp, &timeout);
            const char *msg = "Error select" ;

            if (ret == -1) {
                ice_streamer_error_set (error, ICE_ERROR_GENERIC, msg);
                return -1;
            }
        }

        data->curl_code = curl_multi_perform (data->curl_multi, &handles);

        if (data->curl_code != CURLM_CALL_MULTI_PERFORM &&
            data->curl_code != CURLM_OK) {

            const char *msg = curl_multi_strerror ((CURLMcode)data->curl_code) ;

            ice_streamer_error_set (error, ICE_ERROR_GENERIC, msg);
            return -1;
        }

        /* done */
        if (handles == 0) {
            CURLMsg *curlmsg;
            frost_int messages;

            do {
                curlmsg = curl_multi_info_read (data->curl_multi, &messages);

                if (curlmsg == NULL)
                    break;

                if (curlmsg->msg == CURLMSG_DONE && curlmsg->data.result != CURLE_OK) {
                    TRACE_ERROR(("Curl fill_buffer returned error: (%d) %s",
                                    curlmsg->data.result,
                                    curl_easy_strerror (curlmsg->data.result)));
                } else {
                    TRACE_INFO(("Curl fill_buffer returned unknown message (%d)", curlmsg->msg));
                }
            } while (messages > 0);

            data->done = frost_true;
            return 0;
        }

        if (data->bufferlen > 0) {
            return 1;
        }
    }
}

char *
ice_streamer_curl_get_header(void)
{
    ice_streamer_curl_data_t *data;
    frost_int ret;

    frost_return_val_if_fail (g_handle, 0);


    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (g_handle);
    frost_return_val_if_fail (data, 0);

    data->header_enabled = frost_false ;
    return data->header ;
}

frost_int
ice_streamer_curl_read (ice_streamer_handler_t * handle, void *buffer, frost_int len,
                ice_error_code_t *error)
{
    ice_streamer_curl_data_t *data;
    frost_int ret;

    frost_return_val_if_fail (handle, -1);
    frost_return_val_if_fail (buffer, -1);
    frost_return_val_if_fail (error, -1);

    if(NULL == g_handle)
    {
        g_handle = handle ;
        TRACE_INFO(("g_handle = handle = 0x%x\n",g_handle));
    }

    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (handle);
    frost_return_val_if_fail (data, -1);

    if (data->done)
        return 0;
    TRACE_INFO(("================> (%s,%d) \n",__FUNCTION__,__LINE__));

    while (frost_true) {

        /* if we have data available, just pick it up (even if there's
           less bytes available than was requested) */
        if (data->bufferlen) {
            len = MIN (len, data->bufferlen);
            memcpy (buffer, data->buffer, len);
            data->bufferlen -= len;
            if (data->bufferlen) {
                memmove (data->buffer, data->buffer + len, data->bufferlen);
            }
            return len;
        }
        TRACE_INFO(("ice_streamer_curl_read \n"));
        ret = fill_buffer (handle, data, error);

        if (ret == 0 || ret == -1) {
            return ret;
        }
    }
}

void
ice_streamer_curl_destroy (ice_streamer_handler_t * handle)
{
    ice_streamer_curl_data_t *data;

    frost_return_if_fail (handle);

#ifdef DUMP_CURL_DATA
    if(handle->dump_fp)
    {
        sync();
        fclose(handle->dump_fp);
    }
#endif

    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (handle);
    frost_return_if_fail (data);

    ice_streamer_curl_free_data (data);
}

#ifdef DUMP_CURL_DATA
frost_ulong  g_total_len = 0 ;
#endif

/*
 * CURL callback functions
 */
size_t
ice_streamer_curl_callback_write (void *ptr, size_t size, size_t nmemb, void *stream)
{
    ice_streamer_curl_data_t *data;
    ice_streamer_handler_t * handle = (ice_streamer_handler_t *) stream;
    frost_int len;

    frost_return_val_if_fail (handle, 0);

    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (handle);
    frost_return_val_if_fail (data, 0);

    len = size * nmemb;

    frost_return_val_if_fail ((data->bufferlen + len) <= CURL_MAX_WRITE_SIZE, 0);

    memcpy (data->buffer + data->bufferlen, ptr, len);
    data->bufferlen = data->bufferlen + len;
    if(data->header_enabled)
    {
        if((data->headerlen + len) > data->header_allocated)
        {
            TRACE_INFO(("=============> Realloc for header \n"));
            data->header = (char*)frost_realloc(data->header,(data->headerlen + len));
            data->header_allocated = data->headerlen + len ;
        }
        memcpy (data->header + data->headerlen, ptr, len);
        data->headerlen = data->headerlen + len;
    }

#ifdef DUMP_CURL_DATA
    if(handle->dump_fp)
    {
        fwrite(ptr,size,nmemb,handle->dump_fp);
        sync();
        g_total_len += len ;
        TRACE_INFO(("=============> Downloaded bytes of :%d data written g_total_len : %d \n",len,g_total_len));
    }
#endif
    return len;
}

static int
strlen_no_crlf (char *ptr, int len) {
    int ep = len - 1;
    for (; ep >= 0; --ep) {
        if (ptr[ep] != '\r' && ptr[ep] != '\n') {
            break;
        }
    }
    return ep + 1;
}

size_t
ice_streamer_curl_callback_header (void *ptr, size_t size, size_t nmemb, void *stream)
{
    ice_streamer_handler_t * handle = (ice_streamer_handler_t *) stream;
    handler_func_t func;
    char *header;

    TRACE_INFO(("%.*s\n", strlen_no_crlf ((char*)ptr, size * nmemb), (char*)ptr));

    frost_return_val_if_fail (handle, 0);
    frost_return_val_if_fail (ptr, 0);

    header = frost_strndup ((char*)ptr, size * nmemb);

    func = header_handler_find (header);
    TRACE_INFO(("header_handler_find : %d \n",func));
    if (func != NULL) {
        char *val = strchr (header, ':');
        if (val) {
            frost_strstrip (++val);
        } else {
            val = header;
        }
        func (handle, val);
    }

    frost_free (header);
    return size * nmemb;
}

handler_func_t
header_handler_find (char *header)
{
    frost_uint i;

    frost_return_val_if_fail (header, NULL);

    for (i = 0; handlers[i].name != NULL; i++) {
        frost_uint len = strlen (handlers[i].name);

        if (frost_ascii_strncasecmp (handlers[i].name, header, len) == 0) {
            return handlers[i].func;
        }
    }

    return NULL;
}

void
header_handler_contentlength (ice_streamer_handler_t * handle,
                              char *header)
{
    ice_streamer_curl_data_t *data;
    int length;
    const char *metakey;

    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (handle);

    length = strtoul (header, NULL, 10);

    metakey = ICE_MEDIALIB_ENTRY_PROPERTY_SIZE,
    ice_streamer_handler_metadata_set_int (handle, metakey, length);
}

void
header_handler_icy_metaint (ice_streamer_handler_t * handle,
                            char *header)
{
    ice_streamer_curl_data_t *data;

    data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get (handle);

    data->meta_offset = strtoul (header, NULL, 10);
}

void
header_handler_icy_name (ice_streamer_handler_t * handle,
                         char *header)
{
    const char *metakey = ICE_MEDIALIB_ENTRY_PROPERTY_CHANNEL;
    ice_streamer_handler_metadata_set_str (handle, metakey, header);
}

void
header_handler_icy_genre (ice_streamer_handler_t * handle,
                          char *header)
{
    const char *metakey = ICE_MEDIALIB_ENTRY_PROPERTY_GENRE;
    ice_streamer_handler_metadata_set_str (handle, metakey, header);
}

void
ice_streamer_curl_free_data (ice_streamer_curl_data_t *data)
{
    frost_return_if_fail (data);

    curl_multi_cleanup (data->curl_multi);
    curl_easy_cleanup (data->curl_easy);

    curl_slist_free_all (data->http_200_aliases);
    curl_slist_free_all (data->http_req_headers);

    frost_free (data->buffer);
    frost_free (data->header);

    frost_free (data->url);
    frost_free (data);

    g_handle = NULL; //@@++-- 20120727 g_handle must be reset so that we can get the correct handle at second play | ilters
}

frost_int64
ice_streamer_curl_seek (ice_streamer_handler_t * handle, frost_int64 offset,
                ice_seek_mode_t whence, ice_error_code_t *error)
{

     TRACE_INFO(("  ############ ice_streamer_curl_seek  whence =%d   offset:%lld, handle: %d\n",whence, offset, handle));


    ice_streamer_curl_data_t * data = (ice_streamer_curl_data_t *)ice_streamer_private_data_get(handle);

    if(g_seek_enable && g_handle && data ) //at this point whence should be ICE_SEEK_SET
    {
        ice_streamer_curl_free_data(data);

        g_offset = offset ;

        handle->download_header_disabled = frost_true;

        if(ice_streamer_curl_init(handle ))
        {
            TRACE_INFO(("============> Asf init successfull for seek \n"));
            return offset;
        }
        else
        {
            ice_streamer_error_set (error, ICE_ERROR_INVAL, "Curl init failed for seek");
            return -1;
        }
    }
    else
    {
        ice_streamer_error_set (error, ICE_ERROR_INVAL, "Couldn't seek");
        return -1;
    }

}

void
ice_streamer_curl_seek_enable(frost_bool enable)
{
    if(g_seek_enable != enable)
    {
        g_seek_enable = enable ;
    }

}
ICE_STREAMER_XFORM_BUILTIN (curl,
                           "Curl Transport for HTTP",
                           ICE_STREAMER_VERSION,
                           "HTTP transport using CURL",
                           ice_streamer_curl_plugin_setup);

const ice_streamer_plugin_desc_t * getCurlPluginDesc()
{
    return &ice_streamer_builtin_curl ;
}
