from __future__ import division
import ROOT
from ROOT import gStyle

# Some convenience function to easily iterate over the parts of the collections
# Needed if importing this script from another script in case TMultiGraphs are used
# ROOT.SetMemoryPolicy(ROOT.kMemoryStrict)

# Start a bit right of the Yaxis and above the Xaxis to not overlap with the ticks
start, stop = 0.28, 0.52
x_width, y_width = 0.4, 0.2
PLACES = [
    (start, stop - y_width, start + x_width, stop),  # top left opt
    (start, start, start + x_width, start + y_width),  # bottom left opt
    (stop - x_width, stop - y_width, stop, stop),  # top right opt
    (stop - x_width, start, stop, start + y_width),  # bottom right opt
    (stop - x_width, 0.5 - y_width / 2, stop, 0.5 + y_width / 2),  # right
    (start, 0.5 - y_width / 2, start + x_width, 0.5 + y_width / 2),
]  # left

# Needed if importing this script from another script in case TMultiGraphs are used
# ROOT.SetMemoryPolicy(ROOT.kMemoryStrict)


def transform_to_user(canvas, x1, y1, x2, y2):
    """
    Transforms from Pad coordinates to User coordinates.

    This can probably be replaced by using the built-in conversion commands.
    """
    xstart = canvas.GetX1()
    xlength = canvas.GetX2() - xstart
    xlow = xlength * x1 + xstart
    xhigh = xlength * x2 + xstart
    if canvas.GetLogx():
        xlow = 10**xlow
        xhigh = 10**xhigh

    ystart = canvas.GetY1()
    ylength = canvas.GetY2() - ystart
    ylow = ylength * y1 + ystart
    yhigh = ylength * y2 + ystart
    if canvas.GetLogy():
        ylow = 10**ylow
        yhigh = 10**yhigh

    return xlow, ylow, xhigh, yhigh


def overlap_h(hist, x1, y1, x2, y2):
    xlow = hist.FindFixBin(x1)
    xhigh = hist.FindFixBin(x2)
    for i in range(xlow, xhigh + 1):
        val = hist.GetBinContent(i)
        # Values
        if y1 <= val <= y2:
            return True
        # Errors
        if val + hist.GetBinErrorUp(i) > y1 and val - hist.GetBinErrorLow(i) < y2:
            return True
    return False


def overlap_rect(rect1, rect2):
    """Do the two rectangles overlap?"""
    if rect1[0] > rect2[2] or rect1[2] < rect2[0]:
        return False
    if rect1[1] > rect2[3] or rect1[3] < rect2[1]:
        return False
    return True


def to_list(pointer):
    """turns pointer to array into list after checking if pointer is nullptr"""
    if len(pointer) == 0:
        return []
    return list(pointer)


def overlap_g(graph, x1, y1, x2, y2):
    x_values = to_list(graph.GetX())
    y_values = to_list(graph.GetY())

    x_err = to_list(graph.GetEX()) or [0] * len(x_values)
    y_err = to_list(graph.GetEY()) or [0] * len(y_values)

    for x, ex, y, ey in zip(x_values, x_err, y_values, y_err):
        # Could maybe be less conservative
        if overlap_rect((x1, y1, x2, y2), (x - ex, y - ey, x + ex, y + ey)):
            # print "Overlap with graph", graph.GetName(), "at point", (x, y)
            return True
    return False


def place_legend(
    canvas,
    x1=None,
    y1=None,
    x2=None,
    y2=None,
    header="LHCb Simulation",
    option="lpe",
):
    gStyle.SetFillStyle(0)
    gStyle.SetTextSize(0.06)
    # If position is specified, use that
    if all(x is not None for x in (x1, x2, y1, y2)):
        return canvas.BuildLegend(x1, y1, x2, y2, header, option)

    # Make sure all objects are correctly registered
    canvas.Update()

    # Build a list of objects to check for overlaps
    objects = []
    for x in canvas.GetListOfPrimitives():
        if isinstance(x, ROOT.TH1) or isinstance(x, ROOT.TGraph):
            objects.append(x)
        elif isinstance(x, ROOT.THStack) or isinstance(x, ROOT.TMultiGraph):
            objects.extend(x)

    for place in PLACES:
        place_user = canvas.PadtoU(*place)
        # Make sure there are no overlaps
        if any(obj.Overlap(*place_user) for obj in objects):
            continue
        return canvas.BuildLegend(
            place[0],
            place[1],
            place[2],
            place[3],
            header,
            option,
        )
    # As a fallback, use the default values, taken from TCanvas::BuildLegend
    return canvas.BuildLegend(0.4, 0.37, 0.88, 0.68, header, option)


# Monkey patch ROOT objects to make it all work
ROOT.THStack.__iter__ = lambda self: iter(self.GetHists())
ROOT.TMultiGraph.__iter__ = lambda self: iter(self.GetListOfGraphs())
ROOT.TH1.Overlap = overlap_h
ROOT.TGraph.Overlap = overlap_g
ROOT.TPad.PadtoU = transform_to_user
ROOT.TPad.PlaceLegend = place_legend


def set_legend(legend, gr, title, colors, label):
    legend.SetTextSize(0.05)
    legend.SetFillColor(0)
    legend.SetShadowColor(0)
    legend.SetBorderSize(0)
    legend.SetTextFont(132)
    for idx, lab in enumerate(label):
        legend.AddEntry(gr[lab], title[lab], "lep").SetTextColor(colors[idx])

    return legend
