/* 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 <regex.h>
#include "hCommon.h"
#include "obscure.h"
#include "dnautil.h"
#include "errAbort.h"
#include "htmshell.h"
#include "web.h"
#include "dupTrack.h"
#include "hPrint.h"
#include "hdb.h"
#include "hui.h"
#include "hgConfig.h"
#include "hVarSubst.h"
#include "cheapcgi.h"
#include "dbDb.h"
#include "hgColors.h"
#include "hubConnect.h"
#include "search.h"
#include "geoMirror.h"
#include "trackHub.h"
#include "versionInfo.h"
#include "asmAlias.h"

#ifndef GBROWSE
#include "axtInfo.h"
#include "wikiLink.h"
#include "googleAnalytics.h"
#include "jsHelper.h"
#endif /* GBROWSE */


/* flag that tell if the CGI header has already been outputed */
boolean webHeadAlreadyOutputed = FALSE;
/* flag that tell if text CGI header has been outputed */
boolean webInTextMode = FALSE;

struct hash *includedResourceFiles = NULL;

static char *dbCgiName = "db";
static char *orgCgiName = "org";
static char *cladeCgiName = "clade";
static char *extraStyle = NULL;

/* globals: a cart and db for use in error handlers. */
static struct cart *errCart = NULL;
static char *errDb = NULL;

void textVaWarn(char *format, va_list args)
{
vprintf(format, args);
puts("\n");
}

void softAbort()
{
if (!webInTextMode)
    webEnd();
exit(0);
}

void webPushErrHandlers(void)
/* Push warn and abort handler for errAbort(). */
{
if (webInTextMode)
    pushWarnHandler(textVaWarn);
else
    pushWarnHandler(webVaWarn);
pushAbortHandler(softAbort);
hDumpStackPushAbortHandler();
}

void webPushErrHandlersCartDb(struct cart *cart, char *db)
/* Push warn and abort handler for errAbort(); save cart and db for use in handlers. */
{
errCart = cart;
errDb = db;
webPushErrHandlers();
}

void webPopErrHandlers(void)
/* Pop warn and abort handler for errAbort(). */
{
popWarnHandler();
hDumpStackPopAbortHandler();
popAbortHandler();
}

void webSetStyle(char *style)
/* set a style to add to the header */
{
extraStyle = style;
}

void webPragmasEtc()
/* Print out stuff that tells people not to cache us, and that we use the
 * usual character set and scripting langauge. (Normally done by webStartWrap) */
{
printf("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;CHARSET=iso-8859-1\">" "\n"
     "<META http-equiv=\"Content-Script-Type\" content=\"text/javascript\">" "\n"
     "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" "\n"
     "<META HTTP-EQUIV=\"Expires\" CONTENT=\"-1\">" "\n"
     );
}

void webCirmPragmasEtc()
/* Print out stuff similar to webPragmasEtc (don't cache us, character set, etc.), but
 * use values appropriate for a more modern website (like CIRM). */
{
printf("\t\t<meta charset=\"windows-1252\">\n"   // Be nice to be utf-8, but that's a bigger issue to tackle
    "\t\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
    "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
    "\t\t<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->\n");
}

void webStartText()
/* output the head for a text page */
{
/*printf("Content-Type: text/plain\n\n");*/

webHeadAlreadyOutputed = TRUE;
webInTextMode = TRUE;
webPushErrHandlers();
}

static void webStartWrapperDetailedInternal(struct cart *theCart,
	char *db, char *headerText, char *textOutBuf,
	boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader,
	boolean withHtmlHeader)
/* output a CGI and HTML header with the given title in printf format */
{
char uiState[256];
char *scriptName = cgiScriptName();
char *textOutBufDb = cloneString(textOutBuf);
boolean isEncode = FALSE;
if (theCart)
    {
    char *theGenome = NULL;
    char *genomeEnc = NULL;

    getDbAndGenome(theCart, &db, &theGenome, NULL);
    genomeEnc = cgiEncode(theGenome);

    safef(uiState, sizeof(uiState), "?%s=%s&%s=%s&%s=%s",
	     orgCgiName, genomeEnc,
	     dbCgiName, db,
	     cartSessionVarName(), cartSessionId(theCart));
    }
else
    {
    uiState[0] = 0;
    uiState[1] = 0;
    }
if (db == NULL)
    db = hDefaultDb();
// boolean dbIsFound = hDbExists(db);
// boolean haveBlat = FALSE;  unfortunately this feature has disappeared
// if (dbIsFound)             this needs to be resurrected in the new menu
//    haveBlat = hIsBlatIndexedDatabase(db);   bar system in menuBar()

if (scriptName == NULL)
    scriptName = cloneString("");
/* don't output two headers */
if(webHeadAlreadyOutputed)
    return;

if (sameString(cgiUsualString("action",""),"encodeReleaseLog") ||
    rStringIn("EncodeDataVersions", scriptName))
        isEncode = TRUE;

/* Preamble. */
dnaUtilOpen();


if (withHttpHeader)
    puts("Content-type:text/html\n");

// If the database name is not already in the title string, add it now
if (endsWith(scriptName, "hgc") && db != NULL && !stringIn(db, textOutBufDb))
    {
    struct dyString *newTitle = dyStringNew(0);
    dyStringPrintf(newTitle, "%s %s", db, textOutBufDb);
    textOutBufDb = dyStringCannibalize(&newTitle);
    }
if (withHtmlHeader)
    {
    char *newString, *ptr1, *ptr2;

    char *browserVersion;
    if (btIE == cgiClientBrowser(&browserVersion, NULL, NULL) && *browserVersion < '8')
        puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">");
    else
        puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" "
             "\"http://www.w3.org/TR/html4/loose.dtd\">");
    // Strict would be nice since it fixes atleast one IE problem (use of :hover CSS pseudoclass)
    puts(
	"<HTML>" "\n"
	"<HEAD>" "\n"
	);
    generateCspMetaHeader(stdout);
    htmlPrintAnalyticsLink(stdout);

    printf("\t%s\n", headerText);
    webPragmasEtc();

    printf("\t<TITLE>");

    /* we need to take any HTML formatting out of the titlebar string */
    newString = cloneString(textOutBufDb);

    for(ptr1=newString, ptr2=textOutBufDb; *ptr2 ; ptr2++)
	{
	if (*ptr2 == '<')
	    {
	    for(; *ptr2 && (*ptr2 != '>'); ptr2++)
		;
	    }
	else
	    *ptr1++ = *ptr2;
	}
    *ptr1 = 0;
    htmlTextOut(newString);
    printf("	</TITLE>\n    ");
    webIncludeResourceFile("HGStyle.css");
    if (extraStyle != NULL)
        puts(extraStyle);

    printf("</HEAD>\n");
    printBodyTag(stdout);
    htmlWarnBoxSetup(stdout);// Sets up a warning box which can be filled with errors as they occur
    puts(commonCssStyles());
    }

/* Put up the hot links bar. */

char *menuStr = menuBar(theCart, db);
if(menuStr)
    {
    puts(menuStr);
    }

webStartSectionTables();

if (withLogo)
    {
    puts("<TR><TH COLSPAN=1 ALIGN=\"left\">");
    if (isEncode)
	{
	puts("<A HREF=\"http://www.genome.gov/10005107\" TARGET=\"_BLANK\">"
	     "<IMG SRC=\"../images/ENCODE_scaleup_logo.png\" height=50 ALT=\"ENCODE Project at NHGRI\">"
	     "</A>");
	puts("<IMG SRC=\"../images/encodeDcc.jpg\" ALT=\"ENCODE Project at UCSC\">");
	}
    else
	{
	puts("<IMG SRC=\"../images/title.jpg\">");
	}
    puts("</TH></TR>" "\n"
         "" "\n" );
    }

if(!skipSectionHeader)
/* this HTML must be in calling code if skipSectionHeader is TRUE */
    {
    webFirstSection(textOutBufDb);
    };
webPushErrHandlers();
/* set the flag */
webHeadAlreadyOutputed = TRUE;
errAbortSetDoContentType(FALSE);
}	/*	static void webStartWrapperDetailedInternal()	*/

void webStartWrapperDetailedArgs(struct cart *theCart, char *db,
	char *headerText, char *format, va_list args, boolean withHttpHeader,
	boolean withLogo, boolean skipSectionHeader, boolean withHtmlHeader)
/* output a CGI and HTML header with the given title in printf format */
{
char textOutBuf[1024];
va_list argscp;

va_copy(argscp,args);
vasafef(textOutBuf, sizeof(textOutBuf), format, argscp);
va_end(argscp);

webStartWrapperDetailedInternal(theCart, db, headerText, textOutBuf,
	withHttpHeader, withLogo, skipSectionHeader, withHtmlHeader);
}

void webStartWrapperDetailedNoArgs(struct cart *theCart, char *db,
	char *headerText, char *format, boolean withHttpHeader,
	boolean withLogo, boolean skipSectionHeader, boolean withHtmlHeader)
/* output a CGI and HTML header with the given title in printf format */
{
char textOutBuf[512];

safecpy(textOutBuf, sizeof(textOutBuf), format);
webStartWrapperDetailedInternal(theCart, db, headerText, textOutBuf,
	withHttpHeader, withLogo, skipSectionHeader, withHtmlHeader);
}

void webStartWrapperGatewayHeader(struct cart *theCart, char *db,
	char *headerText, char *format, va_list args, boolean withHttpHeader,
	boolean withLogo, boolean skipSectionHeader)
{
webStartWrapperDetailedArgs(theCart, db, headerText, format, args, withHttpHeader,
	withLogo, skipSectionHeader, TRUE);
}

void webStartWrapperGateway(struct cart *theCart, char *db, char *format, va_list args, boolean withHttpHeader, boolean withLogo, boolean skipSectionHeader)
/* output a CGI and HTML header with the given title in printf format */
{
webStartWrapperGatewayHeader(theCart, db, "", format, args, withHttpHeader,
			     withLogo, skipSectionHeader);
}

void webStartWrapper(struct cart *theCart, char *db, char *format, va_list args, boolean withHttpHeader, boolean withLogo)
    /* allows backward compatibility with old webStartWrapper that doesn't contain the "skipHeader" arg */
	/* output a CGI and HTML header with the given title in printf format */
{
webStartWrapperGatewayHeader(theCart, db, "", format, args, withHttpHeader,
                             withLogo, FALSE);
}

void webStartExt(boolean withHttpHeader, struct cart *theCart, char *db, char *format, ...)
/* Print out pretty wrapper around things when not
 * from cart. Do not output an http header.*/
{
va_list args;
va_start(args, format);
webStartWrapper(theCart, db, format, args, withHttpHeader, TRUE);
va_end(args);
}

void webStart(struct cart *theCart, char *db, char *format, ...)
/* Print out pretty wrapper around things when not
 * from cart. */
{
va_list args;
va_start(args, format);
webStartWrapper(theCart, db, format, args, TRUE, FALSE);
va_end(args);
}

void webStartHeader(struct cart *theCart, char *db, char *headerText, char *format, ...)
/* Print out pretty wrapper around things when not from cart.
 * Include headerText in the html header. */
{
va_list args;
va_start(args, format);
webStartWrapperGatewayHeader(theCart, db, headerText, format, args, TRUE, TRUE,
			     FALSE);
va_end(args);
}

void webEndSection()
/* Close down a section */
{
puts(
    "" "\n"
    "	</TD><TD WIDTH=15></TD></TR></TABLE>" "\n"
//    "<BR>"
    "	</TD></TR></TABLE>" "\n"
    "	</TD></TR></TABLE>" "\n"
    "	" );
puts("</div>");
}

void webStartSectionTables()
/* Put up start of nepharious table layout stuff. (Normally done by webStartWrap). */
{
puts(
    "<A NAME=\"TOP\"></A>" "\n"
    "<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=\"100%\">" "\n");
}

void webFirstSection(char *title)
/* Put up the first section (normally done by webStartWrap). */
{
puts( // TODO: Replace nested tables with CSS (difficulty is that tables are closed elsewhere)
     "<!-- +++++++++++++++++++++ CONTENT TABLES +++++++++++++++++++ -->" "\n"
     "<TR><TD COLSPAN=3>\n"
     "<div id=firstSection>"
     " <!--outer table is for border purposes-->\n"
     " <TABLE WIDTH='100%' BGCOLOR='#" HG_COL_BORDER "' BORDER='0' CELLSPACING='0' "
		 "CELLPADDING='1'><TR><TD>\n"
     " <TABLE CLASS='hgInside' BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%'  BORDER='0' CELLSPACING='0' "
		 "CELLPADDING='0'><TR><TD>\n"
     "     <div class='subheadingBar'><div class='windowSize' id='sectTtl'>"
     );
// FIXME: Need a more general solution here to support styling in blue bar title. 
//      Perhaps support escaped < and > by doubling or backslash
htmlTextOut(title);

puts("     </div></div>\n"
     "     <TABLE CLASS='hgInside' BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' CELLPADDING=0>"
	  "<TR><TH HEIGHT=10></TH></TR>\n"
     "     <TR><TD WIDTH=10>&nbsp;</TD><TD>\n\n"
     );
}

void webNewSectionHeaderStart()
/* Start the header for a new section on the web page.
 * May be used to maintain table layout without a proper section header */
{
webEndSection();
puts("<div>");
puts("<!-- +++++++++++++++++++++ START NEW SECTION +++++++++++++++++++ -->");
puts(  // TODO: Replace nested tables with CSS (difficulty is that tables are closed elsewhere)
    "<BR>\n\n"
    "   <!--outer table is for border purposes-->\n"
    "   <TABLE WIDTH='100%' BGCOLOR='#" HG_COL_BORDER
        "' BORDER='0' CELLSPACING='0' CELLPADDING='1'><TR><TD>\n"
    "    <TABLE BGCOLOR='#" HG_COL_INSIDE
         "' WIDTH='100%'  BORDER='0' CELLSPACING='0' CELLPADDING='0'><TR><TD>\n");
puts("<div class='subheadingBar windowSize'>");
}

void webNewSectionHeaderEnd()
/* Properly close header of collapsible section on web page */
{
puts("     </div>\n"
     "     <TABLE BGCOLOR='#" HG_COL_INSIDE "' WIDTH='100%' CELLPADDING=0>"
          "<TR><TH HEIGHT=10></TH></TR>\n"
     "     <TR><TD WIDTH=10>&nbsp;</TD><TD>\n\n");
}

void webNewSection(char* format, ...)
/* create a new section on the web page */
{
va_list args;
va_start(args, format);
webNewSectionHeaderStart();
vprintf(format, args);
webNewSectionHeaderEnd();
va_end(args);
}

void webNewEmptySection()
/* create a new section on the web page to maintain table layout */
{
webNewSectionHeaderStart();
webNewSectionHeaderEnd();
}

void webEndSectionTables()
/* Finish with section tables (but don't do /BODY /HTML like
 * webEnd does. */
{
webEndSection();
puts("</TD></TR></TABLE>\n");
}

void webEnd()
/* output the footer of the HTML page */
{
if(!webInTextMode)
    {
    webEndSectionTables();
#ifndef GBROWSE
    googleAnalytics();
#endif /* GBROWSE */
    jsInlineFinish();
    puts( "</BODY></HTML>");
    webPopErrHandlers();
    }
}

void webEndExtra(char *footer)
/* output the footer of the HTML page with extra footer as desired */
{
if(!webInTextMode)
    {
    webEndSectionTables();
#ifndef GBROWSE
    googleAnalytics();
#endif /* GBROWSE */
    jsInlineFinish();
    if (footer)
	puts(footer);
    puts( "</BODY></HTML>");
    webPopErrHandlers();
    }
}

static void webStartGbOptionalBanner(struct cart *cart, char *db, char *title, boolean doBanner, 
                                boolean hgGateway)
/* Start HTML with new header and footer design by JWest.  
   Optionally display banner above menubar.  Use flag with hgGateway, till that is migrated.
 */
{
puts("Content-type:text/html\n");

char *csp = getCspMetaHeader();
if (hgGateway)
    {
    printf(
        #include "jWestHeader.h"
               , csp, title
               , webTimeStampedLinkToResource("HGStyle.css", TRUE)
               , webTimeStampedLinkToResource("jWest.css", TRUE));
    }
else
    {
    printf(
        #include "gbHeader.h"
               , csp, title);
    }
if (doBanner)
    {
    printf(
        #include "jWestBanner.h"
          , title);
    }
freeMem(csp);

webPushErrHandlersCartDb(cart, db);
htmlWarnBoxSetup(stdout);

// Add hotlinks bar
char *navBar = menuBar(cart, db);
if (navBar)
    {
    puts(navBar);
    // Override nice-menu.css's menu background and fonts:
    webIncludeResourceFile("gbAfterMenu.css");
    }
webHeadAlreadyOutputed = TRUE;
errAbortSetDoContentType(FALSE);
}

void webStartGbNoBanner(struct cart *cart, char *db, char *title)
/* Start HTML with new header and footer design by jWest, but no banner */
{
webStartGbOptionalBanner(cart, db, title, FALSE, FALSE);
}

void webEndGb()
/* End HTML that was started with webStartJWest. */
{
googleAnalytics();
jsInlineFinish();
puts("</body></html>");
webPopErrHandlers();
}

void webStartJWest(struct cart *cart, char *db, char *title)
/* Start HTML with new banner and footer design by jWest (with modifications). */
{
webStartGbOptionalBanner(cart, db, title, TRUE, TRUE);
}

void webEndJWest()
/* End HTML that was started with webStartJWest. */
{
webEndGb();
}

static boolean gotWarnings = FALSE;

void webVaWarn(char *format, va_list args)
/* Warning handler that closes out page and stuff in
 * the fancy form. */
{
gotWarnings = TRUE;
boolean needStart = !webHeadAlreadyOutputed;
if (needStart)
    {
    // All callers of this (via webPushErrHandlersCartDb) have skipped Content-type
    // because they want to output text unless we hit this condition:
    puts("Content-type:text/html\n");
    cartWebStart(errCart, errDb, "Error");
    }
htmlVaWarn(format, args);
printf("\n<!-- HGERROR -->\n\n\n");
}


boolean webGotWarnings()
/* Return TRUE if webVaWarn has been called. */
{
return gotWarnings;
}

void webAbortExt(boolean withHttpHeader, char* title, char *format, va_list args)
/* an abort function that outputs a error page */
{

/* output the header */
if(!webHeadAlreadyOutputed)
    webStartExt(withHttpHeader, errCart, NULL, "%s", title);

/* in text mode, have a different error */
if(webInTextMode)
	printf("\n\n\n          %s\n\n", title);

vprintf(format, args);

printf("<!-- HGERROR -->\n");
printf("\n\n");

webEnd();

exit(0);
}

void webAbortNoHttpHeader(char* title, char* format, ...)
/* an abort function that outputs a error page. No http header output. */
{
va_list args;
va_start(args, format);

webAbortExt(FALSE, title, format, args); 

va_end(args);
}

void webAbort(char* title, char* format, ...)
/* an abort function that outputs a error page */
{
va_list args;
va_start(args, format);

webAbortExt(TRUE, title, format, args); 

va_end(args);
}

void printCladeListHtml(char *genome, char *event, char *javascript)
/* Make an HTML select input listing the clades. */
{
char **row = NULL;
char *clades[128];
char *labels[128];
char *defaultClade = hClade(genome);
char *defaultLabel = NULL;
int numClades = 0;

if (hubConnectIsCurated(trackHubSkipHubName(genome)))
    defaultClade = hClade(trackHubSkipHubName(genome));
else
    defaultClade = hClade(genome);
struct sqlConnection *conn = hConnectCentral();  // after hClade since it access hgcentral too
// get only the clades that have actual active genomes
char query[4096];
sqlSafef(query, sizeof query, "SELECT DISTINCT(c.name), c.label "
         // mysql 5.7: SELECT list w/DISTINCT must include all fields in ORDER BY list (#18626)
         ", c.priority "
         "FROM %s c, %s g, %s d WHERE c.name=g.clade AND d.organism=g.genome AND d.active=1 "
         "ORDER BY c.priority",
         cladeTable(), genomeCladeTable(), dbDbTable());
struct sqlResult *sr = sqlGetResult(conn, query);
while ((row = sqlNextRow(sr)) != NULL)
    {
    clades[numClades] = cloneString(row[0]);
    labels[numClades] = cloneString(row[1]);
    if (sameWord(defaultClade, clades[numClades]))
	defaultLabel = clades[numClades];
    numClades++;
    if (numClades >= ArraySize(clades))
        internalErr();
    }
sqlFreeResult(&sr);
hDisconnectCentral(&conn);

struct slPair *names = trackHubGetCladeLabels();

for(; names; names = names->next)
    {
    clades[numClades] = names->name;
    labels[numClades] = names->val;
    if (sameWord(defaultClade, clades[numClades]))
	defaultLabel = clades[numClades];
    numClades++;
    if (numClades >= ArraySize(clades))
        internalErr();
    }

cgiMakeDropListFull(cladeCgiName, labels, clades, numClades,
                    defaultLabel, event, javascript);
}

static void printSomeGenomeListHtmlNamedMaybeCheck(char *customOrgCgiName,
	 char *db, struct dbDb *dbList, char *event, char *javascript, boolean doCheck)
/* Prints to stdout the HTML to render a dropdown list
 * containing a list of the possible genomes to choose from.
 * param db - a database whose genome will be the default genome.
 *                       If NULL, no default selection.
 * param onChangeText - Optional (can be NULL) text to pass in
 *                              any onChange javascript. */
{
char *orgList[1024];
int numGenomes = 0;
struct dbDb *cur = NULL;
struct hash *hash = hashNew(10); // 2^^10 entries = 1024
char *selGenome = hGenome(db);
char *values [1024];
char *cgiName;

for (cur = dbList; cur != NULL; cur = cur->next)
    {
    if (!hashFindVal(hash, cur->genome) &&
	(!doCheck || hDbExists(cur->name)))
        {
        hashAdd(hash, cur->genome, cur);
        orgList[numGenomes] = trackHubSkipHubName(cur->genome);
        values[numGenomes] = cur->genome;
        numGenomes++;
	if (numGenomes >= ArraySize(orgList))
	    internalErr();
        }
    }

cgiName = (customOrgCgiName != NULL) ? customOrgCgiName : orgCgiName;
cgiMakeDropListFull(cgiName, orgList, values, numGenomes,
                    selGenome, event, javascript);
hashFree(&hash);
}

void printSomeGenomeListHtmlNamed(char *customOrgCgiName, char *db, struct dbDb *dbList, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list
 * containing a list of the possible genomes to choose from.
 * param db - a database whose genome will be the default genome.
 *                       If NULL, no default selection.
 * param event e.g. "change"
 *   javascript - Optional (can be NULL) onEvent javascript. */
{
return printSomeGenomeListHtmlNamedMaybeCheck(customOrgCgiName, db, dbList,
					      event, javascript, TRUE);
}

void printLiftOverGenomeList(char *customOrgCgiName, char *db,
			     struct dbDb *dbList, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list
 * containing a list of the possible genomes to choose from.
 * Databases in dbList do not have to exist.
 * param db - a database whose genome will be the default genome.
 *                       If NULL, no default selection.
 * param event e.g. "change"
 *   javascript - Optional (can be NULL) onEvent javascript. */
{
return printSomeGenomeListHtmlNamedMaybeCheck(customOrgCgiName, db, dbList,
					      event, javascript, FALSE);
}

void printSomeGenomeListHtml(char *db, struct dbDb *dbList, char *event, char *javascript)
/* Prints the dropdown list using the orgCgiName */
{
printSomeGenomeListHtmlNamed(NULL, db, dbList, event, javascript);
}

void printGenomeListHtml(char *db, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list
 * containing a list of the possible genomes to choose from.
 * param db - a database whose genome will be the default genome.
 *                       If NULL, no default selection.
 * param event e.g. "change"
 *   javascript - Optional (can be NULL) onEvent javascript. */
{
printSomeGenomeListHtml(db, hGetIndexedDatabases(), event, javascript);
}

void printBlatGenomeListHtml(char *db, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list
 * containing a list of the possible genomes to choose from.
 * param db - a database whose genome will be the default genome.
 *                       If NULL, no default selection.
 * param event e.g. "change"
 *   javascript - Optional (can be NULL) onEvent javascript. */
{
printSomeGenomeListHtml(db, hGetBlatIndexedDatabases(), event, javascript);
}


void printGenomeListForCladeHtml(char *db, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list containing
 * a list of the possible genomes from selOrganism's clade to choose from.
 * selOrganism is the default for the select.
 */
{
printSomeGenomeListHtml(db, hGetIndexedDatabasesForClade(db), event, javascript);
}

void printAllAssemblyListHtmlParm(char *db, struct dbDb *dbList,
                            char *dbCgi, bool allowInactive, char *event, char *javascript)
/* Prints to stdout the HTML to render a dropdown list containing the list
 * of assemblies for the current genome to choose from.  By default,
 * this includes only active assemblies with a database (with the
 * exception of the default assembly, which will be included even
 * if it isn't active).
 *  param db - The default assembly (the database name) to choose as selected.
 *             If NULL, no default selection.
 *  param allowInactive - if set, print all assemblies for this genome,
 *                        even if they're inactive or have no database
 */
{

char *assemblyList[128];
char *values[128];
int numAssemblies = 0;
struct dbDb *cur = NULL;
char *genome = hGenome(db);
char *selAssembly = NULL;

if (genome == NULL)
#ifdef LOWELAB
    genome = "Pyrococcus furiosus";
#else
    genome = "Human";
#endif
for (cur = dbList; cur != NULL; cur = cur->next)
    {
    /* Only for this genome */
    if (!sameWord(genome, cur->genome))
        continue;

    /* Save a pointer to the current assembly */
    if (sameWord(db, cur->name))
        selAssembly = cur->name;

    if (allowInactive ||
        ((cur->active || sameWord(cur->name, db))
                && (trackHubDatabase(db) || sqlDatabaseExists(cur->name))))
        {
        assemblyList[numAssemblies] = cur->description;
        values[numAssemblies] = cur->name;
        numAssemblies++;
	if (numAssemblies >= ArraySize(assemblyList))
	    internalErr();
        }

    }
cgiMakeDropListFull(dbCgi, assemblyList, values, numAssemblies,
                                selAssembly, event, javascript);
}

void printSomeAssemblyListHtmlParm(char *db, struct dbDb *dbList,
                                        char *dbCgi, char *event, char *javascript)
/* Find all the assemblies from the list that are active.
 * Prints to stdout the HTML to render a dropdown list containing the list
 * of the possible assemblies to choose from.
 * param db - The default assembly (the database name) to choose as selected.
 *    If NULL, no default selection.  */
{

    printAllAssemblyListHtmlParm(db, dbList, dbCgi, TRUE, event, javascript);
}

void printSomeAssemblyListHtml(char *db, struct dbDb *dbList, char *event, char *javascript)
/* Find all assemblies from the list that are active, and print
 * HTML to render dropdown list
 * param db - default assembly.  If NULL, no default selection */
{
printSomeAssemblyListHtmlParm(db, dbList, dbCgiName, event, javascript);
}

void printAssemblyListHtml(char *db, char *event, char *javascript)
/* Find all the assemblies that pertain to the selected genome
 * Prints to stdout the HTML to render a dropdown list containing
 * a list of the possible assemblies to choose from.
 * Param db - The assembly (the database name) to choose as selected.
 * If NULL, no default selection.  */
{
struct dbDb *dbList = hGetIndexedDatabases();
printSomeAssemblyListHtml(db, dbList, event, javascript);
}

void printAssemblyListHtmlExtra(char *db, char *event, char *javascript)
{
/* Find all the assemblies that pertain to the selected genome
Prints to stdout the HTML to render a dropdown list containing a list of the possible
assemblies to choose from.

param curDb - The assembly (the database name) to choose as selected.
If NULL, no default selection.
 */
struct dbDb *dbList = hGetIndexedDatabases();
printSomeAssemblyListHtmlParm(db, dbList, dbCgiName, event, javascript);
}

void printBlatAssemblyListHtml(char *db)
{
/* Find all the assemblies that pertain to the selected genome
Prints to stdout the HTML to render a dropdown list containing a list of the possible
assemblies to choose from.

param curDb - The assembly (the database name) to choose as selected.
If NULL, no default selection.
 */
struct dbDb *dbList = hGetBlatIndexedDatabases();
printSomeAssemblyListHtml(db, dbList, NULL, NULL);
}

static char *getDbForGenome(char *genome, struct cart *cart)
/*
  Function to find the default database for the given Genome.
It looks in the cart first and then, if that database's Genome matches the
passed-in Genome, returns it. If the Genome does not match, it returns the default
database that does match that Genome.

param Genome - The Genome for which to find a database
param cart - The cart to use to first search for a suitable database name
return - The database matching this Genome type
*/
{

char *retDb = cartUsualString(cart, dbCgiName, NULL);

if ((retDb == NULL) || !hDbExists(retDb))
    {
    retDb = hDefaultDb();
    }

/* If genomes don't match, then get the default db for that genome */
if (differentWord(genome, hGenome(retDb)))
    {
    retDb = hDefaultDbForGenome(genome);
    }

return retDb;
}

void getDbGenomeClade(struct cart *cart, char **retDb, char **retGenome,
		      char **retClade, struct hash *oldVars)
/* Examine CGI and cart variables to determine which db, genome, or clade
 *  has been selected, and then adjust as necessary so that all three are
 * consistent.  Detect changes and reset db-specific cart variables.
 * Save db, genome and clade in the cart so it will be consistent hereafter.
 * The order of preference here is as follows:
 * If we got a request that explicitly names the db, that takes
 * highest priority, and we synch the organism to that db.
 * If we get a cgi request for a specific organism then we use that
 * organism to choose the DB.  If just clade, go from there.

 * In the cart only, we use the same order of preference.
 * If someone requests an Genome we try to give them the same db as
 * was in their cart, unless the Genome doesn't match.
 */
{
boolean gotClade = hGotClade();
*retDb =  cgiOptionalString(dbCgiName);
if (*retDb == NULL)  // if db is not in URL, but genome is, use it for db 
    *retDb = cgiOptionalString(hgHubGenome);

*retGenome = cgiOptionalString(orgCgiName);
*retClade = cgiOptionalString(cladeCgiName);

/* Was the database passed in as a cgi param?
 * If so, it takes precedence and determines the genome. */
*retDb = asmAliasFind(*retDb);
if (*retDb && hDbExists(*retDb))
    {
    *retGenome = hGenome(*retDb);
    }
/* If no db was passed in as a cgi param then was the organism (a.k.a. genome)
 * passed in as a cgi param?
 * If so, the we use the proper database for that genome. */
else if (*retGenome && !sameWord(*retGenome, "0"))
    {
    *retDb = getDbForGenome(*retGenome, cart);
    *retGenome = hGenome(*retDb);
    }
else if (*retClade && gotClade)
    {
    *retGenome = hDefaultGenomeForClade(*retClade);
    *retDb = getDbForGenome(*retGenome, cart);
    }
/* If no cgi params passed in then we need to inspect the session */
else
    {
    *retDb = cartOptionalString(cart, dbCgiName);
    *retDb = asmAliasFind(*retDb);
    *retGenome = cartOptionalString(cart, orgCgiName);
    *retClade = cartOptionalString(cart, cladeCgiName);
    /* If there was a db found in the session that determines everything. */
    if (*retDb && hDbExists(*retDb))
        {
        *retGenome = hGenome(*retDb);
        }
    else if (*retGenome && !sameWord(*retGenome, "0"))
	{
	*retDb = hDefaultDbForGenome(*retGenome);
	}
    else if (*retClade && gotClade)
	{
        *retGenome = hDefaultGenomeForClade(*retClade);
	*retDb = getDbForGenome(*retGenome, cart);
	}
    /* If no organism in the session then get the default db and organism. */
    else
	{
	*retDb = hDefaultDb();
	*retGenome = hGenome(*retDb);
        }
    }
*retDb = cloneString(*retDb);
*retGenome = cloneString(*retGenome);
*retClade = hClade(*retGenome);

if ( (*retClade != NULL) && sameString(*retClade, "none"))
    cartRemove(cart, "position");
/* Detect change of database and reset db-specific cart variables: */
if (oldVars)
    {
    char *oldDb = hashFindVal(oldVars, "db");
    char *oldOrg = hashFindVal(oldVars, "org");
    char *oldClade = hashFindVal(oldVars, "clade");
    if ((!IS_CART_VAR_EMPTY(oldDb)    && differentWord(oldDb, *retDb)) ||
        (!IS_CART_VAR_EMPTY(oldOrg)   && differentWord(oldOrg, *retGenome)) ||
        (!IS_CART_VAR_EMPTY(oldClade) && differentWord(oldClade, *retClade)))
	{
	/* Change position to default -- unless it was passed in via CGI: */
	if (cgiOptionalString("position") == NULL)
	    cartSetString(cart, "position", hDefaultPos(*retDb));
	// remove virtual chrom cart vars related to position
	cartRemove(cart, "virtMode");
	cartRemove(cart, "virtModeType");
	cartRemove(cart, "lastVirtModeExtraState");
	cartRemove(cart, "lastVirtModeType");
	cartRemove(cart, "nonVirtPosition");
	cartRemove(cart, "oldPosition");
	/* hgNear search term -- unless it was passed in via CGI: */
	if (cgiOptionalString("near_search") == NULL)
	    cartRemove(cart, "near_search");
	/* hgBlat results (hgUserPsl track): */
	cartRemove(cart, "ss");
	/* hgTables correlate: */
	cartRemove(cart, "hgta_correlateTrack");
	cartRemove(cart, "hgta_correlateTable");
	cartRemove(cart, "hgta_correlateGroup");
	cartRemove(cart, "hgta_correlateOp");
	cartRemove(cart, "hgta_nextCorrelateTrack");
	cartRemove(cart, "hgta_nextCorrelateTable");
	cartRemove(cart, "hgta_nextCorrelateGroup");
	cartRemove(cart, "hgta_nextCorrelateOp");
	cartRemove(cart, "hgta_corrWinSize");
	cartRemove(cart, "hgta_corrMaxLimitCount");
	}
    }

/* Save db, genome (as org) and clade in cart. */
cartSetString(cart, "db", *retDb);
cartSetString(cart, "org", *retGenome);
if (gotClade)
    cartSetString(cart, "clade", *retClade);
}

void getDbAndGenome(struct cart *cart, char **retDb, char **retGenome,
		    struct hash *oldVars)
/* Get just the db and genome. */
{
char *garbage = NULL;
getDbGenomeClade(cart, retDb, retGenome, &garbage, oldVars);
freeMem(garbage);
}

static void webIncludeFileSubst(char *file, struct cart *cart)
/* Include an HTML file in a CGI.  If cart is non-null, invoke hVarSubstWithCart.
 *   The file path may begin with hDocumentRoot(); if it doesn't, it is
 *   assumed to be relative and hDocumentRoot() will be prepended. */
{
char *str = hFileContentsOrWarning(file);
if (cart != NULL)
    {
    char *db = cartString(cart, "db");
    hVarSubstWithCart("webIncludeFileSubst", cart, NULL, db, &str);
    }
puts(str);
freeMem(str);
}

void webIncludeFile(char *file)
/* Include an HTML file in a CGI.
 *   The file path may begin with hDocumentRoot(); if it doesn't, it is
 *   assumed to be relative and hDocumentRoot() will be prepended. */
{
return webIncludeFileSubst(file, NULL);
}

void webIncludeHelpFileSubst(char *fileRoot, struct cart *cart, boolean addHorizLine)
/* Given a help file root name (e.g. "hgPcrResult" or "cutters"),
 * print out the contents of the file.  If cart is non-NULL, invoke hVarSubstWithCart
 * before printing.  If addHorizLine, print out an <HR> first. */
{
if (addHorizLine)
    htmlHorizontalLine();
char *file = hHelpFile(fileRoot);
webIncludeFileSubst(file, cart);
}

void webIncludeHelpFile(char *fileRoot, boolean addHorizLine)
/* Given a help file root name (e.g. "hgPcrResult" or "cutters"),
 * print out the contents of the file.  If addHorizLine, print out an
 * <HR> first. */
{
return webIncludeHelpFileSubst(fileRoot, NULL, addHorizLine);
}

void webPrintLinkTableStart()
/* Print link table start in our colors. */
{
printf("<TABLE><TR><TD BGCOLOR='#" HG_COL_BORDER "'>\n");
printf("<TABLE CELLSPACING=1 CELLPADDING=3><TR>\n");
}

void webPrintLinkTableEnd()
/* Print link table end in our colors. */
{
printf("</TR></TABLE>\n");
printf("</TD></TR></TABLE>\n");
}

void webPrintLinkOutCellStart()
/* Print link cell that goes out of our site. End with
 * webPrintLinkTableEnd. */
{
printf("<TD BGCOLOR='#" HG_COL_LOCAL_TABLE "'>");
}

void webPrintWideCellStart(int colSpan, char *bgColorRgb)
/* Print link multi-column cell start in our colors. */
{
printf("<TD BGCOLOR='#%s'", bgColorRgb);
if (colSpan > 1)
    printf(" COLSPAN=%d", colSpan);
printf(">");
}

void webPrintLinkCellStart()
/* Print link cell start in our colors. */
{
webPrintWideCellStart(1, HG_COL_TABLE);
}

void webPrintLinkCellRightStart()
/* Print right-justified cell start in our colors. */
{
printf("<TD BGCOLOR='#"HG_COL_TABLE"' ALIGN='right'>");
}

void webPrintLinkCellEnd()
/* Print link cell end in our colors. */
{
printf("</TD>");
}

void webPrintLinkCell(char *link)
/* Print link cell in our colors, if links is null, print empty cell */
{
webPrintLinkCellStart();
if (link != NULL)
    puts(link);
webPrintLinkCellEnd();
}

void webPrintIntCell(int val)
/* Print right-justified int cell in our colors. */
{
webPrintLinkCellRightStart();
printf("%d", val);
webPrintLinkCellEnd();
}

void webPrintDoubleCell(double val)
/* Print right-justified cell in our colors with two digits to right of decimal. */
{
webPrintLinkCellRightStart();
printf("%4.2f", val);
webPrintLinkCellEnd();
}

void webPrintWideLabelCellStart(int colSpan)
/* Print start of wrapper around a label in a table. */
{
printf("<TD BGCOLOR='#"HG_COL_TABLE_LABEL"'");
if (colSpan > 1)
    printf(" COLSPAN=%d", colSpan);
printf("><span style='color:#FFFFFF;'><B>");
}

void webPrintLabelCellStart()
/* Print start of wrapper around a label in a table. */
{
webPrintWideLabelCellStart(1);
}

void webPrintLabelCellEnd()
/* Print end of wrapper around a label in a table. */
{
printf("</B></span></TD>");
}

void webPrintWideLabelCell(char *label, int colSpan)
/* Print label cell over multiple columns in our colors. */
{
webPrintWideLabelCellStart(colSpan);
printf("%s", label);
webPrintLabelCellEnd();
}

void webPrintWideCenteredLabelCell(char *label, int colSpan)
/* Print label cell over multiple columns in our colors and centered. */
{
printf("<TD BGCOLOR='#" HG_COL_TABLE_LABEL "'");
if (colSpan > 1)
    printf(" COLSPAN=%d", colSpan);
printf("><CENTER><span style='color:#FFFFFF;'><B>%s</B></span></CENTER></TD>", label);
}

void webPrintLabelCell(char *label)
/* Print label cell in our colors. */
{
webPrintWideLabelCell(label, 1);
}

void webPrintLinkTableNewRow()
/* start a new row */
{
printf("</TR>\n<TR>");
}

void finishPartialTable(int rowIx, int itemPos, int maxPerRow,
	void (*cellStart)())
/* Fill out partially empty last row. */
{
if (rowIx != 0 && itemPos < maxPerRow)
    {
    int i;
    for (i=itemPos; i<maxPerRow; ++i)
        {
	cellStart();
	webPrintLinkCellEnd();
	}
    }
}

void webFinishPartialLinkOutTable(int rowIx, int itemPos, int maxPerRow)
/* Fill out partially empty last row. */
{
finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkOutCellStart);
}

void webFinishPartialLinkTable(int rowIx, int itemPos, int maxPerRow)
/* Fill out partially empty last row. */
{
finishPartialTable(rowIx, itemPos, maxPerRow, webPrintLinkCellStart);
}

static struct dyString* getHtdocsSubdir(char *dirName)
/* return dystring with subdirectory under htDocs, tolerant of missing docRoot */
{
struct dyString *fullDirName = NULL;
char *docRoot = hDocumentRoot();
if (docRoot != NULL)
    fullDirName = dyStringCreate("%s/%s", docRoot, dirName);
else
    // tolerate missing docRoot (i.e. when running from command line)
    fullDirName = dyStringCreate("%s", dirName);
return fullDirName;
}

char *webCssLink(char *fileName, boolean mustExist)
/* alternative for webTimeStampedLinkToResource for CSS files: returns a string with a time-stamped
 * link to a CSS file as a html fragment <link .... >. returns empty string if file does not exist.
 * errAborts if mustExist is True.
 * */
{
// construct the absolute path to the file on disk
char *relDir = cfgOptionDefault("browser.styleDir","style");
struct dyString *absDir = getHtdocsSubdir(relDir);
struct dyString *absFileName = dyStringCreate("%s/%s", dyStringContents(absDir), fileName);

struct dyString *htmlFrag = NULL;
if (!fileExists(dyStringContents(absFileName)))
    {
    if (mustExist)
        errAbort("webCssLink: file: %s doesn't exist.\n", dyStringContents(absFileName));
    else
        htmlFrag = dyStringNew(0);
    }
else
    {
    // construct a link with the relative path to the file on the web server
    long mtime = fileModTime(dyStringContents(absFileName));
    htmlFrag = dyStringCreate("<link rel='stylesheet' href='../%s/%s?v=%ld' type='text/css'>\n", relDir, fileName, mtime);
    }

dyStringFree(&absFileName);
dyStringFree(&absDir);
return dyStringCannibalize(&htmlFrag);
}

char *webTimeStampedLinkToResource(char *fileName, boolean wrapInHtml)
// If wrapInHtml
//   returns versioned link embedded in style or script html (free after use).
// else
//   returns full path of a versioned path to the requested resource file (js, or css).
// NOTE: png, jpg and gif should also be supported but are untested.
//
// In production sites we use a versioned softlink that includes the CGI version. This has the following benefits:
// a) flushes user's web browser cache when the user visits a GB site whose version has changed since their last visit;
// b) enforces the requirement that static files are the same version as the CGIs (something that often fails to happen in mirrors).
// (see notes in redmine #3170).
//
// In dev trees we use mtime to create a pseudo-version; this forces web browsers to reload css/js file when it changes,
// so we don't get odd behavior that can be caused by caching of mis-matched javascript and style files in dev trees.
//
// In either case, the actual file has to have been previously created by running make in the appropriate directory (kent/src/hg/js
// or kent/src/hg/htdocs/style).
{
char baseName[PATH_LEN];
char extension[FILEEXT_LEN];
splitPath(fileName, NULL, baseName, extension);
boolean js = sameString(".js",extension);
boolean style = !js && sameString(".css",extension);
boolean image = !js
             && !style
             && (  sameString(".png",extension)
                || sameString(".jpg",extension)
                || sameString(".gif",extension));
if (!js && !style) // && !image) NOTE: This code has not been tested on images but should work.
    errAbort("webTimeStampedLinkToResource: unknown resource type for %s.\n", fileName);

char *httpHost = hHttpHost();

// Build and verify directory
char *dirName = "";
if (js)
    dirName = cfgOptionDefault("browser.javaScriptDir", "js");
else if (style)
    dirName = cfgOptionDefault("browser.styleDir","style");
else if (image)
    dirName = cfgOptionDefault("browser.styleImagesDir","style/images");

struct dyString *fullDirName = NULL;
char *docRoot = hDocumentRoot();
if (docRoot != NULL)
    fullDirName = dyStringCreate("%s/%s", docRoot, dirName);
else
    // tolerate missing docRoot (i.e. when running from command line)
    fullDirName = dyStringCreate("%s", dirName);

if (!fileExists(dyStringContents(fullDirName)))
    errAbort("webTimeStampedLinkToResource: dir: %s doesn't exist. (host: %s)\n",
             dyStringContents(fullDirName), httpHost);

// build and verify real path to file
struct dyString *realFileName = dyStringCreate("%s/%s", dyStringContents(fullDirName), fileName);
if (!fileExists(dyStringContents(realFileName)))
    errAbort("webTimeStampedLinkToResource: file: %s doesn't exist.\n",
             dyStringContents(realFileName));

// build and verify link path including timestamp in the form of dir/baseName + timeStamp or CGI Version + ext
long mtime = fileModTime(dyStringContents(realFileName));
struct dyString *linkWithTimestamp;

linkWithTimestamp = dyStringCreate("%s/%s%s?v=%ld", dyStringContents(fullDirName), baseName, extension, mtime);

// Free up all that extra memory
dyStringFree(&realFileName);
dyStringFree(&fullDirName);
char *linkFull = dyStringCannibalize(&linkWithTimestamp);
char *link = linkFull;
if (docRoot != NULL)
    {
    struct dyString *relativeLink = dyStringCreate("../%s", linkFull + strlen(docRoot) + 1);
    link = dyStringCannibalize(&relativeLink);
    freeMem(linkFull);
    }

if (wrapInHtml) // wrapped for christmas
    {
    struct dyString *wrapped = dyStringNew(0);
    if (js)
        dyStringPrintf(wrapped,"<script type='text/javascript' SRC='%s'></script>\n", link);
    else if (style)
        dyStringPrintf(wrapped,"<link rel='stylesheet' href='%s' type='text/css'>\n", link);
    else // Will be image, since these are the only three choices allowed
        dyStringPrintf(wrapped,"<IMG src='%s' />\n", link);
    freeMem(link);
    link = dyStringCannibalize(&wrapped);
    }

return link;
}

char *webTimeStampedLinkToResourceOnFirstCall(char *fileName, boolean wrapInHtml)
// If this is the first call, will return full path of timestamped link to the requested
//   resource file (js, or css).  Free after use.
// else returns NULL.  Useful to ensure multiple references to the same resource file are not made
// NOTE: png, jpg and gif should also be supported but are untested.
{
if (!includedResourceFiles)
    includedResourceFiles = newHash(0);

if (hashLookup(includedResourceFiles, fileName))
    return NULL;

char * link = webTimeStampedLinkToResource(fileName,wrapInHtml);
if (link)
    hashAdd(includedResourceFiles, fileName, NULL);  // Don't hash link, because
return link;                                         // memory will be freed by caller!!!
}

boolean webIncludeResourcePrintToFile(FILE * toFile, char *fileName)
// Converts fileName to web Resource link and prints the html reference
// This only prints and returns TRUE on first call for this resource.
// Passing in NULL as the file pointer results in hPrintf call
// The reference will be to a link with timestamp.
{
char *link = webTimeStampedLinkToResourceOnFirstCall(fileName,TRUE);
if (link)
    {
    if (toFile == NULL)
        hPrintf("%s",link);
    else
        fprintf(toFile,"%s",link);
    freeMem(link);
    return TRUE;
    }
return FALSE;
}

// overrides for default context specific help link.
char *contextSpecificHelpLink = NULL;
char *contextSpecificHelpLabel = NULL;

void setContextSpecificHelp(char *link, char *label)
// Override default behavior for the context specific help link
{
if(link)
    contextSpecificHelpLink = cloneString(link);
if(label)
    contextSpecificHelpLabel = cloneString(label);
}

char *menuBarAddUiVars(char *oldString, char *cgiPrefix, char *uiVars)
/* Look for CGI program calls in oldString, and add session vars hgsid to them */
{
int len = strlen(oldString);
char buf[4096];

/* Create a regular expression and compile it */
regex_t re;
regmatch_t match[2];
safef(buf, sizeof(buf), "%s[A-Za-z]+(%c%c?)", cgiPrefix, '\\', '?');
int err = regcomp(&re, buf, REG_EXTENDED);
if(err)
    errAbort("regcomp failed; err: %d", err);

/* Search through oldString with regex, and build up new string in dy */
struct dyString *dy = dyStringNew(0);
int offset;
for(offset = 0; offset < len && !regexec(&re, oldString + offset, ArraySize(match), match, 0); 
    offset += match[0].rm_eo)
    {
    dyStringAppendN(dy, oldString + offset, match[0].rm_eo);
    if(match[1].rm_so == match[1].rm_eo)
	dyStringAppend(dy, "?");
    dyStringAppend(dy, uiVars);
    if(match[1].rm_so != match[1].rm_eo)
	dyStringAppend(dy, "&amp;");
    }
if(offset < len)
    dyStringAppend(dy, oldString + offset);
return dyStringCannibalize(&dy);
}

char *menuBar(struct cart *cart, char *db)
// Return HTML for the menu bar (read from a configuration file);
// we fixup internal CGI's to add hgsid's and include the appropriate js and css files.
//
// Note this function is also called by hgTracks which extends the menu bar
//  with a View menu defined in hgTracks/menu.c
{
char *docRoot = hDocumentRoot();
char *menuStr, buf[4096], uiVars[128];
FILE *fd;
char *navBarFile = "inc/globalNavBar.inc";
struct stat statBuf;
char *scriptName = cgiScriptName();
if (cart)
    safef(uiVars, sizeof(uiVars), "%s=%s", cartSessionVarName(), cartSessionId(cart));
else
    uiVars[0] = 0;

if(docRoot == NULL)
    // tolerate missing docRoot (i.e. don't bother with menu when running from command line)
    return NULL;

jsIncludeFile("jquery.js", NULL);
jsIncludeFile("jquery.plugins.js", NULL);
jsIncludeFile("utils.js", NULL);
webIncludeResourceFile("nice_menu.css");

// Read in menu bar html
safef(buf, sizeof(buf), "%s/%s", docRoot, navBarFile);
fd = mustOpen(buf, "r");
fstat(fileno(fd), &statBuf);
int len = statBuf.st_size;
menuStr = needMem(len + 1);
mustRead(fd, menuStr, statBuf.st_size);
menuStr[len] = 0;
carefulClose(&fd);

if (cart)
    {
    char *newMenuStr = menuBarAddUiVars(menuStr, "/cgi-bin/hg", uiVars);
    freez(&menuStr);
    menuStr = newMenuStr;
    }

if(scriptName)
    {
    // Provide hgTables options for some CGIs.
    char hgTablesOptions[1024] = "";
    char *track = (cart == NULL ? NULL :
                   (endsWith(scriptName, "hgGene") ?
                    cartOptionalString(cart, "hgg_type") :
                    cartOptionalString(cart, "g")));
    if (track && cart && db &&
        (endsWith(scriptName, "hgc") || endsWith(scriptName, "hgTrackUi") ||
         endsWith(scriptName, "hgGtexTrackSettings") || endsWith(scriptName, "hgGene")))
        {
        struct trackDb *tdb = hTrackDbForTrack(db, track);
        if (tdb)
	    {
	    struct trackDb *topLevel = trackDbTopLevelSelfOrParent(tdb); 
	    char *undupedTrack = dupTrackSkipToSourceName(topLevel->track);
	    safef(hgTablesOptions, sizeof  hgTablesOptions, 
		    "../cgi-bin/hgTables?hgta_doMainPage=1&hgta_group=%s&hgta_track=%s&hgta_table=%s&", 
		    topLevel->grp, undupedTrack, tdb->table);
	    menuStr = replaceChars(menuStr, "../cgi-bin/hgTables?", hgTablesOptions);
	    trackDbFree(&tdb);
	    }
        }
    }

if(!loginSystemEnabled())
    stripRegEx(menuStr, "<\\!-- LOGIN_START -->.*<\\!-- LOGIN_END -->", REG_ICASE);

if(scriptName)
    {  // Provide optional official mirror servers menu items
    char *geoMenu = geoMirrorMenu();
    char *pattern = "<!-- OPTIONAL_MIRROR_MENU -->";
    char *newMenuStr = replaceChars(menuStr, pattern, geoMenu);
    freez(&menuStr);
    menuStr = newMenuStr;
    }


if(scriptName)
    {
    // Provide view menu for some CGIs.
    struct dyString *viewItems = dyStringCreate("%s","");
    boolean hasViewMenu = TRUE;
    if (endsWith(scriptName, "hgGenome"))
        {
	safef(buf, sizeof(buf), "../cgi-bin/hgGenome?%s&hgGenome_doPsOutput=1", uiVars);
    	dyStringPrintf(viewItems, "<li><a href='%s' id='%s'>%s</a></li>\n", buf, "pdfLink", "PDF");
        }
    else
	{
	hasViewMenu = FALSE;
	}
    if (hasViewMenu)
	{
	struct dyString *viewMenu = dyStringCreate("<li class='menuparent' id='view'><span>View</span>\n<ul style='display: none; visibility: hidden;'>\n");
	dyStringAppend(viewMenu, viewItems->string);
	dyStringAppend(viewMenu, "</ul>\n</li>\n");
    	menuStr = replaceChars(menuStr, "<!-- OPTIONAL_VIEW_MENU -->", viewMenu->string);
	dyStringFree(&viewMenu);
	}
    else if (!endsWith(scriptName, "hgTracks"))
	{
    	replaceChars(menuStr, "<!-- OPTIONAL_VIEW_MENU -->", "");
	}
    dyStringFree(&viewItems);
    }


if(scriptName)
    {
    // Provide context sensitive help links for some CGIs.
    char *link = NULL;
    char *label = NULL;
    if (endsWith(scriptName, "hgBlat"))
        {
        link = "../goldenPath/help/hgTracksHelp.html#BLATAlign";
        label = "Help on Blat";
        }
    else if (endsWith(scriptName, "hgHubConnect"))
        {
        link = "../goldenPath/help/hgTrackHubHelp.html";
        label = "Help on Track Hubs";
        }
    else if (endsWith(scriptName, "hgNear"))
        {
        link = "../goldenPath/help/hgNearHelp.html";
        label = "Help on Gene Sorter";
        }
    else if (endsWith(scriptName, "hgTables"))
        {
        link = "../goldenPath/help/hgTablesHelp.html";
        label = "Help on Table Browser";
        }
    else if (endsWith(scriptName, "hgIntegrator"))
        {
        link = "../goldenPath/help/hgIntegratorHelp.html";
        label = "Help on Data Integrator";
        }
    else if (endsWith(scriptName, "hgGenome"))
        {
        link = "../goldenPath/help/hgGenomeHelp.html";
        label = "Help on Genome Graphs";
        }
    else if (endsWith(scriptName, "hgSession"))
        {
        link = "../goldenPath/help/hgSessionHelp.html";
        label = "Help on Sessions";
        }
    else if (endsWith(scriptName, "hgVisiGene"))
        {
        link = "../goldenPath/help/hgTracksHelp.html#VisiGeneHelp";
        label = "Help on VisiGene";
        }
    else if (endsWith(scriptName, "hgCustom"))
        {
        link = "../goldenPath/help/customTrack.html";
        label = "Help on Custom Tracks";
        }
    // Don't overwrite any previously set defaults
    if(!contextSpecificHelpLink && link)
        contextSpecificHelpLink = link;
    if(!contextSpecificHelpLabel && label)
        contextSpecificHelpLabel = label;
    }
if(contextSpecificHelpLink)
    {
    char buf[1024];
    safef(buf, sizeof(buf), "<li><a href='%s'>%s</a></li>", contextSpecificHelpLink, contextSpecificHelpLabel);
    menuStr = replaceChars(menuStr, "<!-- CONTEXT_SPECIFIC_HELP -->", buf);
    }

// links to hgTracks need to use the web browser width and set the hgTracks image
// size in pixels correctly to match the hgGateway "GO" button
jsInline("$(\"#tools1 ul li a\").each( function (a) {\n"
"    if (this.href && this.href.indexOf(\"hgTracks\") !== -1) {\n"
"        var obj = this;\n"
"        obj.onclick = function(e) {\n"
"            var pix = calculateHgTracksWidth();\n"
"            e.currentTarget.href += \"&pix=\" + pix;\n"
"        }\n"
"    }\n"
"});\n");
return menuStr;
}

void checkForGeoMirrorRedirect(struct cart *cart)
// Implement Geo/IP based redirection.
{
char *thisNodeStr = geoMirrorNode();
if (thisNodeStr)   // if geo-mirroring is enabled
    {
    char *redirectCookie = findCookieData("redirect");
    char *redirect = cgiOptionalString("redirect");

    // if we're not already redirected
    if (redirect == NULL && redirectCookie == NULL) 
        {
        int thisNode = sqlUnsigned(thisNodeStr);
        struct sqlConnection *centralConn = hConnectCentral();
        char *ipStr = cgiRemoteAddr();
        int node = geoMirrorDefaultNode(centralConn, ipStr);

        // if our node is not the node that's closest.
        if (thisNode != node)
            {
	    char *geoSuffix = cfgOptionDefault("browser.geoSuffix","");
            char query[1056];
            sqlSafef(query, sizeof query, "select domain from gbNode%s where node = %d", geoSuffix, node);
            char *newDomain = sqlQuickString(centralConn, query);
            char *oldDomain = cgiServerName();
            char *port = cgiServerPort();
            char *uri = cgiRequestUri();
            char *sep = strchr(uri, '?') ? "&" : "?";
            int newUriSize = strlen(uri) + 1024;
            char *newUri = needMem(newUriSize);
            char *oldUri = needMem(newUriSize);
            safef(oldUri, newUriSize, "http%s://%s:%s%s%sredirect=manual&source=%s", 
		cgiServerHttpsIsOn() ? "s" : "", oldDomain, port, uri, sep, oldDomain);
            safef(newUri, newUriSize, "http%s://%s:%s%s%sredirect=manual&source=%s", 
		cgiServerHttpsIsOn() ? "s" : "", newDomain, port, uri, sep, oldDomain);

	    printf("<TR><TD COLSPAN=3 id='redirectTd'>"
	    "<div style=\"margin: 10px 25%%; border-style:solid; border-width:thin; border-color:#97D897;\">"
	    "<h3 style=\"background-color: #97D897; text-align: left; margin-top:0px; margin-bottom:0px;\">"
	    "&nbsp;You might want to navigate to your nearest mirror - %s"
	    "</h3> "
	    "<ul style=\"margin:5px;\">",
	    newDomain);
	    jsOnEventById("click","redirectTd", "document.getElementById('redirectTd').innerHTML='';");
	    
	    printf("<li>User settings (sessions and custom tracks) <B>will differ</B> between sites."
		"<idiv style=\"float:right;\"><a href=\"../goldenPath/help/genomeEuro.html#sessions\">Read more.</a></idiv>");
	    printf("<li>Take me to  <a href=\"%s\">%s</a> </li>",
		newUri, newDomain);
	    printf("<li>Let me stay here   <a href=\"%s\">%s</a>",
		oldUri, oldDomain );
	    printf("</div></TD></TR>\n");
	    jsInlineFinish();
            cartCheckout(&cart);
            exit(0);
            }
        hDisconnectCentral(&centralConn);
        }
    }
}
