r"""This module is used to export catalyst defined data products directly
within the ParaView GUI."""

from paraview.simple import *

haveCinemaC = True
try:
    import paraview.tpl.cinema_python.adaptors.paraview.pv_introspect as pvi
except:
    haveCinemaC = False
import os


class CinemaDHelper(object):
    """ A helper that we funnel file save commands through so that we can build up a CinemaD table for them. """

    def __init__(self, mcd, rd):
        self.__EnableCinemaDTable = mcd
        self.__RootDirectory = rd
        if rd and not rd.endswith("/"):
            self.__RootDirectory = rd + "/"
        self.Keys = set()
        self.Contents = []
        self.KeysWritten = None

    def __StripRootDir(self, filename):
        if self.__RootDirectory:
            return filename[filename.startswith(self.__RootDirectory) and len(self.__RootDirectory):]
        return filename

    def __MakeCinDFileNamesUnderRootDir(self, filename=""):
        """ filename wrangling for root directory placement """
        indexfilename = 'data.csv'
        datafilename = filename
        if self.__RootDirectory:
            indexfilename = self.__RootDirectory + "data.csv"
            # strip leading root directory from filename if present
            datafilename = self.__StripRootDir(filename)
        return indexfilename, datafilename

    def AppendToCinemaDTable(self, time, producer, filename):
        """ keep a record of every standard file written out so that we can list it later """
        if not self.__EnableCinemaDTable:
            return
        indexfilename, datafilename = self.__MakeCinDFileNamesUnderRootDir(filename)
        self.Keys.add("timestep")
        self.Keys.add("producer")
        self.Keys.add("FILE")
        self.Contents.append({'timestep': time, 'producer': producer, 'FILE': datafilename})

    def AppendCViewToCinemaDTable(self, time, producer, filelist):
        """ keep a record of every new file that cinema image writes along with the keys that produced them so that we can list them all later """
        if not self.__EnableCinemaDTable or not haveCinemaC:
            return
        self.Keys.add("timestep")
        self.Keys.add("producer")
        self.Keys.add("FILE")
        # unroll the contents into key lists and filenames
        for viewname in filelist:
            for entry in filelist[viewname]:
                keylist = entry[0]
                # avoid redundancy from name change
                if 'time' in keylist:
                    time = keylist['time']
                    del keylist['time']
                for k in keylist:
                    self.Keys.add(k)
                keylist['timestep'] = time
                keylist['producer'] = producer
                keylist['FILE'] = self.__StripRootDir(entry[1])
                self.Contents.append(keylist)

    def Finalize(self):
        """ Finish off the table and write it to a file """
        # this is necessary because we don't really know keys (from cinema image) until the end.
        if not self.__EnableCinemaDTable:
            return
        indexfilename, datafilename = self.__MakeCinDFileNamesUnderRootDir()
        if self.__RootDirectory and not os.path.exists(self.__RootDirectory):
            os.makedirs(self.__RootDirectory)
        f = open(indexfilename, "w")
        # write the header line
        f.write("timestep,")
        f.write("producer,")
        for k in self.Keys:
            if k != 'timestep' and k != 'producer' and k != 'FILE':
                f.write("%s," % k)
        f.write("FILE\n")
        # write all of the contents
        for l in self.Contents:
            f.write("%s," % l['timestep'])
            f.write("%s," % l['producer'])
            for k in self.Keys:
                if k != 'timestep' and k != 'producer' and k != 'FILE':
                    v = ''
                    if k in l:
                        v = l[k]
                    f.write("%s," % v)
            f.write("%s\n" % self.__StripRootDir(l['FILE']))
        f.close()

    def WriteNow(self):
        """ For Catalyst, we don't generally have a Final state, so we call this every CoProcess call and fixup the table if we have to. """
        if not self.__EnableCinemaDTable:
            return

        indexfilename, datafilename = self.__MakeCinDFileNamesUnderRootDir()
        if self.KeysWritten == self.Keys:
            # phew nothing new, we can just append a record
            f = open(indexfilename, "a+")
            for l in self.Contents:
                f.write("%s," % l['timestep'])
                f.write("%s," % l['producer'])
                for k in self.Keys:
                    if k != 'timestep' and k != 'producer' and k != 'FILE':
                        v = ''
                        if k in l:
                            v = l[k]
                        f.write("%s," % v)
                f.write("%s\n" % self.__StripRootDir(l['FILE']))
            f.close()
            self.Contents = []
            return
        # dang, whatever we wrote recently had a new variable
        # we may have to extend and rewrite the old output file
        readKeys = None
        readContents = []
        if os.path.exists(indexfilename):
            # yep we have to do it
            # parse the old file
            f = open(indexfilename, "r")
            for line in f:
                read = line[0:-1].split(",")  # -1 to skip trailing\n
                if readKeys is None:
                    readKeys = read
                else:
                    entry = {}
                    for idx in range(0, len(read)):
                        entry.update({readKeys[idx]: read[idx]})
                    readContents.append(entry)
        # combine contents
        if readKeys is not None:
            self.Keys = self.Keys.union(readKeys)
            readContents.extend(self.Contents)
            self.Contents = readContents
        # finally, write
        self.Finalize()
        self.KeysWritten = self.Keys.copy()
        self.Contents = []


class __CinemaACHelper(object):
    """ Another helper that connects up to cinema_python's export function. """

    def __init__(self, rd, vsel, tsel, asel):
        self.__RootDirectory = rd
        self.__ViewSelection = vsel
        self.__TrackSelection = tsel
        self.__ArraySelection = asel
        self.NewFiles = None

    if haveCinemaC:
        def ExportNow(self, time):
            r = pvi.export_scene(baseDirName=self.__RootDirectory,
                                 viewSelection=dict(self.__ViewSelection),
                                 trackSelection=dict(self.__TrackSelection),
                                 arraySelection=dict(self.__ArraySelection),
                                 forcetime=time)
            self.NewFiles = r
    else:
        def ExportNow(self, time):
            pass


def _fixup_and_makedir_if_needed(rootdir):
    if rootdir and not rootdir.endswith("/"):
        rootdir = rootdir + "/"
    if rootdir and not os.path.exists(rootdir):
        os.makedirs(rootdir)
    return rootdir


def ExportNow(image_root_directory,
              data_root_directory,
              file_name_padding,
              make_cinema_table,
              cinema_tracks,
              cinema_arrays,
              rendering_info):
    """The user facing entry point. Here we get a hold of ParaView's animation controls, step through the animation, and export the things we've been asked to be the caller."""

    CIND = CinemaDHelper(make_cinema_table, image_root_directory)
    image_root_directory = _fixup_and_makedir_if_needed(image_root_directory)
    data_root_directory = _fixup_and_makedir_if_needed(data_root_directory)

    # get a hold of the scene
    spm = servermanager.vtkSMProxyManager.GetProxyManager().GetActiveSessionProxyManager()
    ed = spm.GetExportDepot()
    s = GetAnimationScene()
    s.GoToFirst()
    et = s.EndTime
    tnow = s.AnimationTime

    numTimesteps = len(paraview.simple.GetAnimationScene().TimeKeeper.TimestepValues)
    timesteps = {}
    # We must perform the export loop at least once, even in the case where
    # the input data contains no timesteps
    if (numTimesteps == 0):
        timesteps = list(range(1))
    else:
        timesteps = list(range(numTimesteps))

    # export loop
    for tstep in timesteps:
        padded_tstep = str(tstep).rjust(file_name_padding, '0')
        helpers = []

        # loop through the configured writers and export at the requested times
        ed.InitNextWriterProxy()
        wp = ed.GetNextWriterProxy()
        writercnt = 0
        while wp:
            freq = wp.GetProperty("WriteFrequency").GetElement(0)
            if ((tstep % freq == 0) and
                    (not (wp.GetXMLName() == "Cinema image options"))):
                # this isn't pretty. I couldn't find a way to write
                # directly with the writer proxy. So what I do here
                # is find the input and filename and make a new writer
                # to use.
                proxyname = spm.GetProxyName("export_writers", wp)
                inputname = proxyname[0:proxyname.find("|")]
                inputproxy = FindSource(inputname)
                if wp.GetProperty("ChooseArraysToWrite").GetElement(0) == 1:
                    point_arrays = []
                    cell_arrays = []
                    arrays_property = wp.GetProperty("PointDataArrays")
                    for i in range(arrays_property.GetNumberOfElements()):
                        point_arrays.append(arrays_property.GetElement(i))
                    arrays_property = wp.GetProperty("CellDataArrays")
                    for i in range(arrays_property.GetNumberOfElements()):
                        cell_arrays.append(arrays_property.GetElement(i))
                    if not point_arrays:
                        point_arrays = [' ']
                    if not cell_arrays:
                        cell_arrays = [' ']
                    # create a temporary array culling filter
                    pass_arrays = PassArrays(Input=inputproxy, \
                                             PointDataArrays=point_arrays, CellDataArrays=cell_arrays)
                    inputproxy = pass_arrays
                    helpers.append(inputproxy)
                fname = wp.GetProperty("CatalystFilePattern").GetElement(0)
                if wp.GetXMLName() == "ExodusIIWriter":
                    fnamefilled = data_root_directory + fname + padded_tstep
                else:
                    fnamefilled = data_root_directory + fname.replace("%t", padded_tstep)

                kwargs = {}
                DataMode = wp.GetProperty("DataMode")
                if DataMode is not None:
                    kwargs["DataMode"] = wp.GetProperty("DataMode").GetElement(0)
                HeaderType = wp.GetProperty("HeaderType")
                if HeaderType is not None:
                    kwargs["HeaderType"] = wp.GetProperty("HeaderType").GetElement(0)
                EncodeAppendedData = wp.GetProperty("EncodeAppendedData")
                if EncodeAppendedData is not None:
                    kwargs["EncodeAppendedData"] = wp.GetProperty("EncodeAppendedData").GetElement(0)
                CompressorType = wp.GetProperty("CompressorType")
                if CompressorType is not None:
                    kwargs["CompressorType"] = wp.GetProperty("CompressorType").GetElement(0)
                CompressionLevel = wp.GetProperty("CompressionLevel")
                if CompressionLevel is not None:
                    kwargs["CompressionLevel"] = wp.GetProperty("CompressionLevel").GetElement(0)

                # finally after all of the finageling above, save the data
                SaveData(fnamefilled, inputproxy, **kwargs)
                # don't forget to tell cinema D about it
                CIND.AppendToCinemaDTable(tnow, "writer_%s" % writercnt, fnamefilled)
            wp = ed.GetNextWriterProxy()
            writercnt = writercnt + 1

        # loop through the configured screenshots and export at the requested times
        ed.InitNextScreenshotProxy()
        ssp = ed.GetNextScreenshotProxy()
        viewcnt = 0
        while ssp:
            if not ssp.HasAnnotation("enabled") or not (ssp.GetAnnotation("enabled") == '1'):
                ssp = ed.GetNextScreenshotProxy()
                continue
            freq = ssp.GetProperty("WriteFrequency").GetElement(0)
            if tstep % freq == 0:
                fname = ssp.GetProperty("CatalystFilePattern").GetElement(0)
                if fname.endswith("cdb"):
                    CINAC = __CinemaACHelper(image_root_directory,
                                             rendering_info,
                                             cinema_tracks,
                                             cinema_arrays)
                    # special treatment for cinema image data bases
                    CINAC.ExportNow(tnow)
                    # don't forget to tell cinema D about it
                    CIND.AppendCViewToCinemaDTable(tnow, "cview_%s" % viewcnt, CINAC.NewFiles)
                else:
                    fnamefilled = image_root_directory + fname.replace("%t", padded_tstep)
                    # save the screenshot
                    ssp.WriteImage(fnamefilled)
                    # don't forget to tell cinema D about it
                    CIND.AppendToCinemaDTable(tnow, "view_%s" % viewcnt, fnamefilled)
            ssp = ed.GetNextScreenshotProxy()
            viewcnt = viewcnt + 1
        tstep = tstep + 1
        s.GoToNext()
        tnow = s.AnimationTime

        # destroy array culling filters
        for x in helpers:
            Delete(x)

    # defer actual cinema D output until the end because we only know now what the full set of cinema D columns actually are
    CIND.Finalize()
