/**********************************************************************/
/* wrap some common curl operations for use with OpenCOBOL 1.1        */
/* Author:    Brian Tiffin                                            */
/* Date:      21-July-2008, 24-Nov-2008                               */
/* Version:   0.5                                                     */
/* Purpose:   Provide some net access to OpenCOBOL                    */
/* Tectonics: gcc -c occurl.c                                         */
/*            with libcurl dev installed                              */
/**********************************************************************/
#include <stdio.h>
#include <sys/stat.h>
#include <curl/curl.h>

/* A release bump changed/merged some error codes, and this is a hack */
#ifndef CURLE_REMOTE_FILE_NOT_FOUND
#define CURLE_REMOTE_FILE_NOT_FOUND 78
#endif


/* Support structure for the file callbacks */
struct LocalFileStruc {
    const char *filename;
    FILE *stream;
};

/* Progress tracking */
double *Bar;

/* libcurl call back for file write */
static size_t wrap_fwrite(void *buffer, size_t size, size_t nmemb, void *stream) {
    struct LocalFileStruc *out=(struct LocalFileStruc *)stream;
    if(out && !out->stream) {
        /* open file for writing */
        out->stream=fopen(out->filename, "wb");
        if(!out->stream)
            return 0; /* failure, can't open file to write */
    }
    return fwrite(buffer, size, nmemb, out->stream);
}

/* Progress */
int progress_callback(char *Bar, double t, double d, double ultotal, double ulnow) {
    int oe;
    double val;
    if (t == 0) t = 1.0;
    val = d / t * 100;
    oe = (int)val;
    if (oe & 1) {
        putc('\\', stdout);
    } else {
        putc('/', stdout);
    }
    printf("%03.0f%%\b\b\b\b\b\b\b\b", val);
    fflush(stdout);
    return 0;
}

/* Routines to get and release CURL handles from OpenCOBOL */
/* Usage: */
/*   DATA DIVISION. */
/*   01 handle usage is pointer. */
/*   PROCEDURE DIVISION. */
/*   CALL "occurl_init" RETURNING handle. */ 
CURL* CBL_OC_CURL_INIT() {
    return curl_easy_init();
}

/* Usage: */
/* CALL "occurl_cleanup" USING BY VALUE handle. */
void CBL_OC_CURL_CLEANUP(CURL *curl) {
    curl_easy_cleanup(curl);
}

/* Set verbosity */
/* Usage: vflag being 0 or 1 */
/* CALL "CBL_OC_CURL_VERBOSE" USING BY VALUE vflag. */
void CBL_OC_CURL_VERBOSE(CURL *curl, int vflag) {
    /* Switch on or off full protocol/debug output */
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_VERBOSE, (long)vflag);
    }
}

/* Set progress display on/off */
/* Usage: pflag being 0 or 1 */
/* CALL "CBL_OC_CURL_PROGRESS" USING BY VALUE pflag. */
void CBL_OC_CURL_PROGRESS(CURL *curl, int pflag) {
    /* Switch on or off progress display */
    if (curl) {
        if (pflag) {
            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
            curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
            curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &Bar);
	} else {
            curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
            curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, NULL);
            curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL);
        }
    }
}


/* Ease of use; check mod-times, if since then read url, write to file */ 
/* Retrieve URL and save to local file after checking timestamps */
/* Usage: */
/* DATA DIVISION. */
/* 01 handle USAGE IS POINTER. */
/* 01 url. */
/*   02 urlname PIC x(21) VALUE "http://opencobol.org/". */
/*   02 filler  PIC x VALUE X"00". */
/* 01 filename */
/*    02 PIC x(). */
/*    02 filler pic x value low-value. */
/* 01 modtime USAGE IS BINARY-C-LONG */
/* PROCEDURE DIVISION. */
/* CALL "CBL_OC_CURL_RETRIEVE_FILE" USING BY VALUE handle */
/*                                 BY REFERENCE url */
/*                                 BY REFERENCE filename */
/*                                 BY REFERENCE modtime */
/*                           RETURNING result. */
/* Pass modtime of 0 to get local mtime field, */
/*   modtime is modified with value from url if available */
int CBL_OC_CURL_RETRIEVE_FILE(CURL *curl, char *url, char *file, long *modtime) {
    CURLcode res;
    long urlstamp;

    struct stat st;

    struct LocalFileStruc localfile={
        "occurl.default",                 /* default filename */
        NULL                              /* stream */
    };

    /* point to the COBOL passed filename */
    if (file) {
        localfile.filename = file;
    }

    /* if modtime is zero, get local file modtime */
    if (*modtime == 0) {
        if (stat (file, &st) == 0) {
	    *modtime = st.st_mtime;
	}
    }

    /* let libcurl do all the real work */
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        /* Only fetch new */
        curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
        curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
        curl_easy_setopt(curl, CURLOPT_TIMEVALUE, *modtime);
        /* Define our callback to get called when there's data to be written */
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wrap_fwrite);
        /* Set a pointer to our struct to pass to the callback */
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &localfile);

        /* After all the setopts, perform the operation */
        res = curl_easy_perform(curl);

        /* close off any file pointers */
        if (localfile.stream) {
            fclose(localfile.stream); /* close the local file */
        }

        /* return error results */
        if (res != 0) {
	        return (int)res;
        }

        /* retrieve response code */
        res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &urlstamp);
        if (res != 0) {
            return (int)res;
        } else {
            if (urlstamp == 404) {
                return CURLE_REMOTE_FILE_NOT_FOUND;
	        }
	    }

        /* retrieve filetime for return value */
	    res = curl_easy_getinfo(curl, CURLINFO_FILETIME, &urlstamp);
	    if (res != 0) {
	        return (int)res;
	    } else {
	        *modtime = urlstamp;
            return 0;
        }
    } else {
        return CURLE_FAILED_INIT;
    }
    return 0;
}

/* Fetch a url to a local file */
int CBL_OC_CURL_GET_URL(CURL *curl, char *theurl, char *thefile) {
    CURLcode res;
    long urlstamp;

    struct LocalFileStruc localfile = {
        "occurl.default",         /* default filename */
        NULL                      /* stream */
    };

    /* point to the COBOL field */
    localfile.filename = thefile;

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, theurl);
        curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
        /* Define our callback to get called when there's data to be written */
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wrap_fwrite);
        /* Set a pointer to our struct to pass to the callback */
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &localfile);

        res = curl_easy_perform(curl);

        /* close the local file */
        if (localfile.stream) {
            fclose(localfile.stream);
        }

        /* return error results */
        if (res != 0) {
            return (int)res;
        }

        /* retrieve response code */
        res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &urlstamp);
        if (res != 0) {
            return (int)res;
        } else {
            if (urlstamp == 404) {
                return CURLE_REMOTE_FILE_NOT_FOUND;
            }
        }
    } else {
        return CURLE_FAILED_INIT;
    }
    return 0;
}

/* The plan is to return a structure with all curl INFO fields filled */
int CBL_OC_CURL_GETINFO(CURL *curl, char *theurl, char *thedata) {
    CURLcode res;
    long urlstamp;

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, theurl);
        res = curl_easy_getinfo(curl, CURLINFO_FILETIME, &urlstamp);
    } else {
        return -1;
    }
    return (int)res;
}
