# Copyright (C) University of Tennessee Health Science Center, Memphis, TN.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# This program is available from Source Forge: at GeneNetwork Project
# (sourceforge.net/projects/genenetwork/).
#
# Contact Drs. Robert W. Williams and Xiaodong Zhou (2010)
# at rwilliams@uthsc.edu and xzhou15@uthsc.edu
#
#
#
# This module is used by GeneNetwork project (www.genenetwork.org)
#
# Created by GeneNetwork Core Team 2010/08/10
#
# Last updated by GeneNetwork Core Team 2024/09/11
import os
from pathlib import Path

from PIL import ImageColor
from PIL import ImageDraw
from PIL import ImageFont

from math import *

from flask import current_app as app

import gn2.utility.corestats as corestats
from gn2.base import webqtlConfig
from gn2.utility.pillow_utils import draw_rotated_text


# ---- Define common colours ---- #
BLUE = ImageColor.getrgb("blue")
BLACK = ImageColor.getrgb("black")
# ---- END: Define common colours ---- #

# ---- FONT FILES ---- #
REPO_ROOT = Path(__file__).parent.parent
FONTS_DIR = REPO_ROOT.joinpath("wqflask/static/fonts")
VERDANA_FILE = f"{FONTS_DIR.joinpath('verdana.ttf')}"
COUR_FILE = f"{FONTS_DIR.joinpath('courbd.ttf')}"
TAHOMA_FILE = f"{FONTS_DIR.joinpath('tahoma.ttf')}"
# ---- END: FONT FILES ---- #


def cformat(d, rank=0):
    'custom string format'
    strD = "%2.6f" % d

    if rank == 0:
        while strD[-1] in ('0', '.'):
            if strD[-1] == '0' and strD[-2] == '.' and len(strD) <= 4:
                break
            elif strD[-1] == '.':
                strD = strD[:-1]
                break
            else:
                strD = strD[:-1]

    else:
        strD = strD.split(".")[0]

    if strD == '-0.0':
        strD = '0.0'
    return strD


def frange(start, end=None, inc=1.0):
    "A faster range-like function that does accept float increments..."
    if end == None:
        end = start + 0.0
        start = 0.0
    else:
        start += 0.0  # force it to be a float
    count = int((end - start) / inc)
    if start + count * inc != end:
        # Need to adjust the count. AFAICT, it always comes up one short.
        count += 1
    L = [start] * count
    for i in range(1, count):
        L[i] = start + i * inc
    return L


def find_outliers(vals):
    """Calculates the upper and lower bounds of a set of sample/case values


    >>> find_outliers([3.504, 5.234, 6.123, 7.234, 3.542, 5.341, 7.852, 4.555, 12.537])
    (11.252500000000001, 0.5364999999999993)

    >>> find_outliers([9,12,15,17,31,50,7,5,6,8])
    (32.0, -8.0)

    If there are no vals, returns None for the upper and lower bounds,
    which code that calls it will have to deal with.
    >>> find_outliers([])
    (None, None)

    """

    if vals:
        stats = corestats.Stats(vals)
        low_hinge = stats.percentile(25)
        up_hinge = stats.percentile(75)
        hstep = 1.5 * (up_hinge - low_hinge)

        upper_bound = up_hinge + hstep
        lower_bound = low_hinge - hstep

    else:
        upper_bound = None
        lower_bound = None

    return upper_bound, lower_bound

# parameter: data is either object returned by reaper permutation function (called by MarkerRegressionPage.py)
# or the first object returned by direct (pair-scan) permu function (called by DirectPlotPage.py)


def plotBar(canvas, data, barColor=BLUE, axesColor=BLACK, labelColor=BLACK, XLabel=None, YLabel=None, title=None, offset=(60, 20, 40, 40), zoom=1):
    im_drawer = ImageDraw.Draw(canvas)
    xLeftOffset, xRightOffset, yTopOffset, yBottomOffset = offset

    plotWidth = canvas.size[0] - xLeftOffset - xRightOffset
    plotHeight = canvas.size[1] - yTopOffset - yBottomOffset
    if plotHeight <= 0 or plotWidth <= 0:
        return

    if len(data) < 2:
        return

    max_D = max(data)
    min_D = min(data)
    # add by NL 06-20-2011: fix the error: when max_D is infinite, log function in detScale will go wrong
    if (max_D == float('inf') or max_D > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS:
        max_D = webqtlConfig.MAXLRS  # maximum LRS value

    xLow, xTop, stepX = detScale(min_D, max_D)

    # reduce data
    # ZS: Used to determine number of bins for permutation output
    step = ceil((xTop - xLow) / 50.0)
    j = xLow
    dataXY = []
    Count = []
    while j <= xTop:
        dataXY.append(j)
        Count.append(0)
        j += step

    for i, item in enumerate(data):
        if (item == float('inf') or item > webqtlConfig.MAXLRS) and min_D < webqtlConfig.MAXLRS:
            item = webqtlConfig.MAXLRS  # maximum LRS value
        j = int((item - xLow) / step)
        Count[j] += 1

    yLow, yTop, stepY = detScale(0, max(Count))

    # draw data
    xScale = plotWidth / (xTop - xLow)
    yScale = plotHeight / (yTop - yLow)
    barWidth = xScale * step

    for i, count in enumerate(Count):
        if count:
            xc = (dataXY[i] - xLow) * xScale + xLeftOffset
            yc = -(count - yLow) * yScale + yTopOffset + plotHeight
            im_drawer.rectangle(
                xy=((xc + 2, yc), (xc + barWidth - 2, yTopOffset + plotHeight)),
                outline=barColor, fill=barColor)

    # draw drawing region
    im_drawer.rectangle(
        xy=((xLeftOffset, yTopOffset),
            (xLeftOffset + plotWidth, yTopOffset + plotHeight))
    )

    # draw scale
    app.logger.debug("Font file path: %s", os.path.abspath(COUR_FILE))
    scaleFont = ImageFont.truetype(font=COUR_FILE, size=11)
    x = xLow
    for i in range(int(stepX) + 1):
        xc = xLeftOffset + (x - xLow) * xScale
        im_drawer.line(
            xy=((xc, yTopOffset + plotHeight),
                (xc, yTopOffset + plotHeight + 5)),
            fill=axesColor)
        strX = cformat(d=x, rank=0)
        im_drawer.text(
            text=strX,
            xy=(xc - im_drawer.textsize(strX, font=scaleFont)[0] / 2,
                yTopOffset + plotHeight + 14), font=scaleFont)
        x += (xTop - xLow) / stepX

    y = yLow
    for i in range(int(stepY) + 1):
        yc = yTopOffset + plotHeight - (y - yLow) * yScale
        im_drawer.line(
            xy=((xLeftOffset, yc), (xLeftOffset - 5, yc)), fill=axesColor)
        strY = "%d" % y
        im_drawer.text(
            text=strY,
            xy=(xLeftOffset - im_drawer.textsize(strY,
                                                 font=scaleFont)[0] - 6, yc + 5),
            font=scaleFont)
        y += (yTop - yLow) / stepY

    # draw label
    labelFont = ImageFont.truetype(font=TAHOMA_FILE, size=17)
    if XLabel:
        im_drawer.text(
            text=XLabel,
            xy=(xLeftOffset + (
                plotWidth - im_drawer.textsize(XLabel, font=labelFont)[0]) / 2.0,
                yTopOffset + plotHeight + yBottomOffset - 10),
            font=labelFont, fill=labelColor)

    if YLabel:
        draw_rotated_text(canvas, text=YLabel,
                          xy=(19,
                              yTopOffset + plotHeight - (
                                  plotHeight - im_drawer.textsize(
                                      YLabel, font=labelFont)[0]) / 2.0),
                          font=labelFont, fill=labelColor, angle=90)

    labelFont = ImageFont.truetype(font=VERDANA_FILE, size=16)
    if title:
        im_drawer.text(
            text=title,
            xy=(xLeftOffset + (plotWidth - im_drawer.textsize(
                title, font=labelFont)[0]) / 2.0,
                20),
            font=labelFont, fill=labelColor)

# This function determines the scale of the plot


def detScaleOld(min, max):
    if min >= max:
        return None
    elif min == -1.0 and max == 1.0:
        return [-1.2, 1.2, 12]
    else:
        a = max - min
        b = floor(log10(a))
        c = pow(10.0, b)
        if a < c * 5.0:
            c /= 2.0
        # print a,b,c
        low = c * floor(min / c)
        high = c * ceil(max / c)
        return [low, high, round((high - low) / c)]


def detScale(min=0, max=0):

    if min >= max:
        return None
    elif min == -1.0 and max == 1.0:
        return [-1.2, 1.2, 12]
    else:
        a = max - min
        if max != 0:
            max += 0.1 * a
        if min != 0:
            if min > 0 and min < 0.1 * a:
                min = 0.0
            else:
                min -= 0.1 * a
        a = max - min
        b = floor(log10(a))
        c = pow(10.0, b)
        low = c * floor(min / c)
        high = c * ceil(max / c)
        n = round((high - low) / c)
        div = 2.0
        while n < 5 or n > 15:
            if n < 5:
                c /= div
            else:
                c *= div
            if div == 2.0:
                div = 5.0
            else:
                div = 2.0
            low = c * floor(min / c)
            high = c * ceil(max / c)
            n = round((high - low) / c)

        return [low, high, n]


def bluefunc(x):
    return 1.0 / (1.0 + exp(-10 * (x - 0.6)))


def redfunc(x):
    return 1.0 / (1.0 + exp(10 * (x - 0.5)))


def greenfunc(x):
    return 1 - pow(redfunc(x + 0.2), 2) - bluefunc(x - 0.3)


def colorSpectrum(n=100):
    multiple = 10
    if n == 1:
        return [ImageColor.getrgb("rgb(100%,0%,0%)")]
    elif n == 2:
        return [ImageColor.getrgb("100%,0%,0%)"),
                ImageColor.getrgb("rgb(0%,0%,100%)")]
    elif n == 3:
        return [ImageColor.getrgb("rgb(100%,0%,0%)"),
                ImageColor.getrgb("rgb(0%,100%,0%)"),
                ImageColor.getrgb("rgb(0%,0%,100%)")]
    N = n * multiple
    out = [None] * N
    for i in range(N):
        x = float(i) / N
        out[i] = ImageColor.getrgb("rgb({}%,{}%,{}%".format(
            *[int(i * 100) for i in (
                redfunc(x), greenfunc(x), bluefunc(x))]))
    out2 = [out[0]]
    step = N / float(n - 1)
    j = 0
    for i in range(n - 2):
        j += step
        out2.append(out[int(j)])
    out2.append(out[-1])
    return out2


def _test():
    import doctest
    doctest.testmod()


if __name__ == "__main__":
    _test()
