// Code to parse list of external tools and redirect to it via a hidden form */
//
// The link from hgTracks has the form ?hgt.redirectTool=<name>
// Note that you can add the parameter &debug=1 to the URL to see and modify the hidden form
// cgi-bin/extTools.ra defines the parameters and labels of the tools

/* Copyright (C) 2014 The Regents of the University of California 
 * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */

#include "common.h"
#include "dystring.h"
#include "hCommon.h"
#include "htmshell.h"
#include "hash.h"
#include "web.h"
#include "ra.h"
#include "hgTracks.h"
#include "extTools.h"
#include "hgFind.h"
#include "obscure.h"
#include "net.h"

boolean extToolsEnabled()
/* Return TRUE if we can display the external tools menu. */
{
return fileExists("extTools.ra");
}

void printExtMenuData(char *chromName) 
/* print the external tools aka "send to" menu entries as a javascript list to stdout */
{
if (!extToolsEnabled())
    return;

struct extTool *extTools = NULL;

// we allow an alternative location in /gbdb/hgFixed
// This means that we do not have to do a build patch when 
// we have to change this .ra file and can do it with a simple
// file push to the RR
if (fileExists("/gbdb/hgFixed/extTools.ra"))
    extTools = readExtToolRa("/gbdb/hgFixed/extTools.ra");
else
    extTools = readExtToolRa("extTools.ra");

struct extTool *et;
struct dyString *dy = dyStringNew(1024);
dyStringAppend(dy, "extTools = [\n");
for(et = extTools; et != NULL; et = et->next)
    {
    // special case for alternate chroms on hg38, skip the Ensembl links
    if (chromName!=NULL && startsWith("ensembl", et->tool) && endsWith(chromName, "_alt"))
        continue;

    if (et->dbs!=NULL)
        {
        if (!slNameInList(et->dbs, database))
            continue;
        }
    if (et->notDbs!=NULL)
        {
        if (slNameInList(et->notDbs, database))
            continue;
        }
    char* tool = jsonStringEscape(et->tool);
    char* shortLabel = jsonStringEscape(et->shortLabel);
    char* longLabel = jsonStringEscape(et->longLabel);
    dyStringPrintf(dy, "    ['%s', '%s', '%s', %d]", tool, shortLabel, longLabel, et->maxSize);
    if (et->next)
        dyStringAppend(dy, ",");
    dyStringAppend(dy, "\n");
    }
dyStringAppend(dy, "];\n");

jsInline(dy->string);
dyStringFree(&dy);

}

static char *cloneRaSetting(struct hash *hash, char *name, struct lineFile *lf, bool mustExist)
/* clone a setting out of the ra hash.  errAbort if mustExit and not found, otherwise NULL. */
{
char *str;
if ((str = hashFindVal(hash, name)) == NULL) 
    {
    if (mustExist)
        errAbort("missing required setting '%s' on line %d in file %s\n",
            name, lf->lineIx, lf->fileName);
    else
        return NULL;
    }
return cloneString(str);
}

struct extTool *readExtToolRa(char *raFileName) 
/* parse the extTools.ra file. Inspired by trackHub.c:readGroupRa() */
{
struct lineFile *lf = udcWrapShortLineFile(raFileName, NULL, 1*1024*1024);
struct extTool *list = NULL;
//struct hash *ra;
struct slPair *raList;
while ((raList = raNextStanzAsPairs(lf)) != NULL)
    {
    struct extTool *et;
    AllocVar(et);

    struct slPair *paramList = NULL;

    struct hash *raHash;
    raHash = hashNew(0);

    // the "param" field is there more than once, so cannot use a hash for it
    struct slPair *raPair;
    for (raPair = raList; raPair != NULL; raPair = raPair->next)
        {
        if (sameWord(raPair->name, "param"))
            {
            // split the val into parameter name and value
            char *paramArr[2];
            int fCount = chopByWhite(raPair->val, paramArr, 2);
            // hacky way to allow spaces in parameter names and values
            char *name = replaceChars(paramArr[0], "%20", " ");
            char *val;
            if (fCount==2)
                val = cloneString(paramArr[1]);
            else
                val = cloneString("");
            val = replaceChars(val, "%20", " ");
            slPairAdd(&paramList, name, val);
            }
        else
            // add all other name/val pairs to the hash
            hashAdd(raHash, raPair->name, cloneString(raPair->val));
        }

    // pull out the required fields from the hash
    et->tool = cloneRaSetting(raHash, "tool", lf, TRUE);
    et->shortLabel = cloneRaSetting(raHash, "shortLabel", lf, TRUE);
    et->longLabel = cloneRaSetting(raHash, "longLabel", lf, TRUE);
    et->url = cloneRaSetting(raHash, "url", lf, TRUE);
    et->dbs = NULL;

    // optional fields
    et->email = cloneRaSetting(raHash, "email", lf, FALSE);
    if (hashFindVal(raHash, "onlyDbs"))
        et->dbs = commaSepToSlNames(hashFindVal(raHash, "onlyDbs"));
    if (hashFindVal(raHash, "notDbs"))
        et->notDbs = commaSepToSlNames(hashFindVal(raHash, "notDbs"));

    char *isHttpGet = hashFindVal(raHash, "isHttpGet");
    if (isHttpGet && \
     (sameString(isHttpGet, "yes") || sameString(isHttpGet, "on") || sameString(isHttpGet, "true")))
            et->isHttpGet = TRUE;

    char *maxSize = hashFindVal(raHash, "maxSize");
    if (maxSize!=NULL)
        et->maxSize = sqlUnsignedOrError(maxSize, "Error: maxSize %s for tool %s is not a number", \
                maxSize, et->tool);

    slReverse(&paramList);
    et->params = paramList;

    slAddHead(&list, et);
    freeHashAndVals(&raHash);
    slPairFree(&raList);
    }

if (list)
    slReverse(&list);
lineFileClose(&lf);
return list;
}

void extToolRedirect(struct cart *cart, char *tool)
/* redirect to an external tool, sending the data specified in the ext tools config file */
{
bool debug = cgiVarExists("debug");

struct extTool *extTools = readExtToolRa("extTools.ra");
struct extTool *et;
for (et = extTools; et != NULL; et = et->next)
    if (sameWord(et->tool, tool))
        break;
if (et==NULL)
    errAbort("No configuration found for tool %s", tool);


// construct an invisible CGI form with the given parameters
printf("<html>\n<head>\n");
generateCspMetaHeader(stdout);
printf("</head>\n<body>\n");

if (debug)
    printf("Target URL: %s<p>", et->url);

char *chromName;
int winStart, winEnd;
char *db = cartString(cart, "db");
char *pos = cartString(cart, "position");

// Try to deal with virt chrom position used by hgTracks.
if (isNotEmpty(pos) && 
        (startsWith(MULTI_REGION_CHROM, pos) || startsWith(OLD_MULTI_REGION_CHROM, pos)))
    pos = cartString(cart, "nonVirtPosition");

if (!parsePosition(pos, &chromName, (uint *)&winStart, (uint *)&winEnd))
    errAbort("Can't parse position '%s'", pos);
int len = winEnd-winStart;

char start1[255];
safef(start1, sizeof(start1), "%d", winStart+1);

char *url = replaceInUrl(et->url, "", cart, db, chromName, winStart, winEnd, NULL, TRUE, NULL);

char *method = "POST";
if (et->isHttpGet)
    method = "GET";

printf("<form id=\"redirForm\" method=\"%s\" action=\"%s\">\n", method, url);

struct slPair *slp;

if (et->maxSize!=0 && len > et->maxSize)
    {
    printf("Sorry, this tool accepts only a sequence with less than %d base pairs<p>\n"
      "Please zoom in some more.<p>\n", et->maxSize);
    return;
    }

printf("You're being redirected from the UCSC Genome Browser to the site %s<br>\n", url);
if (et->email)
    printf("Please contact %s for questions on this tool.<br>\n", et->email);

boolean submitDone = FALSE;
for (slp=et->params; slp!=NULL; slp=slp->next)
    {
    char* val = slp->val;
    if (sameWord(val, "$db"))
        val = db;
    else if (sameWord(val, "$position"))
        val = pos;
    else if (sameWord(val, "$start1"))
        val = start1;
    else if (sameWord(val, "$returnUrl"))
        {
        // get the full URL of this hgTracks page, so external page can construct a custom track
        // and link back to us
        char* host = getenv("HTTP_HOST");
        char* reqUrl = getenv("REQUEST_URI");
        // remove everything after ? in URL
        char *e = strchr(reqUrl, '?');
        if (e) *e = 0; 

        char url[4000];
        // cannot find a way to find out if the request came in via http or https
        safef(url, sizeof(url), "http://%s%s", host, reqUrl);
        val = url;
        }
    // half the current window size
    else if (stringIn("$halfLen", val))
        {
        char buf[64];
        safef(buf, sizeof(buf), "%d", len/2);
        val = replaceChars(val, "$halfLen", buf);
        }
    else if (sameWord(val, "$seq") || sameWord(val, "$faSeq"))
        {
        static struct dnaSeq *seq = NULL;
        seq = hDnaFromSeq(db, chromName, winStart, winEnd, dnaLower);
        if (sameWord(val, "$seq"))
            val = seq->dna;
        else
            {
            val = catTwoStrings(">sequence\n",seq->dna);
            freez(&seq);
            }
        }
    else if (sameWord(val, "$ncbiGca"))
        {
        char *gcf = hNcbiGcfId(db);	/* GCF takes priority over GCA */
        if (gcf)
            val = gcf;
        else
	    {
	    char *gca = hNcbiGcaId(db);
	    if (gca)
		val = gca;
	    else
    // Really we shouldn't be making this entire form... pass db as hail-mary
		val = db;
	    }
        }
    else if (sameWord(val, "$ncbiGcf"))
        {
        char *gcf = hNcbiGcfId(db);
        if (gcf)
            val = gcf;
        else
            // Really we shouldn't be making this entire form... pass db as hail-mary
            val = db;
        }
    // any remaining $-expression might be one of the general ones
    else if (stringIn("$", val))
        {
        val = replaceInUrl(val, "", cart, db, chromName, winStart, winEnd, NULL, TRUE, NULL);
        }

    // output
    if (debug)
        {
        printf("%s: ", slp->name);
        printf("<input name=\"%s\" value=\"%s\"><p>\n", slp->name, val);
        }
    else
        {
        // parameter named submit is a special case
        if (sameWord(slp->name, "submit"))
            {
            submitDone = TRUE;
            printf("<input type=\"submit\" value=\"%s\" style=\"position: absolute; left: -9999px; "
                "width: 1px; height: 1px;\">\n", val);
            }
        else
            printf("<input type=\"hidden\" name=\"%s\" value=\"%s\">\n", slp->name, val);
        }
    }

// a hidden submit button, see
// http://stackoverflow.com/questions/477691/submitting-a-form-by-pressing-enter-without-a-submit-button
if (debug)
    printf("<input type=\"submit\">\n");
else if (!submitDone)
    printf("<input type=\"submit\" style=\"position: absolute; left: -9999px; width: 1px; height: 1px;\">\n");
printf("</form>\n");
// a little javascript that clicks the submit button
if (!debug)
    {
    jsInline("document.getElementById(\"redirForm\").submit();\n");
    }
jsInlineFinish();
printf("</body></html>\n");
}
