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

#ifdef USE_HAL
/* snakeTrack - stuff to load and display snake type tracks in browser.  */

#include "common.h"
#include "hash.h"
#include "localmem.h"
#include "linefile.h"
#include "jksql.h"
#include "hdb.h"
#include "hgTracks.h"
#include "chainBlock.h"
#include "chainLink.h"
#include "chainDb.h"
#include "chainCart.h"
#include "errCatch.h"
#include "twoBit.h"
#include "bigWarn.h"
#include <pthread.h>
#include "trackHub.h"
#include "limits.h"
#include "snakeUi.h"
#include "bits.h"
#include "trix.h"
#include "chromAlias.h"

#include "halBlockViz.h"
#include "bigPsl.h"

struct snakeInfo
{
int maxLevel;
} ;


// this is the number of pixels used by the target self-align bar
#define DUP_LINE_HEIGHT	4
// this is the number of pixels used when displaying the insertion lengths
#define INSERT_TEXT_HEIGHT 10

struct snakeFeature
    {
    struct snakeFeature *next;
    int start, end;			/* Start/end in browser coordinates. */
    int qStart, qEnd;			/* query start/end */
    int level;				/* level in snake */
    int orientation;			/* strand... -1 is '-', 1 is '+' */
    boolean drawn;			/* did we draw this feature? */
    char *qSequence;			/* may have sequence, or NULL */
    char *tSequence;			/* may have sequence, or NULL */
    char *qName;			/* chrom name on other species */
    unsigned pixX1, pixX2;              /* pixel coordinates within window */
    };

static int snakeFeatureCmpTStart(const void *va, const void *vb)
/* sort by start position on the target sequence */
{
const struct snakeFeature *a = *((struct snakeFeature **)va);
const struct snakeFeature *b = *((struct snakeFeature **)vb);
int diff = a->start - b->start;

return diff;
}

static int snakeFeatureCmpQStart(const void *va, const void *vb)
/* sort by start position on the query sequence */
{
const struct snakeFeature *a = *((struct snakeFeature **)va);
const struct snakeFeature *b = *((struct snakeFeature **)vb);
int diff = a->qStart - b->qStart;

if (diff == 0)
    {
    diff = a->start - b->start;
    }

return diff;
}

// static machinery to calculate full and pack snake

#define NUM_LEVELS	1000

struct level
{
boolean init;		/* has this level been initialized */
int orientation;	/* strand.. see above */
unsigned long edge;	/* the leading edge of this level */
int adjustLevel;	/* used to compress out the unused levels */
boolean hasBlock;	/* are there any blocks in this level */

// these fields are used to compact full snakes
unsigned *connectsToLevel;  /* what levels does this level connect to */
Bits *pixels;               /* keep track of what pixels are used */
struct snakeFeature *blocks; /* the ungapped blocks attached to this level */
};

static struct level Levels[NUM_LEVELS]; /* for packing the snake, not re-entrant! */
static int maxLevel = 0;     	     /* deepest level */	

/* blocks that make it through the min size filter */
static struct snakeFeature *newList = NULL;   

static void clearLevels()
/* clear out the data structure that we use to calculate snakes */
{
int ii;

for(ii=0; ii < sizeof(Levels) / sizeof(Levels[0]); ii++)
    Levels[ii].init = FALSE;
maxLevel = 0;
}

static void calcFullSnakeHelper(struct snakeFeature *list, int level)
// calculate a full snake
// updates global newList with unsorted blocks that pass the min
// size filter.
{
struct snakeFeature *cb = list;
struct snakeFeature *proposedList = NULL;

if (level > maxLevel)
    maxLevel = level;
if (level > ArraySize(Levels))
    errAbort("too many levels");

if (Levels[level].init == FALSE)
    {
    // initialize the level if this is the first time we've seen it
    Levels[level].init = TRUE;
    Levels[level].orientation = list->orientation;
    if (list->orientation == -1)
	Levels[level].edge = LONG_MAX; // bigger than the biggest chrom
    else
	Levels[level].edge = 0;
    }

// now we step through the blocks and assign them to levels
struct snakeFeature *next;
struct snakeFeature *insertHead = NULL;
for(; cb; cb = next)
    {
    // we're going to add this block to a different list 
    // so keep track of the next pointer
    next = cb->next;

    // if this block can't fit on this level add to the insert
    // list and move on to the next block
    if ((Levels[level].orientation != cb->orientation) ||
	((cb->orientation == 1) && (Levels[level].edge > cb->start)) ||
	((cb->orientation == -1) && (Levels[level].edge < cb->end)))
	{
	// add this to the list of an insert
	slAddHead(&insertHead, cb);
	continue;
	}

    // the current block will fit on this level
    // go ahead and deal with the blocks that wouldn't 
    if (insertHead)
	{
	// we had an insert, go ahead and calculate where that goes
	slReverse(&insertHead);
	calcFullSnakeHelper(insertHead, level + 1);
	insertHead = NULL;
	}

    // assign the current block to this level
    cb->level = level;

    if (cb->orientation == 1)
	Levels[level].edge = cb->end;
    else
	Levels[level].edge = cb->start;

    // add this block to the list of proposed blocks for this level
    slAddHead(&proposedList, cb);
    }

// we're at the end of the list.  Deal with any blocks
// that didn't fit on this level
if (insertHead)
    {
    slReverse(&insertHead);
    int nextLevel = level + 1;
    calcFullSnakeHelper(insertHead, nextLevel);
    insertHead = NULL;
    }

// do we have any proposed blocks
if (proposedList == NULL) 
    return;

// we parsed all the blocks in the list to see if they fit in our level
// now let's see if the list of blocks for this level is big enough
// to actually add

struct snakeFeature *temp;
double scale = scaleForWindow(insideWidth, winStart, winEnd);
int start, end;

// order the blocks so lowest start position is first
if (Levels[level].orientation == 1)
    slReverse(&proposedList);

start=proposedList->start;
for(temp=proposedList; temp->next; temp = temp->next)
    ;
end=temp->end;

// calculate how big the string of blocks is in screen coordinates
double apparentSize = scale * (end - start);

if (apparentSize < 0)
    errAbort("size of a list of blocks should not be less than zero");

// check to see if the apparent size is big enough
if (apparentSize < 1)
    return;

// transfer proposedList to new block list
for(temp=proposedList; temp; temp = next)
    {
    next = temp->next;
    temp->next = NULL;
    slAddHead(&newList, temp);
    }
}


static void freeFullLevels()
// free the connection levels and bitmaps for each level
{
int ii;

for(ii=0; ii <= maxLevel; ii++)
    {
    freez(&Levels[ii].connectsToLevel);
    bitFree(&Levels[ii].pixels);
    }
}

static void initializeFullLevels(struct snakeFeature *sfList)
/* initialize levels for compact snakes */
{
int ii;

for(ii=0; ii <= maxLevel; ii++)
    {
    Levels[ii].connectsToLevel = needMem((maxLevel+1) * sizeof(unsigned));
    Levels[ii].pixels = bitAlloc(insideWidth);
    Levels[ii].blocks = NULL;
    }

struct snakeFeature *sf, *next;
int prevLevel = -1;
double scale = scaleForWindow(insideWidth, winStart, winEnd);

for(sf=sfList; sf; sf = next)
    {
    next = sf->next;

    if (positiveRangeIntersection(sf->start, sf->end, winStart, winEnd))
	{
	int sClp = (sf->start < winStart) ? winStart : sf->start;
	sf->pixX1 = round((sClp - winStart)*scale);
	int eClp = (sf->end > winEnd) ? winEnd : sf->end;
	sf->pixX2 = round((eClp - winStart)*scale);
	}
    else
	{
	sf->pixX1 = sf->pixX2 = 0;
	}

    bitSetRange(Levels[sf->level].pixels, sf->pixX1, sf->pixX2 - sf->pixX1);

    if (prevLevel != -1)
	Levels[sf->level].connectsToLevel[prevLevel] = 1;

    if(next != NULL)
	Levels[sf->level].connectsToLevel[next->level] = 1;

    prevLevel = sf->level;

    slAddHead(&Levels[sf->level].blocks, sf);
    }
}

static int highestLevelBelowUs(int level)
// is there a level below us that doesn't connect to us
{
int newLevel;

// find the highest level below us that we connect to
for(newLevel = level - 1; newLevel >= 0; newLevel--)	 
    if (Levels[level].connectsToLevel[newLevel])
	break;

// if the next level is more than one below us, we may
// be able to push our blocks to it.
if ((newLevel < 0) || (newLevel + 1 == level))
    return -1;

return newLevel + 1;
}

// the number of pixels we need clear on either side of the blocks we push
#define EXTRASPACE	10

static boolean checkBlocksOnLevel(int level, struct snakeFeature *sfList)
// is there enough pixels on this level to hold our blocks?
{
struct snakeFeature *sf;

for(sf=sfList; sf;  sf = sf->next)
    {
    if (bitCountRange(Levels[level].pixels, sf->pixX1 - EXTRASPACE, (sf->pixX2 - sf->pixX1) + 2*EXTRASPACE))
	return FALSE;
    }
return TRUE;
}

static void collapseLevel(int oldLevel, int newLevel)
// push level up to higher level
{
struct snakeFeature *sf;
struct snakeFeature *next;

for(sf=Levels[oldLevel].blocks; sf;  sf = next)
    {
    next = sf->next;
    sf->level = newLevel;
    slAddHead(&Levels[newLevel].blocks, sf);

    bitSetRange(Levels[sf->level].pixels, sf->pixX1, sf->pixX2 - sf->pixX1);
    }
Levels[oldLevel].blocks = NULL;
Levels[oldLevel].pixels = bitAlloc(insideWidth);
}

static void remapConnections(int oldLevel, int newLevel)
// if we've moved a level, we need to remap all the connections
{
int ii, jj;

for(ii=0; ii <= maxLevel; ii++)
    {
    for(jj=0; jj <= maxLevel; jj++)
	{
	if (Levels[ii].connectsToLevel[oldLevel])
	    {
	    Levels[ii].connectsToLevel[oldLevel] = 0;
	    Levels[ii].connectsToLevel[newLevel] = 1;
	    }
	}
    }
}

static struct snakeFeature *compactSnakes(struct snakeFeature *sfList)
// collapse levels that will fit on a higer level
{
int ii;

initializeFullLevels(sfList);

// start with third level to see what can be compacted
for(ii=2; ii <= maxLevel; ii++) 
    {
    int newLevel;
    // is there a level below us that doesn't connect to us
    if ((newLevel = highestLevelBelowUs(ii)) > 0)
	{
	int jj;
	for (jj=newLevel; jj < ii; jj++)
	    {
	    if (checkBlocksOnLevel(jj, Levels[ii].blocks))
		{
		collapseLevel(ii, jj);
		remapConnections(ii, jj);
		break;
		}
	    }
	}
    }

// reattach blocks in the levels to linkedFeature
struct snakeFeature *newList = NULL;
for(ii=0; ii <= maxLevel; ii++)
    {
    newList = slCat(Levels[ii].blocks, newList);
    }
slSort(&newList, snakeFeatureCmpQStart);

// figure out the new max
int newMax = 0;
for(ii=0; ii <= maxLevel; ii++)
    if (Levels[ii].blocks != NULL)
	newMax = ii;

freeFullLevels();
maxLevel = newMax;

return newList;
}

static void calcFullSnake(struct track *tg, void *item)
// calculate a full snake
{
struct linkedFeatures  *lf = (struct linkedFeatures *)item;

// if there aren't any blocks, don't bother
if (lf->components == NULL)
    return;

// we use the codons field to keep track of whether we already
// calculated the height of this snake
if (lf->codons == NULL)
    {
    clearLevels();
    struct snakeFeature *sf;

    // this will destroy lf->components, and add to newList
    calcFullSnakeHelper((struct snakeFeature *)lf->components, 0);
    lf->components = (struct simpleFeature *)newList;
    newList = NULL;
    slSort(&lf->components, snakeFeatureCmpQStart);

    // now we're going to compress the levels that aren't used
    // to do that, we need to see which blocks are on the screen,
    // or connected to something on the screen
    int oldMax = maxLevel;
    clearLevels();
    struct snakeFeature *prev = NULL;
    for(sf=(struct snakeFeature *)lf->components; sf; prev = sf, sf = sf->next)
	{
	if (Levels[sf->level].init == FALSE)
	    {
	    Levels[sf->level].init = TRUE;
	    Levels[sf->level].orientation = sf->orientation;
	    Levels[sf->level].hasBlock = FALSE;
	    }
	else
	    {
	    if (Levels[sf->level].orientation != sf->orientation)
		errAbort("found snakeFeature with wrong orientation");
	    if ((sf->orientation == 1) && (Levels[sf->level].edge > sf->start)) 
		errAbort("found snakeFeature that violates edge");
	    if ((sf->orientation == -1) && (Levels[sf->level].edge < sf->end))
		errAbort("found snakeFeature that violates edge");
	    }
	if (sf->orientation == 1)
	    Levels[sf->level].edge = sf->end;
	else
	    Levels[sf->level].edge = sf->start;
	if (sf->level > maxLevel)
	    maxLevel = sf->level;
	if(positiveRangeIntersection(winStart, winEnd, sf->start, sf->end))
	    {
	    Levels[sf->level].hasBlock = TRUE;
	    if (sf->next != NULL)
		Levels[sf->next->level].hasBlock = TRUE;
	    if (prev != NULL)
		Levels[prev->level].hasBlock = TRUE;

	    }
	}

    // now figure out how to remap the blocks
    int ii;
    int count = 0;
    for(ii=0; ii < oldMax + 1; ii++)
	{
	Levels[ii].adjustLevel = count;
	if ((Levels[ii].init) && (Levels[ii].hasBlock))
	    count++;
	}
    maxLevel = count;

    // remap blocks
    for(sf=(struct snakeFeature *)lf->components; sf; sf = sf->next)
	sf->level = Levels[sf->level].adjustLevel;

    // now compact the snakes
    lf->components = (void *)compactSnakes((struct snakeFeature *)lf->components);
    struct snakeInfo *si;
    AllocVar(si);
    si->maxLevel = maxLevel;
    lf->codons = (struct simpleFeature *)si;
    }
}

static void calcPackSnakeHelper(struct snakeFeature *list, int level)
// calculate a packed snake
// updates global newList with unsorted blocks that pass the min
// size filter.
{
struct snakeFeature *cb = list;
struct snakeFeature *proposedList = NULL;

if (level > maxLevel)
    maxLevel = level;
if (level > ArraySize(Levels))
    errAbort("too many levels");

if (Levels[level].init == FALSE)
    {
    // initialize the level if this is the first time we've seen it
    Levels[level].init = TRUE;
    Levels[level].edge = 0;
    }

// now we step through the blocks and assign them to levels
struct snakeFeature *next;
struct snakeFeature *insertHead = NULL;
for(; cb; cb = next)
    {
    // we're going to add this block to a different list 
    // so keep track of the next pointer
    next = cb->next;

    // if this block can't fit on this level add to the insert
    // list and move on to the next block
    if ( Levels[level].edge > cb->start)
	{
	// add this to the list of an insert
	slAddHead(&insertHead, cb);
	continue;
	}

    // the current block will fit on this level
    // go ahead and deal with the blocks that wouldn't 
    if (insertHead)
	{
	// we had an insert, go ahead and calculate where that goes
	slReverse(&insertHead);
	calcPackSnakeHelper(insertHead, level + 1);
	insertHead = NULL;
	}

    // assign the current block to this level
    cb->level = level;

    Levels[level].edge = cb->end;

    // add this block to the list of proposed blocks for this level
    slAddHead(&proposedList, cb);
    }

// we're at the end of the list.  Deal with any blocks
// that didn't fit on this level
if (insertHead)
    {
    slReverse(&insertHead);
    calcPackSnakeHelper(insertHead,  level + 1);
    insertHead = NULL;
    }

// do we have any proposed blocks
if (proposedList == NULL) 
    return;

// we parsed all the blocks in the list to see if they fit in our level
// now let's see if the list of blocks for this level is big enough
// to actually add

struct snakeFeature *temp;
double scale = scaleForWindow(insideWidth, winStart, winEnd);
int start, end;

// order the blocks so lowest start position is first
slReverse(&proposedList);

start=proposedList->start;
for(temp=proposedList; temp->next; temp = temp->next)
    ;
end=temp->end;

// calculate how big the string of blocks is in screen coordinates
double apparentSize = scale * (end - start);

if (apparentSize < 0)
    errAbort("size of a list of blocks should not be less than zero");

// check to see if the apparent size is big enough
if (apparentSize < 1)
    return;

// transfer proposedList to new block list
for(temp=proposedList; temp; temp = next)
    {
    next = temp->next;
    temp->next = NULL;
    slAddHead(&newList, temp);
    }
}

static void calcPackSnake(struct track *tg, void *item)
{
struct linkedFeatures  *lf = (struct linkedFeatures *)item;
if (lf->components == NULL)
    return;

// we use the codons field to keep track of whether we already
// calculated the height of this snake
if (lf->codons == NULL)
    {
    clearLevels();

    // this will destroy lf->components, and add to newList
    calcPackSnakeHelper((struct snakeFeature *)lf->components, 0);
    lf->components = (struct simpleFeature *)newList;
    newList = NULL;
    
    //slSort(&lf->components, snakeFeatureCmpQStart);

    struct snakeInfo *si;
    AllocVar(si);
    si->maxLevel = maxLevel;
    lf->codons = (struct simpleFeature *)si;
    }
}

static int snakeItemHeight(struct track *tg, void *item)
// return height of a single packed snake 
{
if ((item == NULL) || (tg->visibility == tvSquish) || (tg->visibility == tvDense)) 
    return 0;

struct linkedFeatures  *lf = (struct linkedFeatures *)item;
if (lf->components == NULL)
    return 0;

if (tg->visibility == tvFull) 
    calcFullSnake(tg, item);
else if (tg->visibility == tvPack) 
    calcPackSnake(tg, item);

struct snakeInfo *si = (struct snakeInfo *)lf->codons;
int lineHeight = tg->lineHeight ;
int multiplier = 1;

if (tg->visibility == tvFull)
    multiplier = 2;
return (si->maxLevel + 1) * (multiplier * lineHeight);
}

#ifdef NOTNOW
static int linkedFeaturesCmpScore(const void *va, const void *vb)
/* Help sort linkedFeatures by score */
{
const struct linkedFeatures *a = *((struct linkedFeatures **)va);
const struct linkedFeatures *b = *((struct linkedFeatures **)vb);
if (a->score > b->score)
    return -1;
else if (a->score < b->score)
    return 1;
return 0;
}
#endif

static int snakeHeight(struct track *tg, enum trackVisibility vis)
/* calculate height of all the snakes being displayed */
{
if (tg->networkErrMsg != NULL)
    {
    // we had a parallel load failure
    tg->drawItems = bigDrawWarning;
    tg->totalHeight = bigWarnTotalHeight;
    return bigWarnTotalHeight(tg, vis);
    }

if (vis == tvDense)
    return tg->lineHeight;

if (vis == tvSquish)
    return tg->lineHeight/2;

int height = DUP_LINE_HEIGHT + INSERT_TEXT_HEIGHT; 
struct slList *item = tg->items;

item = tg->items;

for (item=tg->items;item; item = item->next)
    {
    height += tg->itemHeight(tg, item);
    }
if (height < DUP_LINE_HEIGHT + tg->lineHeight)
    height = DUP_LINE_HEIGHT + tg->lineHeight;
return height;
}

static void snakeDraw(struct track *tg, int seqStart, int seqEnd,
        struct hvGfx *hvg, int xOff, int yOff, int width, 
        MgFont *font, Color color, enum trackVisibility vis)
/* Draw linked features items. */
{
struct slList *item;
int y;
struct linkedFeatures  *lf;
double scale = scaleForWindow(width, seqStart, seqEnd);
int height = snakeHeight(tg, vis);

hvGfxSetClip(hvg, xOff, yOff, width, height);

if ((tg->visibility == tvFull) || (tg->visibility == tvPack))
    {
    // score snakes by how many bases they cover
    for (item = tg->items; item != NULL; item = item->next)
	{
	lf = (struct linkedFeatures *)item;
	struct snakeFeature  *sf;

	lf->score = 0;
	for (sf =  (struct snakeFeature *)lf->components; sf != NULL;  sf = sf->next)
	    {
	    lf->score += sf->end - sf->start;
	    }
	}

    slSort(&tg->items, linkedFeaturesCmpScore);
    }

y = yOff;
for (item = tg->items; item != NULL; item = item->next)
    {
    if(tg->itemColor != NULL) 
	color = tg->itemColor(tg, item, hvg);
    tg->drawItemAt(tg, item, hvg, xOff, y, scale, font, color, vis);
    if (vis == tvFull)
	y += tg->itemHeight(tg, item);
    } 
}

//  this is a 16 color palette with every other color being a lighter version of
//  the color before it
static int snakePalette2[] =
{
0x1f77b4, 0xaec7e8, 0xff7f0e, 0xffbb78, 0x2ca02c, 0x98df8a, 0xd62728, 0xff9896, 0x9467bd, 0xc5b0d5, 0x8c564b, 0xc49c94, 0xe377c2, 0xf7b6d2, 0x7f7f7f, 0xc7c7c7, 0xbcbd22, 0xdbdb8d, 0x17becf, 0x9edae5
};

static int snakePalette[] =
{
0x1f77b4, 0xff7f0e, 0x2ca02c, 0xd62728, 0x9467bd, 0x8c564b, 0xe377c2, 0x7f7f7f, 0xbcbd22, 0x17becf
};

static Color hashColor(char *name)
{
bits32 hashVal = hashString(name);
unsigned int colorInt = snakePalette2[hashVal % (sizeof(snakePalette2)/sizeof(Color))];

return MAKECOLOR_32(((colorInt >> 16) & 0xff),((colorInt >> 8) & 0xff),((colorInt >> 0) & 0xff));
}

static void boundMapBox(struct hvGfx *hvg, int start, int end, int x, int y, int width, int height,
                       char *track, char *item, char *statusLine, char *directUrl, boolean withHgsid,
                       char *extra)
// make sure start x and end x position are on the screen
// otherwise the tracking box code gets confused
{
if (x > insideWidth)
    return;

if (x < 0)
    {
    width -= -x;
    x = 0;
    }

if (x + width > insideWidth)
    {
    width -= x + width - insideWidth;
    }

mapBoxHgcOrHgGene(hvg, start, end, x, y, width, height,
                       track, item, statusLine, directUrl, withHgsid,
                       extra);
}

static void snakeDrawAt(struct track *tg, void *item,
	struct hvGfx *hvg, int xOff, int y, double scale, 
	MgFont *font, Color color, enum trackVisibility vis)
/* Draw a single simple bed item at position. */
{
unsigned showSnpWidth = cartOrTdbInt(cart, tg->tdb, 
    SNAKE_SHOW_SNP_WIDTH, SNAKE_DEFAULT_SHOW_SNP_WIDTH);
struct linkedFeatures  *lf = (struct linkedFeatures *)item;


if (tg->visibility == tvFull) 
    calcFullSnake(tg, item);
else if (tg->visibility == tvPack)
    calcPackSnake(tg, item);

if (lf->components == NULL)
    return;

boolean isHalSnake = lf->isHalSnake;

struct snakeFeature  *sf = (struct snakeFeature *)lf->components, *prevSf = NULL;
int s = tg->itemStart(tg, item);
int sClp = (s < winStart) ? winStart : s;
int x1 = round((sClp - winStart)*scale) + xOff;
int textX = x1;
int yOff = y;
boolean withLabels = (withLeftLabels && (vis == tvFull) && !tg->drawName);
unsigned   labelColor = MG_BLACK;

// draw the labels
if (withLabels)
    {
    char *name = tg->itemName(tg, item);
    int nameWidth = mgFontStringWidth(font, name);
    int dotWidth = tl.nWidth/2;
    boolean snapLeft = FALSE;
    boolean drawNameInverted = FALSE;
    textX -= nameWidth + dotWidth;
    snapLeft = (textX < fullInsideX);
    /* Special tweak for expRatio in pack mode: force all labels
     * left to prevent only a subset from being placed right: */
    snapLeft |= (startsWith("expRatio", tg->tdb->type));
#ifdef IMAGEv2_NO_LEFTLABEL_ON_FULL
    if (theImgBox == NULL && snapLeft)
#else///ifndef IMAGEv2_NO_LEFTLABEL_ON_FULL
    if (snapLeft)        /* Snap label to the left. */
#endif ///ndef IMAGEv2_NO_LEFTLABEL_ON_FULL
        {
        textX = leftLabelX;
        assert(hvgSide != NULL);
        hvGfxUnclip(hvgSide);
        hvGfxSetClip(hvgSide, leftLabelX, yOff, fullInsideX - leftLabelX, tg->height);
        if(drawNameInverted)
            {
            int boxStart = leftLabelX + leftLabelWidth - 2 - nameWidth;
            hvGfxBox(hvgSide, boxStart, y, nameWidth+1, tg->heightPer - 1, color);
            hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer,
                        MG_WHITE, font, name);
            }
        else
            hvGfxTextRight(hvgSide, leftLabelX, y, leftLabelWidth-1, tg->heightPer,
                        labelColor, font, name);
        hvGfxUnclip(hvgSide);
        hvGfxSetClip(hvgSide, insideX, yOff, insideWidth, tg->height);
        }
    else
        {
        int pdfSlop=nameWidth/5;
        hvGfxUnclip(hvg);
        hvGfxSetClip(hvg, textX-1-pdfSlop, y, nameWidth+1+pdfSlop, tg->heightPer);
        if(drawNameInverted)
            {
            hvGfxBox(hvg, textX - 1, y, nameWidth+1, tg->heightPer-1, color);
            hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, MG_WHITE, font, name);
            }
        else
            hvGfxTextRight(hvg, textX, y, nameWidth, tg->heightPer, labelColor, font, name);
        hvGfxUnclip(hvg);
        hvGfxSetClip(hvg, insideX, yOff, insideWidth, tg->height);
        }
    }

// let's draw some blue bars for the duplications
struct hal_target_dupe_list_t* dupeList = lf->dupeList;

int count = 0;
if ((tg->visibility == tvFull) || (tg->visibility == tvPack)) 
    {
    for(; dupeList ; dupeList = dupeList->next, count++)
	{
	struct hal_target_range_t *range = dupeList->tRange;

	unsigned int colorInt = snakePalette[count % (sizeof(snakePalette)/sizeof(Color))];
	Color color = MAKECOLOR_32(((colorInt >> 16) & 0xff),((colorInt >> 8) & 0xff),((colorInt >> 0) & 0xff));

	for(; range; range = range->next)
	    {
	    int s = range->tStart;
	    int e = range->tStart + range->size;
	    int sClp = (s < winStart) ? winStart : s;
	    int eClp = (e > winEnd) ? winEnd : e;
	    int x1 = round((sClp - winStart)*scale) + xOff;
	    int x2 = round((eClp - winStart)*scale) + xOff;
	    hvGfxBox(hvg, x1, y , x2-x1, DUP_LINE_HEIGHT - 1 , color);
	    }
	}
    y+=DUP_LINE_HEIGHT;
    }

// now we're going to draw the boxes

s = sf->start;
int lastE = -1;
int lastS = -1;
int offY = y;
int lineHeight = tg->lineHeight ;
int  qs, qe;
int heightPer = tg->heightPer;
int lastX = -1;
int lastQEnd = 0;
int lastLevel = -1;
int e;
qe = lastQEnd = 0;
for (sf =  (struct snakeFeature *)lf->components; sf != NULL; lastQEnd = qe, prevSf = sf, sf = sf->next)
    {
    qs = sf->qStart;
    qe = sf->qEnd;
    if (vis == tvDense)
	y = offY;
    else if ((vis == tvPack) || (vis == tvSquish))
	y = offY + (sf->level * 1) * lineHeight;
    else if (vis == tvFull)
	y = offY + (sf->level * 2) * lineHeight;
    s = sf->start; e = sf->end;

    int sx, ex;
    if (!positiveRangeIntersection(winStart, winEnd, s, e))
	continue;
    sx = round((double)((int)s-winStart)*scale) + xOff;
    ex = round((double)((int)e-winStart)*scale) + xOff;

    // color by strand
    static Color darkBlueColor = 0;
    static Color darkRedColor = 0;
    if (darkRedColor == 0)
	{
	//the light blue: rgb(149, 204, 252)
	//the light red: rgb(232, 156, 156)
	darkRedColor = hvGfxFindColorIx(hvg, 232,156,156);
	darkBlueColor = hvGfxFindColorIx(hvg, 149,204,252);
	}
    
    char *colorBy = cartOrTdbString(cart, tg->tdb, 
	SNAKE_COLOR_BY, SNAKE_DEFAULT_COLOR_BY);

    extern Color getChromColor(char *name, struct hvGfx *hvg);
    if (sameString(colorBy, SNAKE_COLOR_BY_STRAND_VALUE))
	color = (sf->orientation == -1) ? darkRedColor : darkBlueColor;
    else if (sameString(colorBy, SNAKE_COLOR_BY_CHROM_VALUE))
	color = hashColor(sf->qName);
    else
	color =  darkBlueColor;

    int w = ex - sx;
    if (w == 0) 
	w = 1;
    assert(w > 0);
    char buffer[1024];
	
    if (vis == tvFull)
	safef(buffer, sizeof buffer, "%d-%d",sf->qStart,sf->qEnd);
    else
	safef(buffer, sizeof buffer, "%s:%d-%d",sf->qName,sf->qStart,sf->qEnd);
    if (sx < insideX)
	{
	int olap = insideX - sx;
	sx = insideX;
	w -= olap;
	}
    char qAddress[4096];
    if ((vis == tvFull) || (vis == tvPack) )
	{
	safef(qAddress, sizeof qAddress, "qName=%s&qs=%d&qe=%d&qWidth=%d",sf->qName,  qs, qe,  winEnd - winStart);
	boundMapBox(hvg, s, e, sx+1, y, w-2, heightPer, tg->track,
		    buffer, buffer, NULL, TRUE, qAddress);
	}
    hvGfxBox(hvg, sx, y, w, heightPer, color);

    // now draw the mismatches if we're at high enough resolution 
    if ((isHalSnake && sf->qSequence != NULL) && (winBaseCount < showSnpWidth) && ((vis == tvFull) || (vis == tvPack)))
	{
	char *twoBitString = trackDbSetting(tg->tdb, "twoBit");
	static struct twoBitFile *tbf = NULL;
	static char *lastTwoBitString = NULL;
	static struct dnaSeq *seq = NULL;
	static char *lastQName = NULL;

	// sequence for chain snakes is in 2bit files which we cache
	if (!isHalSnake)
	    {
	    if (twoBitString == NULL)
		twoBitString = "/gbdb/hg19/hg19.2bit";

	    if ((lastTwoBitString == NULL) ||
		differentString(lastTwoBitString, twoBitString))
		{
		if (tbf != NULL)
		    {
		    lastQName = NULL;
		    twoBitClose(&tbf);
		    }
		tbf = twoBitOpen(twoBitString);
		}

	    // we're reading in the whole chrom
	    if ((lastQName == NULL) || differentString(sf->qName, lastQName))
		seq = twoBitReadSeqFrag(tbf, sf->qName,  0, 0);
	    lastQName = sf->qName;
	    lastTwoBitString = twoBitString;
	    }

	char *ourDna;
	if (isHalSnake)
	    ourDna = sf->qSequence;
	else
	    ourDna = &seq->dna[sf->qStart];

	int seqLen = sf->qEnd - sf->qStart;
	toUpperN(ourDna, seqLen);
	if (!isHalSnake && (sf->orientation == -1))
	    reverseComplement(ourDna,seqLen);

	// get the reference sequence
	char *refDna;
        if (isHalSnake && differentString(tg->tdb->type, "pslSnake"))
	    {
	    refDna = sf->tSequence;
	    }
	else
	    {
	    struct dnaSeq *extraSeq = hDnaFromSeq(database, chromName, sf->start, sf->end, dnaUpper);
	    refDna = extraSeq->dna;
	    }
	int si = s;
	char *ptr1 = refDna;
	char *ptr2 = ourDna;
	for(; si < e; si++,ptr1++,ptr2++)
	    {
	    // if mismatch!  If reference is N ignore, if query is N, paint yellow
	    if ( (*ptr1 != *ptr2) && !((*ptr1 == 'N') || (*ptr1 == 'n')))
		{
		int misX1 = round((double)((int)si-winStart)*scale) + xOff;
		int misX2 = round((double)((int)(si+1)-winStart)*scale) + xOff;
		int w1 = misX2 - misX1;
		if (w1 < 1)
		    w1 = 1;

		Color boxColor = MG_RED;
		if ((*ptr2 == 'N') || (*ptr2 == 'n'))
		    boxColor = hvGfxFindRgb(hvg, &undefinedYellowColor);
		hvGfxBox(hvg, misX1, y, w1, heightPer, boxColor);
		}
	    }

	// if we're zoomed to base level, draw sequence of mismatch
	if (zoomedToBaseLevel)
	    {
	    int mysx = round((double)((int)s-winStart)*scale) + xOff;
	    int myex = round((double)((int)e-winStart)*scale) + xOff;
	    int myw = myex - mysx;
	    spreadAlignString(hvg, mysx, y, myw, heightPer, MG_WHITE, font, ourDna,
		refDna, seqLen, TRUE, FALSE);
	    }

	}
    sf->drawn = TRUE;
    lastLevel = sf->level;
    //lastX = x;
    }

if (vis != tvFull)
    return;

// now we're going to draw the lines between the blocks

lastX = -1;
lastQEnd = 0;
lastLevel = 0;
qe = lastQEnd = 0;
prevSf = NULL;
for (sf =  (struct snakeFeature *)lf->components; sf != NULL; lastQEnd = qe, prevSf = sf, sf = sf->next)
    {
    int y1, y2;
    int sx, ex;
    qs = sf->qStart;
    qe = sf->qEnd;
    if (lastLevel == sf->level)
	{
	y1 = offY + (lastLevel * 2) * lineHeight + lineHeight/2;
	y2 = offY + (sf->level * 2) * lineHeight + lineHeight/2;
	}
    else if (lastLevel > sf->level)
	{
	y1 = offY + (lastLevel * 2 ) * lineHeight;
	y2 = offY + (sf->level * 2 + 1) * lineHeight + lineHeight/3;
	}
    else
	{
	y1 = offY + (lastLevel * 2 + 1) * lineHeight - 1;
	y2 = offY + (sf->level * 2 ) * lineHeight - lineHeight/3;;
	}
    s = sf->start; e = sf->end;

    sx = round((double)((int)s-winStart)*scale) + xOff;
    ex = round((double)((int)e-winStart)*scale) + xOff;
    color = (sf->orientation == -1) ? MG_RED : MG_BLUE;

    if (lastX != -1)
	{
	char buffer[1024];
#define MG_ORANGE  0xff0082E6
	int color = MG_GRAY;

	if (lastQEnd != qs) {
            long long queryInsertSize = llabs(lastQEnd - qs);
            long long targetInsertSize;
            if (sf->orientation == 1)
                targetInsertSize = s - lastE;
            else
                targetInsertSize = lastS - e;
            int blue = 0;
            int red = 0;
            int green = 0;
            if (queryInsertSize > targetInsertSize) {
                double frac = ((double) queryInsertSize - targetInsertSize) / targetInsertSize;
                if (frac > 1.0)
                    frac = 1.0;
                red = 255 - 255 * frac;
                blue = 255 * frac;
            } else {
                double frac = ((double) targetInsertSize - queryInsertSize) / targetInsertSize;
                if (frac > 1.0)
                    frac = 1.0;
                red = 255 - 255 * frac;
                green = 255 * frac;
            }
            color = hvGfxFindColorIx(hvg, red, green, blue);
        }
        double queryGapNFrac = 0.0;
        double queryGapMaskedFrac = 0.0;
        if ((qs > lastQEnd) && qs - lastQEnd < 1000000) {
            // sketchy
            char *fileName = trackDbSetting(tg->tdb, "bigDataUrl");
            char *otherSpecies = trackDbSetting(tg->tdb, "otherSpecies");
            int handle = halOpenLOD(fileName, NULL);
            char *queryGapDna = halGetDna(handle, otherSpecies, sf->qName, lastQEnd, qs, NULL);
            long long numNs = 0;
            long long numMasked = 0;
            char *i = queryGapDna;
            while (*i != '\0') {
                if (*i == 'N' || *i == 'n') {
                    numNs++;
                    numMasked++;
                }
                if (*i == 'a' || *i == 't' || *i == 'g' || *i == 'c') {
                        numMasked++;
                }
                i++;
            }
            free(queryGapDna);
            queryGapMaskedFrac = ((double) numMasked) / (qs - lastQEnd);
            queryGapNFrac = ((double) numNs) / (qs - lastQEnd);
        }

	// draw the vertical orange bars if there is an insert in the other sequence
	if ((winBaseCount < showSnpWidth) )
	    {
	    if ((sf->orientation == 1) && (qs != lastQEnd) && (lastE == s))
		{
		hvGfxLine(hvg, sx, y2 - lineHeight/2 , sx, y2 + lineHeight/2, MG_ORANGE);
		safef(buffer, sizeof buffer, "%dbp (%.1lf%% N, %.1lf%% masked)", qs - lastQEnd, queryGapNFrac*100, queryGapMaskedFrac*100);
		boundMapBox(hvg, s, e, sx, y2 - lineHeight/2, 1, lineHeight, tg->track,
				    "foo", buffer, NULL, TRUE, NULL);
		safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                hvGfxTextCentered(hvg, sx - 10, y2 + lineHeight/2, 20, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
		}
	    else if ((sf->orientation == -1) && (qs != lastQEnd) && (lastS == e))
		{
		hvGfxLine(hvg, ex, y2 - lineHeight/2 , ex, y2 + lineHeight/2, MG_ORANGE);
		safef(buffer, sizeof buffer, "%dbp (%.1lf%% N, %.1lf%% masked)", qs - lastQEnd, queryGapNFrac*100, queryGapMaskedFrac*100);
		boundMapBox(hvg, s, e, ex, y2 - lineHeight/2, 1, lineHeight, tg->track,
				    "foo", buffer, NULL, TRUE, NULL);
		safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                hvGfxTextCentered(hvg, ex - 10, y2 + lineHeight/2, 20, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
		}
	    }

	// now draw the lines between blocks
	if ((!((lastX == sx) && (y1 == y2))) &&
	    (sf->drawn  || ((prevSf != NULL) && (prevSf->drawn))) &&
	    (((lastE >= winStart) && (lastE <= winEnd)) || 
	    ((s > winStart) && (s < winEnd))))
	    {
	    if (lastLevel == sf->level)
		{
                if (sf->orientation == 1)
                    safef(buffer, sizeof buffer, "%dbp (%dbp in ref) (%.1lf%% N, %.1lf%% masked)", qs - lastQEnd, s - lastE, queryGapNFrac*100, queryGapMaskedFrac*100);
                else
                    safef(buffer, sizeof buffer, "%dbp (%dbp in ref) (%.1lf%% N, %.1lf%% masked)", qs - lastQEnd, lastS - e, queryGapNFrac*100, queryGapMaskedFrac*100);
		if (sf->orientation == -1)
		    {
		    if (lastX != ex)
			{
			hvGfxLine(hvg, ex, y1, lastX, y2, color);
			boundMapBox(hvg, s, e, ex, y1, lastX-ex, 1, tg->track,
				"", buffer, NULL, TRUE, NULL);
                        if (lastQEnd != qs) {
                            safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                            hvGfxTextCentered(hvg, ex, y2 + lineHeight/2, lastX-ex, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
                        }
			}
		    }
		else
		    {
		    if (lastX != sx)
			{
			hvGfxLine(hvg, lastX, y1, sx, y2, color);
			boundMapBox(hvg, s, e, lastX, y1, sx-lastX, 1, tg->track,
				"", buffer, NULL, TRUE, NULL);
                        if (lastQEnd != qs) {
                            safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                            hvGfxTextCentered(hvg, lastX, y2 + lineHeight/2, sx-lastX, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
                        }
			}
		    }
		}
	    else if (lastLevel > sf->level)
		{
		hvGfxLine(hvg, lastX, y1, sx, y2, color);
		hvGfxLine(hvg, sx, y2, sx, y2 - lineHeight - lineHeight/3, color);
		char buffer[1024];
		safef(buffer, sizeof buffer, "%d-%d %dbp gap (%.1lf%% N, %.1lf%% masked)",prevSf->qStart,prevSf->qEnd, qs - lastQEnd, queryGapNFrac*100, queryGapMaskedFrac*100);
		boundMapBox(hvg, s, e, sx, y2 - lineHeight - lineHeight/3, 2, lineHeight + lineHeight/3, tg->track,
	                    "", buffer, NULL, TRUE, NULL);
		safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                if (lastQEnd != qs)
                    hvGfxTextCentered(hvg, sx - 10, y2 + lineHeight/2, 20, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);

		}
	    else
		{
		char buffer[1024];
		safef(buffer, sizeof buffer, "%d-%d %dbp gap (%.1lf%% N, %.1lf%% masked)",prevSf->qStart,prevSf->qEnd, qs - lastQEnd, queryGapNFrac*100, queryGapMaskedFrac*100);
		if (sf->orientation == -1)
		    {
		    hvGfxLine(hvg, lastX-1, y1, ex, y2, color);
		    hvGfxLine(hvg, ex, y2, ex, y2 + lineHeight , color);
		    boundMapBox(hvg, s, e, ex-1, y2, 2, lineHeight , tg->track,
				"", buffer, NULL, TRUE, NULL);
                    safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                    if (lastQEnd != qs)
                        hvGfxTextCentered(hvg, ex - 10, y2 + lineHeight, 20, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
		    }
		else
		    {
		    hvGfxLine(hvg, lastX-1, y1, sx, y2, color);
		    hvGfxLine(hvg, sx, y2, sx, y2 + lineHeight , color);
		    boundMapBox(hvg, s, e, sx-1, y2, 2, lineHeight , tg->track,
				"", buffer, NULL, TRUE, NULL);

                    safef(buffer, sizeof buffer, "%dbp", qs - lastQEnd);
                    if (lastQEnd != qs)
                        hvGfxTextCentered(hvg, sx - 10, y2 + lineHeight, 20, INSERT_TEXT_HEIGHT, MG_ORANGE, font, buffer);
		    }
		}
	    }
	}
    if (sf->orientation == -1)
	lastX = sx;
    else
	lastX = ex;
    lastS = s;
    lastE = e;
    lastLevel = sf->level;
    lastQEnd = qe;
    }
}

static char *doChromIxSearch(char *trixFile, char *searchName)
/* search ixFile for the searchName, return name if found */
{
struct trix *trix = trixOpen(trixFile);
char *trixWords[1];
int trixWordCount = 1;
trixWords[0] = strLower(searchName);
char *name = NULL;  // assume NOT found

struct trixSearchResult *tsList = trixSearch(trix, trixWordCount, trixWords, tsmExact);
if (tsList)
   name = tsList->itemId;  // FOUND
return name;
}

static struct hal_block_results_t *pslSnakeBlocks(char *fileName, struct track *track, char *chrom, unsigned start, unsigned end, unsigned int maxItems)
/* create HAL-like blocks from a bigPsl file. */
{
struct hal_block_results_t *head;

AllocVar(head);

struct lm *lm = lmInit(0);
//struct bigBedInterval *bb, *bbList = bigBedSelectRangeExt(track, chrom, start, end, lm, maxItems);
struct bigBedInterval *bb, *bbList = bigBedSelectRangeExt(track, chrom, 0, hChromSize(database,chromName), lm, maxItems);

struct bbiFile *bbi = fetchBbiForTrack(track);
int seqTypeField =  0;
if (sameString(track->tdb->type, "bigPsl"))
    {
    seqTypeField =  bbExtraFieldIndex(bbi, "seqType");
    }
bbiFileClose(&bbi);
for (bb = bbList; bb != NULL; bb = bb->next)
    {
    char *seq, *cds;
    struct psl *psl = pslFromBigPsl(chromName, bb, seqTypeField,  &seq, &cds); 
    unsigned *targetStart = psl->tStarts;
    unsigned *queryStart = psl->qStarts;
    unsigned *size = psl->blockSizes;
    int ii;

    if ((seq != NULL) && (psl->strand[0] == '-'))
        reverseComplement(seq, strlen(seq));
    for(ii = 0; ii < psl->blockCount; ii++)
        {
        struct hal_block_t *block;
        AllocVar(block);
        slAddHead(&head->mappedBlocks, block);

        block->qChrom = psl->qName;
        block->tStart = *targetStart++;
        block->qStart = *queryStart++;
        block->size = *size++;
        block->strand = psl->strand[0];
        block->qSequence = &seq[block->qStart];

        if (block->strand == '-')
            block->qStart = psl->qSize - block->qStart;
        }
    }

return head;
}

void halSnakeLoadItems(struct track *tg)
// load up a snake from a HAL file.   This code is called in threads
// so *no* use of globals please. All but full snakes are read into a single
// linked feature.
{
unsigned showSnpWidth = cartOrTdbInt(cart, tg->tdb, 
    SNAKE_SHOW_SNP_WIDTH, SNAKE_DEFAULT_SHOW_SNP_WIDTH);

char * chromAlias = NULL;	// create later when needed
char * chromAliasFile = trackDbSetting(tg->tdb, "searchTrix");
if (chromAliasFile)
   chromAlias = doChromIxSearch(chromAliasFile, chromName);

char *aliasName = chromName;
if (chromAlias)
   {
       if (differentWord(chromAlias, aliasName))
          aliasName = chromAlias;
   }

boolean isPsl = sameString(tg->tdb->type, "pslSnake");

// if we have a network error we want to put out a message about it
struct errCatch *errCatch = errCatchNew();
if (errCatchStart(errCatch))
    {
    char *fileName = trackDbSetting(tg->tdb, "bigDataUrl");
    char *otherSpecies = trackDbSetting(tg->tdb, "otherSpecies");
    char *errString = "<HAL error message not set>";
    int handle = -1;
    if (!isPsl)
        {
        handle = halOpenLOD(fileName, &errString);
        if (handle < 0)
            {
            errAbort("HAL open error: %s\n", errString);
            goto out;
            }
        }
    boolean isPackOrFull = (tg->visibility == tvFull) || 
	(tg->visibility == tvPack);
    hal_dup_type_t dupMode =  (isPackOrFull) ? HAL_QUERY_AND_TARGET_DUPS :
	HAL_QUERY_DUPS;
    hal_seqmode_type_t needSeq = isPackOrFull && (winBaseCount < showSnpWidth) ? HAL_LOD0_SEQUENCE : HAL_NO_SEQUENCE;
    int mapBackAdjacencies = (tg->visibility == tvFull);
    char codeVarName[1024];
    safef(codeVarName, sizeof codeVarName, "%s.coalescent", tg->tdb->track);
    char *coalescent = cartOptionalString(cart, codeVarName);
    char *otherDbName = trackHubSkipHubName(database);
    struct hal_block_results_t *head = NULL;
    if (isPsl)
        {
        head = pslSnakeBlocks(fileName, tg, chromName, winStart, winEnd, 10000000);
        }
    else 
        {
        struct slName *aliasList = chromAliasFindAliases(chromName);
        struct slName *nativeName = newSlName(aliasName);
        slAddHead(&aliasList, nativeName);
        for (; aliasList; aliasList = aliasList->next)
            {
            head = halGetBlocksInTargetRange(handle, otherSpecies, otherDbName, aliasList->name, winStart, winEnd, 0, needSeq, dupMode,mapBackAdjacencies, coalescent, &errString);
            if (head != NULL) 
                break;
            }
        }

    // did we get any blocks from HAL
    if (head == NULL)
	{
	errAbort("HAL get blocks error: %s\n", errString);
	goto out;
	}
    struct hal_block_t* cur = head->mappedBlocks;
    struct linkedFeatures *lf = NULL;
    struct hash *qChromHash = newHash(5);
    struct linkedFeatures *lfList = NULL;
    char buffer[4096];

#ifdef NOTNOW
    struct hal_target_dupe_list_t* targetDupeBlocks = head->targetDupeBlocks;

    for(;targetDupeBlocks; targetDupeBlocks = targetDupeBlocks->next)
	{
	printf("<br>id: %d qChrom %s\n", targetDupeBlocks->id, targetDupeBlocks->qChrom);
	struct hal_target_range_t *range = targetDupeBlocks->tRange;
	for(; range; range = range->next)
	    {
	    printf("<br>   %ld : %ld\n", range->tStart, range->size);
	    }
	}
#endif

    while (cur)
    {
	struct hashEl* hel;

	if (tg->visibility == tvFull)
	    safef(buffer, sizeof buffer, "%s", cur->qChrom);
	else
	    {
	    // make sure the block is on the screen 
	    if (!positiveRangeIntersection(winStart, winEnd, cur->tStart,  cur->tStart + cur->size))
		{
		cur = cur->next;
		continue;
		}
	    safef(buffer, sizeof buffer, "allInOne");
	    }

	if ((hel = hashLookup(qChromHash, buffer)) == NULL)
	    {
	    AllocVar(lf);
	    lf->isHalSnake = TRUE;
	    slAddHead(&lfList, lf);
	    lf->start = 0;
	    lf->end = 1000000000;
	    lf->grayIx = maxShade;
	    lf->name = cloneString(buffer);
	    lf->extra = cloneString(buffer);
	    lf->orientation = (cur->strand == '+') ? 1 : -1;
	    hashAdd(qChromHash, lf->name, lf);

	    // now figure out where the duplication bars go
	    struct hal_target_dupe_list_t* targetDupeBlocks = head->targetDupeBlocks;

	    if ((tg->visibility == tvPack) || (tg->visibility == tvFull))
		for(;targetDupeBlocks; targetDupeBlocks = targetDupeBlocks->next)
		    {
		    if ((tg->visibility == tvPack) ||
			((tg->visibility == tvFull) &&
			 (sameString(targetDupeBlocks->qChrom, cur->qChrom))))
			{
			struct hal_target_dupe_list_t* dupeList;
			AllocVar(dupeList);
			*dupeList = *targetDupeBlocks;
			slAddHead(&lf->dupeList, dupeList);
			// TODO: should clone the target_range structures
			// rather than copying them
			}
		    }
	    }
	else
	    {
	    lf = hel->val;
	    }

	struct snakeFeature  *sf;
	AllocVar(sf);
	slAddHead(&lf->components, sf);
	
	sf->start = cur->tStart;
	sf->end = cur->tStart + cur->size;
	sf->qStart = cur->qStart;
	sf->qEnd = cur->qStart + cur->size;
	sf->orientation = (cur->strand == '+') ? 1 : -1;
	sf->tSequence = cloneString(cur->tSequence);
	sf->qSequence = cloneString(cur->qSequence);
	if (sf->tSequence != NULL)
	    toUpperN(sf->tSequence, strlen(sf->tSequence));
	if (sf->qSequence != NULL)
	    toUpperN(sf->qSequence, strlen(sf->qSequence));
	sf->qName = cur->qChrom;

	cur = cur->next;
    }
    if (tg->visibility == tvFull)
	{
	for(lf=lfList; lf ; lf = lf->next)
	    {
	    slSort(&lf->components, snakeFeatureCmpQStart);
	    }
	}
    else if ((tg->visibility == tvPack) && (lfList != NULL))
	{
	assert(lfList->next == NULL);
	slSort(&lfList->components, snakeFeatureCmpTStart);
	}
    
    //halFreeBlocks(head);
    //halClose(handle, myThread);

    tg->items = lfList;
    }

out:
errCatchEnd(errCatch);
if (errCatch->gotError)
    {
    tg->networkErrMsg = cloneString(errCatch->message->string);
    tg->drawItems = bigDrawWarning;
    tg->totalHeight = bigWarnTotalHeight;
    }
errCatchFree(&errCatch);
}

void halSnakeDrawLeftLabels(struct track *tg, int seqStart, int seqEnd,
        struct hvGfx *hvg, int xOff, int yOff, int width, int height,
        boolean withCenterLabels, MgFont *font,
        Color color, enum trackVisibility vis)
/* Draw left label (shortLabel) in pack and dense modes. */
{
if ((vis == tvDense) ||  (vis == tvPack))
    {
    hvGfxSetClip(hvgSide, leftLabelX, yOff + tg->lineHeight, insideWidth, tg->height);
    hvGfxTextRight(hvgSide, leftLabelX, yOff + tg->lineHeight, leftLabelWidth-1, tg->lineHeight,
                   color, font, tg->shortLabel);
    hvGfxUnclip(hvgSide);
    }
}

void halSnakeMethods(struct track *tg, struct trackDb *tdb, 
	int wordCount, char *words[])
{
linkedFeaturesMethods(tg);
tg->canPack = tdb->canPack = TRUE;
tg->loadItems = halSnakeLoadItems;
tg->drawItems = snakeDraw;
tg->mapItemName = lfMapNameFromExtra;
tg->subType = lfSubChain;
//tg->extraUiData = (void *) chainCart;
tg->totalHeight = snakeHeight; 
tg->drawLeftLabels = halSnakeDrawLeftLabels;

tg->drawItemAt = snakeDrawAt;
tg->itemHeight = snakeItemHeight;
tg->nextItemButtonable = FALSE;
}
#endif  // USE_HAL

#ifdef NOTNOW

// from here down are routines to support the visualization of chains as snakes
// this code is currently BROKEN, and may be removed completely in the future

struct cartOptions
    {
    enum chainColorEnum chainColor; /*  ChromColors, ScoreColors, NoColors */
    int scoreFilter ; /* filter chains by score if > 0 */
    };

// mySQL code to read in chains

static void doQuery(struct sqlConnection *conn, char *fullName, 
			struct lm *lm, struct hash *hash, 
			int start, int end,  boolean isSplit, int chainId)
/* doQuery- check the database for chain elements between
 * 	start and end.  Use the passed hash to resolve chain
 * 	id's and place the elements into the right
 * 	linkedFeatures structure
 */
{
struct sqlResult *sr = NULL;
char **row;
struct linkedFeatures *lf;
struct snakeFeature *sf;
struct dyString *query = dyStringNew(1024);

if (chainId == -1)
    {
    sqlDyStringPrintf(query, 
	"select chainId,tStart,tEnd,qStart from %sLink ", fullName);
    if (isSplit)
	sqlDyStringPrintf(query,"force index (bin) ");
    sqlDyStringPrintf(query, "where ",
    }
else
    sqlDyStringPrintf(query, 
	"select chainId, tStart,tEnd,qStart from %sLink where chainId=%d and ",
	fullName, chainId);
if (!isSplit)
    sqlDyStringPrintf(query, "tName='%s' and ", chromName);
hAddBinToQuery(start, end, query);
sqlDyStringPrintf(query, "tStart<%u and tEnd>%u", end, start);
sr = sqlGetResult(conn, query->string);

/* Loop through making up simple features and adding them
 * to the corresponding linkedFeature. */
while ((row = sqlNextRow(sr)) != NULL)
    {
    lf = hashFindVal(hash, row[0]);
    if (lf != NULL)
	{
	struct chain *pChain = lf->extra;
	lmAllocVar(lm, sf);
	sf->start = sqlUnsigned(row[1]);
	sf->end = sqlUnsigned(row[2]);
	sf->qStart = sqlUnsigned(row[3]); 

	sf->qEnd = sf->qStart + (sf->end - sf->start);
	if ((pChain) && pChain->qStrand == '-')
	    {
	    int temp;

	    temp = sf->qStart;
	    sf->qStart = pChain->qSize - sf->qEnd;
	    sf->qEnd = pChain->qSize - temp;
	    }
	sf->orientation = lf->orientation;
	slAddHead(&lf->components, sf);
	}
    }
sqlFreeResult(&sr);
dyStringFree(&query);
}

static void fixItems(struct linkedFeatures *lf)
// put all chain blocks from a single query chromosome into one
// linkedFeatures structure
{
struct linkedFeatures *firstLf, *next;
struct snakeFeature  *sf,  *nextSf;

firstLf = lf;
for (;lf; lf = next)
    {
    next = lf->next;
    if (!sameString(firstLf->name, lf->name) && (lf->components != NULL))
	{
	slSort(&firstLf->components, snakeFeatureCmpQStart);
	firstLf = lf;
	}
    for (sf =  (struct snakeFeature *)lf->components; sf != NULL; sf = nextSf)
	{
	sf->qName = lf->name;
	sf->orientation = lf->orientation;
	nextSf = sf->next;
	if (firstLf != lf)
	    {
	    lf->components = NULL;
	    slAddHead(&firstLf->components, sf);
	    }
	}
    }

if (firstLf != NULL)
    {
    slSort(&firstLf->components, snakeFeatureCmpQStart);
    firstLf->next = 0;
    }
}


static void loadLinks(struct track *tg, int seqStart, int seqEnd,
         enum trackVisibility vis)
// load up the chain elements into linkedFeatures
{
int start, end, extra;
char fullName[64];
int maxOverLeft = 0, maxOverRight = 0;
int overLeft, overRight;
struct linkedFeatures *lf;
struct lm *lm;
struct hash *hash;	/* Hash of chain ids. */
struct sqlConnection *conn;
lm = lmInit(1024*4);
hash = newHash(0);
conn = hAllocConn(database);

/* Make up a hash of all linked features keyed by
 * id, which is held in the extras field.  */
for (lf = tg->items; lf != NULL; lf = lf->next)
    {
    char buf[256];
    struct chain *pChain = lf->extra;
    safef(buf, sizeof(buf), "%d", pChain->id);
    hashAdd(hash, buf, lf);
    overRight = lf->end - seqEnd;
    if (overRight > maxOverRight)
	maxOverRight = overRight;
    overLeft = seqStart - lf->start ;
    if (overLeft > maxOverLeft)
	maxOverLeft = overLeft;
    }

if (hash->size)
    {
    boolean isSplit = TRUE;
    /* Make up range query. */
    safef(fullName, sizeof fullName, "%s_%s", chromName, tg->table);
    if (!hTableExists(database, fullName))
	{
	strcpy(fullName, tg->table);
	isSplit = FALSE;
	}

    /* in dense mode we don't draw the lines 
     * so we don't need items off the screen 
     */
    if (vis == tvDense)
	doQuery(conn, fullName, lm,  hash, seqStart, seqEnd,  isSplit, -1);
    else
	{
	/* if chains extend beyond edge of window we need to get 
	 * elements that are off the screen
	 * in both directions so we know whether to draw
	 * one or two lines to the edge of the screen.
	 */
#define STARTSLOP	0
#define MULTIPLIER	10
#define MAXLOOK		100000
	extra = (STARTSLOP < maxOverLeft) ? STARTSLOP : maxOverLeft;
	start = seqStart - extra;
	extra = (STARTSLOP < maxOverRight) ? STARTSLOP : maxOverRight;
	end = seqEnd + extra;
	doQuery(conn, fullName, lm,  hash, start, end,  isSplit, -1);
	}
    }
hFreeConn(&conn);
}

void snakeLoadItems(struct track *tg)
// Load chains from a mySQL database
{
char *track = tg->table;
struct chain chain;
int rowOffset;
char **row;
struct sqlConnection *conn = hAllocConn(database);
struct sqlResult *sr = NULL;
struct linkedFeatures *list = NULL, *lf;
int qs;
char optionChr[128]; /* Option -  chromosome filter */
char *optionChrStr;
char extraWhere[128] ;
struct cartOptions *chainCart;
struct chain *pChain;

chainCart = (struct cartOptions *) tg->extraUiData;

safef( optionChr, sizeof(optionChr), "%s.chromFilter", tg->table);
optionChrStr = cartUsualString(cart, optionChr, "All");
int ourStart = winStart;
int ourEnd = winEnd;

// we're grabbing everything now.. we really should be 
// doing this as a preprocessing stage, rather than at run-time
ourStart = 0;
ourEnd = 500000000;
//ourStart = winStart;
//ourEnd = winEnd;
if (startsWith("chr",optionChrStr)) 
    {
    sqlSafef(extraWhere, sizeof(extraWhere), 
            "qName = \"%s\" and score > %d",optionChrStr, 
            chainCart->scoreFilter);
    sr = hRangeQuery(conn, track, chromName, ourStart, ourEnd, 
            extraWhere, &rowOffset);
    }
else
    {
    if (chainCart->scoreFilter > 0)
        {
        sqlSafef(extraWhere, sizeof(extraWhere), 
                "score > \"%d\"",chainCart->scoreFilter);
        sr = hRangeQuery(conn, track, chromName, ourStart, ourEnd, 
                extraWhere, &rowOffset);
        }
    else
        {
        sqlSafef(extraWhere, sizeof(extraWhere), " ");
        sr = hRangeQuery(conn, track, chromName, ourStart, ourEnd, 
                NULL, &rowOffset);
        }
    }
while ((row = sqlNextRow(sr)) != NULL)
    {
    chainHeadStaticLoad(row + rowOffset, &chain);
    AllocVar(pChain);
    *pChain = chain;
    AllocVar(lf);
    lf->start = lf->tallStart = chain.tStart;
    lf->end = lf->tallEnd = chain.tEnd;
    lf->grayIx = maxShade;
    if (chainCart->chainColor == chainColorScoreColors)
	{
	float normScore = sqlFloat((row+rowOffset)[11]);
	lf->grayIx = (int) ((float)maxShade * (normScore/100.0));
	if (lf->grayIx > (maxShade+1)) lf->grayIx = maxShade+1;
	lf->score = normScore;
	}
    else
	lf->score = chain.score;

    lf->filterColor = -1;

    if (chain.qStrand == '-')
	{
	lf->orientation = -1;
        qs = chain.qSize - chain.qEnd;
	}
    else
        {
	lf->orientation = 1;
	qs = chain.qStart;
	}
    char buffer[1024];
    safef(buffer, sizeof(buffer), "%s", chain.qName);
    lf->name = cloneString(buffer);
    lf->extra = pChain;
    slAddHead(&list, lf);
    }

/* Make sure this is sorted if in full mode. Sort by score when
 * coloring by score and in dense */
if (tg->visibility != tvDense)
    slSort(&list, linkedFeaturesCmpStart);
else if ((tg->visibility == tvDense) &&
	(chainCart->chainColor == chainColorScoreColors))
    slSort(&list, chainCmpScore);
else
    slReverse(&list);
tg->items = list;


/* Clean up. */
sqlFreeResult(&sr);
hFreeConn(&conn);

/* now load the items */
loadLinks(tg, ourStart, ourEnd, tg->visibility);
lf=tg->items;
fixItems(lf);
}	/*	chainLoadItems()	*/

void snakeMethods(struct track *tg, struct trackDb *tdb, 
	int wordCount, char *words[])
/* Fill in custom parts of alignment chains. */
{

struct cartOptions *chainCart;

AllocVar(chainCart);

boolean normScoreAvailable = chainDbNormScoreAvailable(tdb);

/*	what does the cart say about coloring option	*/
chainCart->chainColor = chainFetchColorOption(cart, tdb, FALSE);

chainCart->scoreFilter = cartUsualIntClosestToHome(cart, tdb,
	FALSE, SCORE_FILTER, 0);


linkedFeaturesMethods(tg);
tg->itemColor = lfChromColor;	/*	default coloring option */

/*	if normScore column is available, then allow coloring	*/
if (normScoreAvailable)
    {
    switch (chainCart->chainColor)
	{
	case (chainColorScoreColors):
	    tg->itemColor = chainScoreColor;
	    tg->colorShades = shadesOfGray;
	    break;
	case (chainColorNoColors):
	    setNoColor(tg);
	    break;
	default:
	case (chainColorChromColors):
	    break;
	}
    }
else
    {
    char option[128]; /* Option -  rainbow chromosome color */
    char *optionStr;	/* this old option was broken before */

    safef(option, sizeof(option), "%s.color", tg->table);
    optionStr = cartUsualString(cart, option, "on");
    if (differentWord("on",optionStr))
	{
	setNoColor(tg);
	chainCart->chainColor = chainColorNoColors;
	}
    else
	chainCart->chainColor = chainColorChromColors;
    }

tg->canPack = FALSE;
tg->loadItems = snakeLoadItems;
tg->drawItems = snakeDraw;
tg->mapItemName = lfMapNameFromExtra;
tg->subType = lfSubChain;
tg->extraUiData = (void *) chainCart;
tg->totalHeight = snakeHeight; 

tg->drawItemAt = snakeDrawAt;
tg->itemHeight = snakeItemHeight;
}

static Color chainScoreColor(struct track *tg, void *item, struct hvGfx *hvg)
{
struct linkedFeatures *lf = (struct linkedFeatures *)item;

return(tg->colorShades[lf->grayIx]);
}

static Color chainNoColor(struct track *tg, void *item, struct hvGfx *hvg)
{
return(tg->ixColor);
}

static void setNoColor(struct track *tg)
{
tg->itemColor = chainNoColor;
tg->color.r = 0;
tg->color.g = 0;
tg->color.b = 0;
tg->color.a = 255;
tg->altColor.r = 127;
tg->altColor.g = 127;
tg->altColor.b = 127;
tg->altColor.a = 255;
tg->ixColor = MG_BLACK;
tg->ixAltColor = MG_GRAY;
}
#endif
