# Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration

#
# File: draw_obj.py
# Created: sss, 2004.
# Purpose: Helpers for drawing ROOT objects.
#

"""Helpers for drawing ROOT objects.
The main function in here is draw_obj(obj [, options]).
This is similar to obj->Draw(options), but some extra
functionality is added:

  - Automatic zone handling.  You can split the canvas into
    subpads (zones) with zone().  Each call to draw_obj() will
    plot into the next sequential pad (unless SAME was specified).
    Selecting a pad by clicking with button 2 will change
    the sequence so that the next objects is drawn in the
    selected pad.  draw_obj also takes an optional padnum
    argument to explicitly specify the pad number to use.

  - If the SAME option is given, then the new object will be
    superimposed on an existing one in the current pad.
    In the case of 1D histograms, we change the line
    type of the new histogram, and also rescale the vertical
    axis, if needed to make the new histogram fit.

  - The option MERGE is like SAME, except that we step through
    the pads as normal.  The new plot will be made on top of
    whatever happens to be on that pad.  By default, the line
    type used will be the same as one would get the first time
    one overlays a plot with SAME; to override this, put a number
    directly after the option, like `MERGE2'.

  - The option NORM draws the histogram normalized to 1.

  - The option LOGY draws the histogram using a logarithmic
    y-axis.

  - The line type will recycle after four histograms have
    been superimposed.  If the LINECOLORS option is given, then
    the new histograms will be drawn in a different color.

  - draw_obj takes optional min and max parameters to set the
    minimum and maximum values for the y-axis of the histogram
    being drawn.

Besides draw_obj, a few other functions are available.
zone(nx,ny) was already mentioned.
printeps(fname) is a shortcut to print an eps file from the current canvas.
get_canvas() returns the canvas currently being used for drawing.
get_pad() returns the pad currently being used for drawing.
"""
from __future__ import division


from ROOT import gROOT, TCanvas, TVirtualPad, TH1, TH2, TObject


# The number of histograms we've superimposed on the current pad with SAME.
_samecount = 0

# The number of the last subpad we drew in.
# Will be 0 if there is only one pad.
_lastpad = 0

# The number of the next subpad to draw in, starting with 1.
_nextpad = 1

# Total number of subpads.
_npads = 1



class _options(object):
    """Helper class for parsing options."""
    def __init__ (self, options):
        self.merge = 0
        self.same = 0
        self.norm = 0
        self.logy = 0
        self.fill = -1 
        self.linetype = -1
        self.color = -1
        self.linecolors = 0
        self.other = ""

        options = options.replace (',', ' ')
        for o in options.split():
            lo = o.lower()
            if lo in ["merge", "same", "norm", "logy", 'linecolors']:
                setattr (self, lo, 1)
            elif (self._optmatch (lo, "fill") or
                  self._optmatch (lo, "linetype") or
                  self._optmatch (lo, "color")):
                pass
            else:
                self.other += o
        return


    def _optmatch (self, lo, pat):
        if lo.startswith (pat+'='):
            setattr (self, pat, int (lo[len (pat)+1:]))
            return 1
        return 0
    

def draw_obj (obj, options = "", padnum = -1, pad = None, min=None, max=None):
    """Draw the root object OBJ in the next available pad.

Inputs:
  obj -         The object to draw.
  options -     Drawing options.
                These are passed through to the root Draw
                method, except that we have special
                handling for the SAME option, and add a new MERGE option.
                See the header for details.
  padnum -      If this is a non-negative integer, then this specifies
                the pad in which to draw, overriding
                other specifications except for PAD.  Note: subpad numbers
                start with 1.
  pad -         Explicitly specify the pad to use for drawing.
                Overrides all other specifications.

Returns:
  The object that we drew (may not be the same as OBJ if we
  made a copy).
"""
    global _samecount

    if min is not None:
        obj.SetMinimum (min)
    if max is not None:
        obj.SetMaximum (max)
        
    op = _options (options)

    # Should we advance to the next pad?
    advance_p = 0

    # Should we do vertical axis rescaling?
    rescale_p = 0

    # For SAME and MERGE, we have to pass SAME on to root.
    if op.same or op.merge:
        op.other += "SAME"

    if not op.same:
        # No SAME option.  Reset the count.
        _samecount = 0

        # Advance to the next pad.
        advance_p = 1
    else:
        # SAME was specified.  Keep count of the number of such.
        _samecount += 1
        rescale_p = 1

    # Handle the MERGE option.
    if op.merge:
        rescale_p = 1
        advance_p = 1

    if pad:
        pad.cd()
    else:
        pad = get_pad (advance_p, padnum)

    if not op.merge and not op.same:
        pad.SetLogy (not not op.logy)

    if isinstance (obj, TH1) and not isinstance (obj, TH2):
        h = obj
        if op.norm:
            h = h.Clone()
            intg =  h.Integral()
            if intg == 0:
                intg = 1
            h.Scale (1. / intg)
            if max is not None:
                h.SetMaximum (max)

        # Special handling for 1D histograms.
        # If SAME was specified, rescale the vertical axis, if needed.
        if rescale_p and max is None:
            # Find the first hist already plotted.
            hfirst = None
            prims = pad.GetListOfPrimitives()
            # Avoids RecursiveRemove crash...
            prims.ResetBit(TObject.kMustCleanup)
            for obj in prims:
                if isinstance (obj, TH1):
                    hfirst = obj
                    break

            # If the new hist's maximum is larger than the first one,
            # adjust the maximum of the first.
            if hfirst and h.GetMaximum() > hfirst.GetMaximum():
                hfirst.SetMaximum (h.GetMaximum() * 1.1)

            if hfirst and h.GetMinimum() < hfirst.GetMinimum():
                hfirst.SetMinimum (h.GetMinimum())

        # Draw a copy of the histogram.
        # Adjust the line style.
        hh = h.DrawCopy (op.other)
        if op.linetype >= 0:
            hh.SetLineStyle (op.linetype)
        else:
            hh.SetLineStyle ((_samecount%4)+1)
        if op.color >= 0:
            hh.SetLineColor (op.color)
        elif op.linecolors and _samecount >= 4:
            hh.SetLineColor (_samecount//4 + 1)
        if op.fill >= 0:
            hh.SetFillColor (op.fill)
        obj = hh
    else:
        # Not a 1-D histogram.  Just draw it.
        obj.Draw (op.other)

    return obj


def get_pad (advance_p = 1, padnum = -1):
    """Advance to the next pad, if requested.
Allow clicking on the pads (button 2) to change the next pad.
"""
    global _lastpad, _nextpad, _npads
    
    c1 = get_canvas()
    pad = TVirtualPad.Pad()

    if advance_p:
        if (pad and
            pad.GetCanvasID() == c1.GetCanvasID() and
            pad.GetNumber() != _lastpad and pad.GetNumber() != 0):
            _nextpad = pad.GetNumber()
            if _nextpad > _npads:
                _nextpad = 1

        # Set up to draw on nextpad.
        # Bump it for the next go around.
        if _npads > 1:
            _lastpad = _nextpad
            _nextpad += 1
            if _nextpad > _npads:
                _nextpad = 1
        else:
            _lastpad = 0

        # Select the pad.
        c1.cd (_lastpad)

    # Handle padnum.
    if padnum >= 0:
        _lastpad = padnum
        if _npads > 0:
            _nextpad = _lastpad + 1
            if _nextpad > _npads:
                _nextpad = 0
        else:
            _nextpad = 0

        c1.cd (_lastpad)

    # Refetch the pad.
    return TVirtualPad.Pad()


_canvas = None
def get_canvas (cname = "c1"):
    """Return the canvas named CNAME.
Create it if it doesn't exist.
"""
    global _canvas
    _canvas = gROOT.FindObject (cname)
    if not _canvas:
        _canvas = TCanvas (cname, cname, 700, 600)
        _canvas.SetLeftMargin (0.15)
        _canvas.SetBottomMargin (0.15)
        _canvas.SetLogy (0)
    return _canvas



def zone (nx, ny):
    """Divide the canvas into subpads.
NX and NY are the number of subpads in x and y, respectively.
"""
    global _npads, _nextpad, _lastpad
    c1 = get_canvas()
    c1.Clear()
    c1.Divide (nx, ny)
    _npads = nx*ny
    _nextpad = 1
    _lastpad = 0
    return


def printeps (s = "out.eps"):
    """Print the current canvas as an eps file."""

    c1 = get_canvas()
    c1.Print (s, "eps,Portrait")
    return