/*
 * multi.c --
 *
 * Implementation of the part of the TclCurl extension that deals with libcurl's
 * 'multi' interface.
 *
 * Copyright (c)2002-2011 Andres Garcia Garcia.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

#include "multi.h"
#include "curl_mime.h"

#ifndef _WIN32
#include <sys/time.h>
#endif

typedef int (*TclCurlMultiCommandProc)(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);

typedef int (*TclCurlMultiConfigProc)(Tcl_Interp *interp,
        CURLM *curlMultiHandle, CURLMoption opt, int tableIndex,
        Tcl_Obj *tclObj);

struct TclCurlMultiConfigDispatch {
    CURLMoption              option;
    TclCurlMultiConfigProc   proc;
};

static int TclCurl_MultiDispatchAddHandle(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchRemoveHandle(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchPerform(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchCleanup(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchGetInfo(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchActive(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchAuto(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);
static int TclCurl_MultiDispatchConfigure(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]);

static const TclCurlMultiCommandProc multiCommandDispatch[TCLCURL_MULTI_CMD_COUNT] = {
#define TCLCURL_MULTI_COMMAND_DISPATCH_ENTRY(name, label, proc) [name] = proc,
    TCLCURL_MULTI_COMMAND_LIST(TCLCURL_MULTI_COMMAND_DISPATCH_ENTRY)
#undef TCLCURL_MULTI_COMMAND_DISPATCH_ENTRY
};

static const struct TclCurlMultiConfigDispatch multiConfigDispatch[TCLCURL_MULTI_CONFIG_COUNT] = {
#define TCLCURL_MULTI_CONFIG_DISPATCH_ENTRY(name, label, opt, proc) [name] = { opt, proc },
    TCLCURL_MULTI_CONFIG_LIST(TCLCURL_MULTI_CONFIG_DISPATCH_ENTRY)
#undef TCLCURL_MULTI_CONFIG_DISPATCH_ENTRY
};

/*
 *----------------------------------------------------------------------
 *
 * Tclcurl_MultiInit --
 *
 *	This procedure initializes the 'multi' part of the package
 *
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
Tclcurl_MultiInit (Tcl_Interp *interp) {

    Tcl_CreateObjCommand (interp,"::curl::multiinit",curlInitMultiObjCmd,
                         (ClientData)NULL,(Tcl_CmdDeleteProc *)NULL);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * curlCreateMultiObjCmd --
 *
 *	Looks for the first free handle (mcurl1, mcurl2,...) and creates a
 *	Tcl command for it.
 *
 * Results:
 *  A string with the name of the handle, don't forget to free it.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

char *
curlCreateMultiObjCmd (Tcl_Interp *interp,struct curlMultiObjData *curlMultiData) {
    char                *handleName;
    int                 i;
    Tcl_CmdInfo         info;
    Tcl_Command         cmdToken;

    /* We try with mcurl1, if it already exists with mcurl2, ... */
    handleName=(char *)Tcl_Alloc(10);
    for (i=1;;i++) {
        sprintf(handleName,"mcurl%d",i);
        if (!Tcl_GetCommandInfo(interp,handleName,&info)) {
            cmdToken=Tcl_CreateObjCommand(interp,handleName,curlMultiObjCmd,
                                            (ClientData)curlMultiData,
                                            (Tcl_CmdDeleteProc *)curlMultiDeleteCmd);
            break;
        }
    }

    curlMultiData->token=cmdToken;
    return handleName;
}

/*
 *----------------------------------------------------------------------
 *
 * curlInitMultiObjCmd --
 *
 *	This procedure is invoked to process the "curl::multiInit" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
curlInitMultiObjCmd (ClientData clientData, Tcl_Interp *interp,
        int objc,Tcl_Obj *const objv[]) {


    Tcl_Obj                     *result;
    struct curlMultiObjData     *curlMultiData;
    char                        *multiHandleName;

    curlMultiData=(struct curlMultiObjData *)Tcl_Alloc(sizeof(struct curlMultiObjData));
    if (curlMultiData==NULL) {
        result=Tcl_NewStringObj("Couldn't allocate memory",-1);
        Tcl_SetObjResult(interp,result); 
        return TCL_ERROR;
    }

    memset(curlMultiData, 0, sizeof(struct curlMultiObjData));
    curlMultiData->interp=interp;

    curlMultiData->mcurl=curl_multi_init();

    if (curlMultiData->mcurl==NULL) {
        result=Tcl_NewStringObj("Couldn't open curl multi handle",-1);
        Tcl_SetObjResult(interp,result); 
        return TCL_ERROR;
    }

    multiHandleName=curlCreateMultiObjCmd(interp,curlMultiData);

    result=Tcl_NewStringObj(multiHandleName,-1);
    Tcl_SetObjResult(interp,result);
    Tcl_Free(multiHandleName);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiObjCmd --
 *
 *	This procedure is invoked to process the "multi curl" commands.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
int
curlMultiObjCmd (ClientData clientData, Tcl_Interp *interp,
    int objc,Tcl_Obj *const objv[]) {

    struct curlMultiObjData    *curlMultiData=(struct curlMultiObjData *)clientData;
    int                         tableIndex;

    if (objc<2) {
        Tcl_WrongNumArgs(interp,1,objv,"option arg ?arg?");
        return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], multiCommandTable, "option",
            TCL_EXACT,&tableIndex)==TCL_ERROR) {
        return TCL_ERROR;
    }
    return multiCommandDispatch[tableIndex](interp,curlMultiData,objc,objv);
}

static int
TclCurl_MultiDispatchAddHandle(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    CURLMcode errorCode;

    errorCode=curlAddMultiHandle(interp,curlMultiData->mcurl,objv[2]);
    return curlReturnCURLMcode(interp,errorCode);
}

static int
TclCurl_MultiDispatchRemoveHandle(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    CURLMcode errorCode;

    errorCode=curlRemoveMultiHandle(interp,curlMultiData->mcurl,objv[2]);
    return curlReturnCURLMcode(interp,errorCode);
}

static int
TclCurl_MultiDispatchPerform(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    return curlMultiPerform(interp,curlMultiData->mcurl);
}

static int
TclCurl_MultiDispatchCleanup(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    Tcl_DeleteCommandFromToken(interp,curlMultiData->token);
    return TCL_OK;
}

static int
TclCurl_MultiDispatchGetInfo(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    curlMultiGetInfo(interp,curlMultiData->mcurl);
    return TCL_OK;
}

static int
TclCurl_MultiDispatchActive(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    curlMultiActiveTransfers(interp,curlMultiData);
    return TCL_OK;
}

static int
TclCurl_MultiDispatchAuto(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    curlMultiAutoTransfer(interp,curlMultiData,objc,objv);
    return TCL_OK;
}

static int
TclCurl_MultiDispatchConfigure(Tcl_Interp *interp,
        struct curlMultiObjData *curlMultiData, int objc,
        Tcl_Obj *const objv[]) {
    curlMultiConfigTransfer(interp,curlMultiData,objc,objv);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * curlAddMultiHandle --
 *
 *	Adds an 'easy' curl handle to the stack of a 'multi' handle.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      curlMultiHandle: The handle into which we will add the easy one.
 *      objvPtr: The Tcl object with the name of the easy handle.
 *
 * Results:
 *  '0' all went well.
 *  'non-zero' in case of error.
 *----------------------------------------------------------------------
 */
CURLMcode
curlAddMultiHandle(Tcl_Interp *interp,CURLM *curlMultiHandlePtr
        ,Tcl_Obj *objvPtr) {

    struct curlObjData        *curlDataPtr;
    CURLMcode                  errorCode;


    curlDataPtr=curlGetEasyHandle(interp,objvPtr);

    if (curlOpenFiles(interp,curlDataPtr)) {
        return TCL_ERROR;
    }
    if (TclCurl_SetPostData(interp,curlDataPtr)) {
        return TCL_ERROR;
    }

    errorCode=curl_multi_add_handle(curlMultiHandlePtr,curlDataPtr->curl);

    curlEasyHandleListAdd(curlMultiHandlePtr,curlDataPtr->curl
            ,Tcl_GetString(objvPtr));

    return errorCode;
}

/*
 *----------------------------------------------------------------------
 *
 * curlRemoveMultiHandle --
 *
 *	Removes an 'easy' curl handle to the stack of a 'multi' handle.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      curlMultiHandle: The handle into which we will add the easy one.
 *      objvPtr: The Tcl object with the name of the easy handle.
 *
 * Results:
 *  '0' all went well.
 *  'non-zero' in case of error.
 *----------------------------------------------------------------------
 */
CURLMcode
curlRemoveMultiHandle(Tcl_Interp *interp,CURLM *curlMultiHandle
        ,Tcl_Obj *objvPtr) {
    struct curlObjData        *curlDataPtr;
    CURLMcode                  errorCode;

    curlDataPtr=curlGetEasyHandle(interp,objvPtr);
    errorCode=curl_multi_remove_handle(curlMultiHandle,curlDataPtr->curl);
    curlEasyHandleListRemove(curlMultiHandle,curlDataPtr->curl);

    curlCloseFiles(curlDataPtr);
    TclCurl_ResetPostData(curlDataPtr);

    if (curlDataPtr->bodyVarName) {
        curlSetBodyVarName(interp,curlDataPtr);
    }

    return errorCode;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiPerform --
 *
 *	Invokes the 'curl_multi_perform' function to update the current
 *  transfers.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      curlMultiHandle: The handle of the transfer to update.
 *      objvPtr: The Tcl object with the name of the easy handle.
 *
 * Results:
        Usual Tcl result.
 *----------------------------------------------------------------------
 */
int
curlMultiPerform(Tcl_Interp *interp,CURLM *curlMultiHandlePtr) {

    CURLMcode        errorCode;
    int              runningTransfers;

    for (errorCode=-1;errorCode<0;) {   
        errorCode=curl_multi_perform(curlMultiHandlePtr,&runningTransfers);
    }

    if (errorCode==0) {
        curlReturnCURLMcode(interp,runningTransfers);
        return TCL_OK;
    }

    curlReturnCURLMcode(interp,errorCode);

    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiDeleteCmd --
 *
 *	This procedure is invoked when curl multi handle is deleted.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Cleans the curl handle and frees the memory.
 *
 *----------------------------------------------------------------------
 */
int
curlMultiDeleteCmd(ClientData clientData) {
    struct curlMultiObjData     *curlMultiData=(struct curlMultiObjData *)clientData;
    CURLM                       *curlMultiHandle=curlMultiData->mcurl;
    CURLMcode                    errorCode;
    Tcl_Interp                  *interp=curlMultiData->interp;
    struct easyHandleList       *listPtr1,*listPtr2;

    listPtr1=curlMultiData->handleListFirst;
    while (listPtr1!=NULL) {
        listPtr2=listPtr1->next;
        Tcl_Free(listPtr1->name);
        Tcl_Free((char *)listPtr1); 
        listPtr1=listPtr2;
    }
    errorCode=curl_multi_cleanup(curlMultiHandle);
    curlMultiFreeSpace(curlMultiData);
    return curlReturnCURLMcode(interp,errorCode);
}

/*
 *----------------------------------------------------------------------
 *
 * curlGetMultiInfo --
 *    Invokes the curl_multi_info_read function in libcurl to get
 *    some info about the transfer, like if they are done and
 *    things like that.
 *
 * Parameter:
 *    interp: The Tcl interpreter we are using, mainly to report errors.
 *    curlMultiHandlePtr: Pointer to the multi handle of the transfer.
 *
 * Results:
 *    Standard Tcl codes. The Tcl command will return a list with the
 *    name of the Tcl command and other info.
 *----------------------------------------------------------------------
 */
int
curlMultiGetInfo(Tcl_Interp *interp,CURLM *curlMultiHandlePtr) {
    struct CURLMsg        *multiInfo;
    int                    msgLeft;
    Tcl_Obj               *resultPtr;

    multiInfo=curl_multi_info_read(curlMultiHandlePtr, &msgLeft);
    resultPtr=Tcl_NewListObj(0,(Tcl_Obj **)NULL); 
    if (multiInfo==NULL) {
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewStringObj("",-1));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(0));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(0));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(0));
    } else {
        Tcl_ListObjAppendElement(interp,resultPtr,
            Tcl_NewStringObj(curlGetEasyName(curlMultiHandlePtr,multiInfo->easy_handle),-1));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(multiInfo->msg));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(multiInfo->data.result));
        Tcl_ListObjAppendElement(interp,resultPtr,Tcl_NewIntObj(msgLeft));
    }
    Tcl_SetObjResult(interp,resultPtr); 

    return TCL_OK;            
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiActiveTransfers --
 *    This function is used to know whether an connection is ready to
 *    transfer data. This code has been copied almost verbatim from
 *    libcurl's examples.
 *
 * Parameter:
 *    multiHandlePtr: Pointer to the multi handle of the transfer.
 *
 * Results:
 *    
 *----------------------------------------------------------------------
 */
int
curlMultiGetActiveTransfers( struct curlMultiObjData *curlMultiData) {
    struct timeval  timeout;
    int             selectCode;
    int             maxfd;

    FD_ZERO(&(curlMultiData->fdread));
    FD_ZERO(&(curlMultiData->fdwrite));
    FD_ZERO(&(curlMultiData->fdexcep));

    /* set a suitable timeout to play around with */
    timeout.tv_sec  = 1;
    timeout.tv_usec = 0;

    /* get file descriptors from the transfers */
    curl_multi_fdset(curlMultiData->mcurl,
            &(curlMultiData->fdread),
            &(curlMultiData->fdwrite),
            &(curlMultiData->fdexcep), &maxfd);

    selectCode = select(maxfd+1, &(curlMultiData->fdread)
            , &(curlMultiData->fdwrite), &(curlMultiData->fdexcep)
            , &timeout);

    return selectCode;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiActiveTransfers --
 *    Implements the Tcl 'active', it queries the multi handle to know
 *    if any of the connections are ready to transfer data.
 *
 * Parameter:
 *    interp: The Tcl interpreter we are using, mainly to report errors.
 *    curlMultiHandlePtr: Pointer to the multi handle of the transfer.
 *
 * Results:
 *    Standard Tcl codes. The Tcl command will return the number of
 *    transfers.
 *----------------------------------------------------------------------
 */
int
curlMultiActiveTransfers(Tcl_Interp *interp, struct curlMultiObjData *curlMultiData) {
    int             selectCode;
    Tcl_Obj        *resultPtr;

    selectCode = curlMultiGetActiveTransfers(curlMultiData);

    if (selectCode==-1) {
        return TCL_ERROR;
    }

    resultPtr=Tcl_NewIntObj(selectCode);
    Tcl_SetObjResult(interp,resultPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * curlGetEasyHandle --
 *
 *	Given the name of an easy curl handle (curl1,...), in a Tcl object
 *  this function  will return the pointer the 'internal' libcurl handle.
 *
 * Parameter:
 *  The Tcl object with the name.
 *
 * Results:
 *  The pointer to the libcurl handle
 *----------------------------------------------------------------------
 */
struct curlObjData *
curlGetEasyHandle(Tcl_Interp *interp,Tcl_Obj *nameObjPtr) {

    char                    *handleName;
    Tcl_CmdInfo             *infoPtr=(Tcl_CmdInfo *)Tcl_Alloc(sizeof(Tcl_CmdInfo));
    struct curlObjData      *curlDataPtr;

    handleName=Tcl_GetString(nameObjPtr);

    if (0==Tcl_GetCommandInfo(interp,handleName,infoPtr)) {
        return NULL;
    }
    curlDataPtr=(struct curlObjData *)(infoPtr->objClientData);
    Tcl_Free((char *)infoPtr);
    return curlDataPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiFreeSpace --
 *
 *	Frees the space taken by a curlMultiObjData struct.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      curlMultiHandle: the curl handle for which the option is set.
 *      objc and objv: The usual in Tcl.
 *
 * Results:
 *  A standard Tcl result.
 *----------------------------------------------------------------------
 */
void
curlMultiFreeSpace(struct curlMultiObjData *curlMultiData) {

    curl_multi_cleanup(curlMultiData->mcurl);

    Tcl_Free(curlMultiData->postCommand);
    Tcl_Free((char *)curlMultiData);
}

/*
 *----------------------------------------------------------------------
 *
 * curlEasyHandleListAdd
 *	Adds an easy handle to the list of handles in a multiHandle.
 *
 *  Parameter:
 *      multiDataPtr: Pointer to the struct of the multi handle.
 *      easyHandle: The easy handle to add to the list.
 *
 * Results:
 *----------------------------------------------------------------------
 */
void
curlEasyHandleListAdd(struct curlMultiObjData *multiDataPtr,CURL *easyHandlePtr,char *name) {
    struct easyHandleList    *easyHandleListPtr;

    easyHandleListPtr=(struct easyHandleList *)Tcl_Alloc(sizeof(struct easyHandleList));
    easyHandleListPtr->curl   =easyHandlePtr;
    easyHandleListPtr->name   =curlstrdup(name);
    easyHandleListPtr->next=NULL;
    if (multiDataPtr->handleListLast==NULL) {
        multiDataPtr->handleListFirst=easyHandleListPtr;
        multiDataPtr->handleListLast =easyHandleListPtr;
    } else {
        multiDataPtr->handleListLast->next=easyHandleListPtr;
        multiDataPtr->handleListLast=easyHandleListPtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * curlEasyHandleListRemove
 *	When we remove an easy handle from the multiHandle, this function
 *  will remove said handle from the linked list.
 *
 *  Parameter:
 *      multiDataPtr: Pointer to the struct of the multi handle.
 *      easyHandle: The easy handle to add to the list.
 *
 * Results:
 *----------------------------------------------------------------------
 */
void
curlEasyHandleListRemove(struct curlMultiObjData *multiDataPtr,CURL *easyHandle) {
    struct easyHandleList *listPtr1,*listPtr2;

    listPtr1=NULL;
    listPtr2=multiDataPtr->handleListFirst;
    while(listPtr2!=NULL) {
        if (listPtr2->curl==easyHandle) {
            if (listPtr1==NULL) {
                multiDataPtr->handleListFirst=listPtr2->next;
            } else {
                listPtr1->next=listPtr2->next;
            }
            if (listPtr2==multiDataPtr->handleListLast) {
                multiDataPtr->handleListLast=listPtr1;
            }
            Tcl_Free(listPtr2->name);
            Tcl_Free((char *)listPtr2);
            break;
        }
        listPtr1=listPtr2;
        listPtr2=listPtr2->next;
    }
}
/*
 *----------------------------------------------------------------------
 *
 * curlGetEasyName
 *
 *	Given the pointer to an easy handle, this function will return
 *  the name of the Tcl command.
 *
 *  Parameter:
 *      multiDataPtr: Multi handle we are using.
 *      easyHandle: The easy handle whose Tcl command we want to know.
 *
 * Results:
 *  A string with the name of the command.
 *----------------------------------------------------------------------
 */
char *
curlGetEasyName(struct curlMultiObjData *multiDataPtr,CURL *easyHandle) {
    struct easyHandleList    *listPtr;

    listPtr=multiDataPtr->handleListFirst;
    while(listPtr!=NULL) {
        if (listPtr->curl==easyHandle) {
            return listPtr->name;
        }
        listPtr=listPtr->next;
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * curlReturnCURLMcode
 *
 *	When one of the command wants to return a CURLMcode, it calls
 *  this function.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      errorCode: the value to be returned.
 *
 * Results:
 *  A standard Tcl result.
 *----------------------------------------------------------------------
 */
int
curlReturnCURLMcode (Tcl_Interp *interp,CURLMcode errorCode) {
    Tcl_Obj        *resultPtr;

    resultPtr=Tcl_NewIntObj(errorCode);
    Tcl_SetObjResult(interp,resultPtr);

    if (errorCode>0) {
        return TCL_ERROR;
    }
    return TCL_OK;
}


/*----------------------------------------------------------------------
 *
 * curlMultiAutoTransfer --
 *
 *	Creates the event source that will take care of downloading using
 *  the multi interface driven by Tcl's event loop.
 *
 * Parameters:
 *  The usual Tcl command parameters.
 *
 * Results:
 *	Standard Tcl return code.
 *----------------------------------------------------------------------
 */

int
curlMultiAutoTransfer(Tcl_Interp *interp, struct curlMultiObjData *curlMultiData,
        int objc,Tcl_Obj *const objv[]) {

    if (objc==4) {
        Tcl_Free(curlMultiData->postCommand);
        curlMultiData->postCommand=curlstrdup(Tcl_GetString(objv[3]));
    }

    Tcl_CreateEventSource((Tcl_EventSetupProc *)curlEventSetup, 
            (Tcl_EventCheckProc *)curlEventCheck, (ClientData *)curlMultiData);

    /* We have to call perform once to boot the transfer, otherwise it seems nothing
       works *shrug* */

    while(CURLM_CALL_MULTI_PERFORM ==
            curl_multi_perform(curlMultiData->mcurl,&(curlMultiData->runningTransfers))) {
    }

    return TCL_OK;
}

/*----------------------------------------------------------------------
 *
 * curlMultiConfigTrasnfer --
 *  This procedure is invoked by the user command 'configure', it reads 
 *  the options passed by the user to configure a multi handle.
 *
 * Parameters:
 *  The usual Tcl command parameters.
 *
 * Results:
 *	Standard Tcl return code.
 *----------------------------------------------------------------------
 */

int
curlMultiConfigTransfer(Tcl_Interp *interp, struct curlMultiObjData *curlMultiData,
        int objc,Tcl_Obj *const objv[]) {
    int            tableIndex;
    int            i,j;

    Tcl_Obj       *resultPtr;
    char           errorMsg[500];

    for(i=2,j=3;i<objc;i=i+2,j=j+2) {
        if (Tcl_GetIndexFromObj(interp, objv[i], multiConfigTable, "option", 
                TCL_EXACT, &tableIndex)==TCL_ERROR) {
            return TCL_ERROR;
        }
        if (i==objc-1) {
            snprintf(errorMsg,500,"Empty value for %s",multiConfigTable[tableIndex]);
            resultPtr=Tcl_NewStringObj(errorMsg,-1);
            Tcl_SetObjResult(interp,resultPtr);            
            return TCL_ERROR;
        }
        if (curlMultiSetOpts(interp,curlMultiData,objv[j],tableIndex)==TCL_ERROR) {
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * curlMultiSetOpts --
 *
 *	This procedure takes care of setting the transfer options.
 *
 *  Parameter:
 *      interp: Pointer to the interpreter we are using.
 *      curlMultiHandle: the curl handle for which the option is set.
 *      objv: A pointer to the object where the data to set is stored.
 *      tableIndex: The index of the option in the options table.
 *
 * Results:
 *  A standard Tcl result.
 *----------------------------------------------------------------------
 */
int
curlMultiSetOpts(Tcl_Interp *interp, struct curlMultiObjData *curlMultiData,
        Tcl_Obj *const objv,int tableIndex) {

    if (multiConfigDispatch[tableIndex].proc(interp,curlMultiData->mcurl,
            multiConfigDispatch[tableIndex].option,tableIndex,objv)) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SetMultiOptLong --
 *
 *	Set the curl options that require a long
 *
 *  Parameter:
 *      interp: The interpreter we are working with.
 *      curlMultiHandle: and the multi curl handle
 *      opt: the option to set
 *      tclObj: The Tcl with the value for the option.
 *
 * Results:
 *  0 if all went well.
 *  1 in case of error.
 *----------------------------------------------------------------------
 */
int
SetMultiOptLong(Tcl_Interp *interp,CURLM *curlMultiHandle,CURLMoption opt,
        int tableIndex,Tcl_Obj *tclObj) {
    long        longNumber;
    char        *parPtr;

    if (Tcl_GetLongFromObj(interp,tclObj,&longNumber)) {
        parPtr=curlstrdup(Tcl_GetString(tclObj));
        curlErrorSetOpt(interp,multiConfigTable,tableIndex,parPtr);
        Tcl_Free(parPtr);
        return 1;
    }
    if (curl_multi_setopt(curlMultiHandle,opt,longNumber)) {
        parPtr=curlstrdup(Tcl_GetString(tclObj));
        curlErrorSetOpt(interp,multiConfigTable,tableIndex,parPtr);
        Tcl_Free(parPtr);
        return 1;
    }
    return 0;
}

/*----------------------------------------------------------------------
 *
 * curlEventSetup --
 *
 *  This function is invoked by Tcl just after curlMultiAutoTransfer and
 *  then every time just before curlEventCheck, I only use to set the
 *  maximun time without checking for events
 *
 *  NOTE: I hate having a fixed value, I will have to look into it.
 *
 * Parameters:
 *  They are passed automagically by Tcl, but I don't use them.
 *----------------------------------------------------------------------
 */

void
curlEventSetup(ClientData clientData, int flags) {
    Tcl_Time     time = {0 , 0};

    Tcl_SetMaxBlockTime(&time);
}

/*----------------------------------------------------------------------
 *
 * curlEventCheck --
 *
 *	Invoked automagically by Tcl from time to time, we check if there
 *  are any active transfer, if so we put an event in the queue so that
 *  'curl_multi_perfom' will be eventually called, if not we delete
 *  the event source.
 *
 * Parameters:
 *  They are passed automagically by Tcl.
 *----------------------------------------------------------------------
 */

void
curlEventCheck(ClientData clientData, int flags) {
    struct curlMultiObjData    *curlMultiData = (struct curlMultiObjData *)clientData;
    struct curlEvent           *curlEventPtr;
    int                         selectCode;

    selectCode = curlMultiGetActiveTransfers(curlMultiData);

    if (curlMultiData->runningTransfers == 0) {
        Tcl_DeleteEventSource((Tcl_EventSetupProc *)curlEventSetup, 
                (Tcl_EventCheckProc *)curlEventCheck, (ClientData *)curlMultiData);
    } else {
        if (selectCode >= 0) {
            curlEventPtr=(struct curlEvent *)Tcl_Alloc(sizeof(struct curlEvent));
            curlEventPtr->proc=curlEventProc;
            curlEventPtr->curlMultiData=curlMultiData;
            Tcl_QueueEvent((Tcl_Event *)curlEventPtr, TCL_QUEUE_TAIL);
        }
    }
}

/*----------------------------------------------------------------------
 *
 * curlEventProc --
 *
 *	Finally Tcl event loop decides it is time to transfer something.
 *
 * Parameters:
 *  They are passed automagically by Tcl.
 *----------------------------------------------------------------------
*/ 

int
curlEventProc(Tcl_Event *evPtr,int flags) {
    struct curlMultiObjData   *curlMultiData;
    //CURLMcode                errorCode;
    Tcl_Obj                   *tclCommandObjPtr;
    char                       tclCommand[300];

    curlMultiData = (struct curlMultiObjData *)((struct curlEvent *)evPtr)->curlMultiData;
    //errorCode=curl_multi_perform(curlMultiData->mcurl,&curlMultiData->runningTransfers);
    curl_multi_perform(curlMultiData->mcurl,&curlMultiData->runningTransfers);
    if (curlMultiData->runningTransfers==0) {
        if (curlMultiData->postCommand!=NULL) {
            snprintf(tclCommand,299,"%s",curlMultiData->postCommand);
            tclCommandObjPtr=Tcl_NewStringObj(tclCommand,-1);
            if (Tcl_EvalObjEx(curlMultiData->interp,tclCommandObjPtr,TCL_EVAL_GLOBAL)!=TCL_OK) {
/*
                fprintf(stdout,"Error invoking command\n");
                fprintf(stdout,"Error: %s\n",Tcl_GetString(Tcl_GetObjResult(curlMultiData->interp)));
*/
            }
        }
    }
    return 1;
}
