/* Deal with factorSource type tracks, which are used to display 
 * transcription factor binding sites as measured in a number of
 * tissue or cell type sources. */

/* 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 "hash.h"
#include "linefile.h"
#include "jksql.h"
#include "hdb.h"
#include "hgTracks.h"
#include "expRecord.h"
#include "dystring.h"
#include "factorSource.h"
#include "bed6FloatScore.h"

static struct factorSource *loadOne(char **row)
/* Load up factorSource from array of strings. */
{
return factorSourceLoad(row);
}

/* Save info about factors and their motifs */
struct factorSourceInfo {
    boolean showCellAbbrevs;
    boolean showExpCounts;
    struct hash *factorExpCounts;
    struct hash *factorChoices;
    struct hash *motifTargets;
    struct bed6FloatScore *motifs;
};

boolean factorFilter(struct track *track, void *item)
/* Returns true if an item should be passed by the filter. NOTE: single filter supported here*/
{
struct hash *factorHash = ((struct factorSourceInfo *)track->extraUiData)->factorChoices;
if (track->extraUiData != NULL && factorHash != NULL)
    if (hashLookup(factorHash, ((struct factorSource *)item)->name) != NULL)
        return TRUE;
return FALSE;
}

static void factorSourceLoadItems(struct track *track)
/* Load all items (and motifs if table is present) in window */
{
bedLoadItem(track, track->table, (ItemLoader)loadOne);
if (track->items == NULL)
    return;

struct factorSourceInfo *fsInfo = NULL;
AllocVar(fsInfo);
track->extraUiData = fsInfo;

// Check UI setting to show cell abbreviations or counts
char varName[64];
safef(varName, sizeof(varName), "%s.showCellAbbrevs", track->track);
fsInfo->showCellAbbrevs = cartUsualBoolean(cart, varName, TRUE);
safef(varName, sizeof(varName), "%s.showExpCounts", track->track);
fsInfo->showExpCounts = cartUsualBoolean(cart, varName, TRUE);

if (fsInfo->showExpCounts)
    {
    struct sqlConnection *conn = hAllocConn(database);
    char query[256];
    char *inputTrackTable = trackDbRequiredSetting(track->tdb, "inputTrackTable");
    sqlSafef(query, sizeof(query), 
                "select factor, count(*) as num from %s group by factor", inputTrackTable);
    fsInfo->factorExpCounts = sqlQuickHash(conn, query);
    // TODO: If we're worried about performance, can remake as hash of ints
    hFreeConn(&conn);
    }

// Filter factors based on multi-select
filterBy_t *filter = filterBySetGet(track->tdb, cart, NULL);
if (filter != NULL && filter->slChoices != NULL && differentString(filter->slChoices->name, "All"))
    {
    struct slName *choice;
    struct hash *factorHash = newHash(0);
    for (choice = filter->slChoices; choice != NULL; choice = choice->next)
        {
        hashAdd(factorHash, choice->name, NULL);
        }
    fsInfo->factorChoices = factorHash;
    filterItems(track, factorFilter, "include");
}

// Motifs
safef(varName, sizeof(varName), "%s.highlightMotifs", track->track);
if (!cartUsualBoolean(cart, varName, trackDbSettingClosestToHomeOn(track->tdb, "motifDrawDefault")))
    return;

char *motifMaxWindow = trackDbSetting(track->tdb, "motifMaxWindow");
if (motifMaxWindow != NULL)
    if (winEnd - winStart > sqlUnsigned(motifMaxWindow))
        return;

char *motifTable = trackDbSetting(track->tdb, "motifTable");
if (motifTable == NULL)
    return;

struct sqlConnection *conn = hAllocConn(database);
if ((track->items != NULL) & sqlTableExists(conn, motifTable))
    {

    // Load all motifs for items in window (including motifs outside of window)
    struct slList *items = track->items;
    int winStartSave = winStart;
    int winEndSave = winEnd;

    winStart = min(winStart, ((struct factorSource *)items)->chromStart);
    winEnd = max(winEnd, ((struct factorSource *)items)->chromEnd);
    bedLoadItem(track, motifTable, (ItemLoader)bed6FloatScoreLoad);
    fsInfo->motifs = track->items;

    track->items = items;
    winStart = winStartSave;
    winEnd = winEndSave;

    char *motifMapTable = trackDbSetting(track->tdb, "motifMapTable");
    if (motifMapTable != NULL && sqlTableExists(conn, motifMapTable))
        {
        // Load map table 
        char query[256];
        struct sqlResult *sr = NULL;
        char **row = NULL;
        sqlSafef(query, sizeof(query), "select target, motif from %s", motifMapTable);
        sr = sqlGetResult(conn, query);
        struct hash *targetHash = newHash(0);
        while ((row = sqlNextRow(sr)) != NULL)
            {
            char *target = row[0];
            char *motifs = row[1];   // string, comma-sep list, or empty string
            if (motifs[0] != 0)
                hashAdd(targetHash, target, slNameListFromString(motifs, ','));
            }
        sqlFreeResult(&sr);
        fsInfo->motifTargets = targetHash;
        }
    }
hFreeConn(&conn);
}

static int factorSourceRightPixels(struct track *track, void *item)
/* Return number of pixels we need to the right. */
{
struct factorSourceInfo *fsInfo = (struct factorSourceInfo *)track->extraUiData;

// can we test this here ?
#ifdef BETTER_LAYOUT
if (!(vis == tvFull || vis == tvPack))
    return 0;
#endif
struct factorSource *fs = item;
struct dyString *dy = dyStringNew(0);
if (fsInfo->showExpCounts)
    {
    dyStringPrintf(dy, "%d", fs->expCount);
    char *s = hashFindVal(fsInfo->factorExpCounts, fs->name);
    int allCount = s ? sqlUnsigned(s) : 0;
    if (fs->expCount != allCount)
        dyStringPrintf(dy, "/%d", allCount);
    if (fsInfo->showCellAbbrevs)
        dyStringAppend(dy, " ");
    }

if (fsInfo->showCellAbbrevs)
    {
    int i;
    for (i=0; i<fs->expCount; ++i)
        {
        int expNum = fs->expNums[i];
        char *label = track->sources[expNum]->name;
        dyStringAppend(dy, label);
        }
    }
int result = mgFontStringWidth(tl.font, dy->string);
dyStringFree(&dy);
return result;
}

static void factorSourceDrawItemAt(struct track *track, void *item, 
	struct hvGfx *hvg, int xOff, int y, 
	double scale, MgFont *font, Color color, enum trackVisibility vis)
/* Draw factorSource item at a particular position. */
{
/* Figure out maximum score and draw box based on it. */
struct factorSource *fs = item;
int grayIx = grayInRange(fs->score, 0, 1000);
color = shadesOfGray[grayIx];

/* Calculate position, and draw box around where we are. */
int heightPer = track->heightPer;
int x1 = round((double)((int)fs->chromStart-winStart)*scale) + xOff;
int x2 = round((double)((int)fs->chromEnd-winStart)*scale) + xOff;
int w = x2-x1;
if (w < 1)
    w = 1;
hvGfxBox(hvg, x1, y, w, heightPer, color);
if (vis != tvFull && vis != tvPack)
    return;

/* Draw text to the right */
int x = x2 + tl.mWidth/2;
struct factorSourceInfo *fsInfo = (struct factorSourceInfo *)track->extraUiData;
if (fsInfo->showExpCounts)
    {
    struct dyString *ds = dyStringNew(0);
    dyStringPrintf(ds, "%d", fs->expCount);
    char *s = hashFindVal(fsInfo->factorExpCounts, fs->name);
    int allCount = s ? sqlUnsigned(s) : 0;
    if (fs->expCount != allCount)
        dyStringPrintf(ds, "/%d", allCount);
    if (fsInfo->showCellAbbrevs)
        dyStringAppend(ds, " ");
    char *label = dyStringCannibalize(&ds);
    int w = mgFontStringWidth(font, label);
    hvGfxTextCentered(hvg, x, y, w, heightPer, MG_BLACK, font, label);
    x += w;
    }
if (fsInfo->showCellAbbrevs)
    {
    int i;
    for (i=0; i<fs->expCount; ++i)
        {
        int id = fs->expNums[i];
        char *label = track->sources[id]->name;
        int w = mgFontStringWidth(font, label);
        float score = fs->expScores[i];
        int grayIx = grayInRange(score, 0, 1000);
        int color = shadesOfGray[grayIx];
        hvGfxTextCentered(hvg, x, y, w, heightPer, color, font, label);
        x += w;
        }
    }
}

static void factorSourceDrawMotifForItemAt(struct track *track, void *item, 
	struct hvGfx *hvg, int xOff, int y, 
	double scale, MgFont *font, Color color, enum trackVisibility vis)
/* Draw motif on factorSource item at a particular position. */
{
struct factorSourceInfo *fsInfo = (struct factorSourceInfo *)track->extraUiData;
if (fsInfo == NULL)
    return;

// Draw region with highest motif score
// NOTE: shows only canonical motif for the factor, so hides co-binding potential
// NOTE: current table has single motif per factor
struct factorSource *fs = item;
int heightPer = track->heightPer;
struct hash *targetHash = fsInfo->motifTargets;
struct slName *motifNames = NULL;
if (targetHash != NULL)
    motifNames = hashFindVal(targetHash, fs->name);
if (motifNames == NULL)
    motifNames = slNameNew(fs->name);

// Find motifs that lie completely within peak (sensible for ChIP-seq data ?)
struct bed6FloatScore *m, *motif = NULL, *motifs = NULL;

for (m = fsInfo->motifs; m != NULL && m->chromEnd <= fs->chromEnd; m = m->next)
    {
    if (m->chromStart < fs->chromStart)
        continue;
    if (!slNameInList(motifNames, m->name))
        continue;
    AllocVar(motif);
    motif->chrom = cloneString(m->chrom);
    motif->chromStart = m->chromStart;
    motif->chromEnd = m->chromEnd;
    motif->name = m->name;
    motif->score = m->score;
    motif->strand[0] = m->strand[0];
    slAddHead(&motifs, motif);
    }
if (motifs == NULL)
    return;
slSort(&motifs, bedCmpScore);
slReverse(&motifs);

#define HIGHEST_SCORING
#ifdef HIGHEST_SCORING
if ((motif = motifs) != NULL)
    {
    if (motif->chromStart > winEnd || motif->chromEnd < winStart)
        return;
#else
for (motif = motifs; motifs != NULL; motif = motif->next)
    {
    // exclude motifs outside of window
    if (motif->chromStart > winEnd || motif->chromEnd < winStart)
        continue;
#endif
    int x1 = round((double)((int)motif->chromStart-winStart)*scale) + xOff;
    int x2 = round((double)((int)motif->chromEnd-winStart)*scale) + xOff;
    int w = x2-x1;
    if (w < 1)
        w = 1;
    hvGfxBox(hvg, x1, y, w, heightPer, color);
    if (w > 2)
        {
        // Draw chevrons for directionality
        Color textColor = hvGfxContrastingColor(hvg, color);
        int midY = y + (heightPer>>1);
        int dir = (*motif->strand == '+' ? 1 : -1);
        if (vis != tvDense)
            clippedBarbs(hvg, x1, midY, w, tl.barbHeight, tl.barbSpacing,
                       dir, textColor, TRUE);
        }
    }
//bed6FloatScoreFreeList(&motifs);  # not worth exec time (or debug time)
}

static void factorSourceDraw(struct track *track, int seqStart, int seqEnd,
        struct hvGfx *hvg, int xOff, int yOff, int width,
        MgFont *font, Color color, enum trackVisibility vis)
/* Draw factorSource items. */
{
if (vis == tvDense)
    slSort(&track->items, bedCmpScore);
genericDrawItems(track, seqStart, seqEnd, hvg, xOff, yOff, width,
	font, color, vis);

// Highlight motif regions in green
color = hvGfxFindColorIx(hvg, 22, 182, 33);
//Color color = hvGfxFindColorIx(hvg, 25, 204, 37);
track->drawItemAt = factorSourceDrawMotifForItemAt;

// suppress re-draw of item labels in motif color
extern boolean withLeftLabels;
boolean saveWithLeftLabels = withLeftLabels;
withLeftLabels = FALSE;
genericDrawItems(track, seqStart, seqEnd, hvg, xOff, yOff, width,
	font, color, vis);
withLeftLabels = saveWithLeftLabels;
}

void factorSourceMethods(struct track *track)
/* Set up special methods for factorSource type tracks. */
{
/* Start out as a bed, and then specialize loader, mark self as packable. */
track->bedSize = 5;
bedMethods(track);
track->drawItemAt = factorSourceDrawItemAt;
track->drawItems = factorSourceDraw;
track->loadItems = factorSourceLoadItems;
track->itemRightPixels = factorSourceRightPixels;

/* Get the associated data describing the various sources. */
track->expTable = trackDbRequiredSetting(track->tdb, SOURCE_TABLE);
struct sqlConnection *conn = hAllocConn(database);
char query[256];
track->sourceCount = sqlTableSizeIfExists(conn, track->expTable);
sqlSafef(query, sizeof(query), "select * from %s order by id", track->expTable);
struct expRecord *exp, *expList = expRecordLoadByQuery(conn, query);
int expIx;
AllocArray(track->sources, track->sourceCount);
for (exp=expList, expIx=0; exp != NULL; exp = exp->next, expIx += 1)
    track->sources[expIx] = exp;
hFreeConn(&conn);

/* Figure out how many pixels need to the right. */
#ifdef UNUSED
int rightCount = tl.mWidth/2;
for (expIx=0; expIx < track->sourceCount; ++expIx)
    rightCount += mgFontStringWidth(tl.font, track->sources[expIx]->name);
track->sourceRightPixels = rightCount;
#endif
}

