"""
This module has utilities to benchmark paraview.

You can set up arbitrary pipelines and this module helps you obtain,
interpret and report the information recorded by ParaView's logs.
Do that like so:

1. Optionally, call maximize logs first
2. Setup and run your visualization pipeline (via GUI or script as you prefer)
3. Call print_logs() to print out the logs in raw format
"""

from __future__ import print_function
from paraview.simple import *
import paraview

start_frame = 0
default_log_threshold = dict()


def component_to_string(comp):
    session = servermanager.ProxyManager().GetSessionProxyManager().GetSession()
    if comp == session.NONE: return 'None'
    if comp == session.DATA_SERVER: return 'DataServer'
    if comp == session.DATA_SERVER_ROOT: return 'DataServerRoot'
    if comp == session.RENDER_SERVER: return 'RenderServer'
    if comp == session.RENDER_SERVER_ROOT: return 'RenderServerRoot'
    if comp == session.SERVERS: return 'Servers'
    if comp == session.CLIENT: return 'Client'
    if comp == session.CLIENT_AND_SERVERS: return 'ClientAndServers'
    return None


class OneLog:
    def __init__(self, runmode='batch', servertype='unified', component=0, rank=0):
        self.runmode = runmode
        self.servertype = servertype
        self.component = component_to_string(component)
        self.rank = rank
        self.lines = []

    def print_log(self, showlines=False):
        print("#RunMode:", self.runmode, end="")
        print("ServerType:", self.servertype, end="")
        print("Component:", self.component, end="")
        print("processor#:", self.rank)
        if showlines:
            for i in self.lines:
                print(i)

    def toString(self, showlines=False):
        result = "#RunMode: " + self.runmode + " ServerType: " + self.servertype + " processor#: " + str(
            self.rank) + "\n"
        if showlines:
            for i in self.lines:
                result += i + "\n"
        return result


logs = []


def maximize_logs():
    """
    Convenience method to ask paraview to produce logs with lots of space and
    highest resolution.
    """
    pm = paraview.servermanager.vtkProcessModule.GetProcessModule()
    if pm == None:
        return

    ss = paraview.servermanager.vtkSMSession
    for ptype in [ss.CLIENT_AND_SERVERS, ss.CLIENT, ss.SERVERS,
                  ss.RENDER_SERVER, ss.DATA_SERVER]:
        default_log_threshold[str(ptype)] = 0.0

    pxm = paraview.servermanager.ProxyManager()
    tl = pxm.NewProxy("misc", "TimerLog")
    prop = tl.GetProperty("MaxEntries")
    prop.SetElements1(1000000)
    tl.UpdateVTKObjects()


def get_memuse():
    pm = paraview.servermanager.vtkProcessModule.GetProcessModule()
    session = servermanager.ProxyManager().GetSessionProxyManager().GetSession()

    retval = []
    if pm.GetProcessTypeAsInt() == pm.PROCESS_BATCH:
        components = {'CL_DS_RS': session.CLIENT_AND_SERVERS}
    else:
        if session.GetRenderClientMode() == session.RENDERING_UNIFIED:
            components = {'CL': session.CLIENT, 'DS_RS': session.SERVERS}
        else:
            components = {'CL': session.CLIENT, 'RS': session.RENDER_SERVER, 'DS': session.DATA_SERVER}

    for comp_label, comp_type in components.items():
        infos = servermanager.vtkPVMemoryUseInformation()
        session.GatherInformation(comp_type, infos, 0)
        for i in range(0, infos.GetSize()):
            retval.append('%(l)s[%(r)d] %(pu)d / %(hu)d' %
                          {'l': comp_label, 'r': infos.GetRank(i),
                           'pu': infos.GetProcMemoryUse(i),
                           'hu': infos.GetHostMemoryUse(i)})
    return retval


def dump_logs(filename):
    """
    This saves off the logs we've gathered.
    Ot allows you to run a benchmark somewhere, save off all of the details in
    raw format, then load them somewhere else. You can then do a detailed
    analysis and you always have the raw data to go back to.
    """
    import pickle
    global logs
    f = open(filename, "wb")
    pickle.dump(logs, f)
    f.close()


def import_logs(filename):
    """
    This is for bringing in a saved log files and parse it after the fact.
    TODO: add an option to load in raw paraview logs in text format
    """
    import pickle
    global logs
    logs = []
    f = open(filename, "rb")
    logs = pickle.load(f)
    f.close()


def get_logs():
    """
    This is for bringing in logs at run time to parse while running.
    """
    global logs
    logs = []

    pm = paraview.servermanager.vtkProcessModule.GetProcessModule()
    if pm == None:
        return

    connectionId = paraview.servermanager.ActiveConnection.ID
    session = paraview.servermanager.ActiveConnection.Session

    is_symmetric_mode = False
    if pm.GetProcessTypeAsInt() == pm.PROCESS_BATCH:
        runmode = 'batch'
        is_symmetric_mode = pm.GetSymmetricMPIMode()
    else:
        runmode = 'interactive'

    if session.GetRenderClientMode() == session.RENDERING_UNIFIED:
        servertype = 'unified'
    else:
        servertype = 'split'

    if runmode == 'batch':
        # collect information from all processes in one go.
        components = [session.CLIENT_AND_SERVERS]
    else:
        if servertype == 'unified':
            # collect information separately for client and servers.
            components = [session.CLIENT, session.SERVERS]
        else:
            # collect information separately for all process types.
            components = [session.CLIENT, session.RENDER_SERVER, session.DATA_SERVER]

    for component in components:
        timerInfo = paraview.servermanager.vtkPVTimerInformation()
        if len(default_log_threshold) != 0:
            timerInfo.SetLogThreshold(default_log_threshold[str(component)])
        session.GatherInformation(component, timerInfo, 0)

        for i in range(timerInfo.GetNumberOfLogs()):
            alog = OneLog(runmode, servertype, component, i)

            if is_symmetric_mode:
                # in Symmetric mode, GatherInformation() only collects
                # information from the current node. so the
                # vtkPVTimerInformation will only have info for local process.
                alog.rank = pm.GetPartitionId()

            alog.lines = timerInfo.GetLog(i).split('\n');
            logs.append(alog)


def print_logs():
    """
    Print logs on the root node by gathering logs across all the nodes
    regardless if the process was started in symmetric mode or not.
    """
    global logs

    if len(logs) == 0:
        get_logs()

    # Handle symmetric mode specifically if need be
    pm = paraview.servermanager.vtkProcessModule.GetProcessModule()
    is_symmetric_mode = False
    if pm != None:
        is_symmetric_mode = pm.GetSymmetricMPIMode()

    if is_symmetric_mode:
        # Need to provide extra synchronization
        ctrl = pm.GetGlobalController()
        proc = pm.GetPartitionId()
        nbProc = pm.GetNumberOfLocalPartitions()
        if proc == 0:
            # Start with my logs
            for i in logs:
                i.print_log(True)
            # Then Print the log of every other rank
            for otherProc in range(1, nbProc):
                # Max buffer size 999999
                logSize = " " * 6
                ctrl.Receive(logSize, len(logSize), otherProc, 987455)
                logSize = int(logSize)
                logTxt = " " * logSize
                ctrl.Receive(logTxt, logSize, otherProc, 987456)
                print(logTxt)
        else:
            # Extract logs text
            logTxt = ""
            for i in logs:
                logTxt += i.toString(True)
            logSize = str(len(logTxt))

            # Push local logs to process 0
            ctrl.Send(logSize, len(logSize), 0, 987455)
            ctrl.Send(logTxt, len(logTxt), 0, 987456)

    else:
        # Regular local print
        for i in logs:
            i.print_log(True)


def test_module():
    """Simply exercises a few components of the module."""
    maximize_logs()

    paraview.servermanager.SetProgressPrintingEnabled(0)
    ss = Sphere(ThetaResolution=1000, PhiResolution=500)
    rep = Show()
    v = Render()

    print_logs()


if __name__ == "__main__":
    test_module()
