#
# Excerpt from plot_manager_data_classes.py for mpl-users discussion about
# persistent storage of plots
#
# Code originally written by Dave vanEe and Anthony Floyd,
# Convergent Manufacturing Technologies, Inc
# http://www.convergent.ca
#
# Licensed under the Creative Commons Attribution 3.0 Unported license
# http://creativecommons.org/licenses/by/3.0/
# 
#

import numpy

import cmt.common.data_classes as dc
import cmt.compro0d.intf.constants as cst
import cmt.common.common_functions as ccf
import cmt.common.plot_data_classes as pdc
import cmt.common.unit_conversion as uc

from cmt.compro0d.logger import Log

class Legend(object):
    """
    Plot legend class
    
    This class is used to track the legend on a plot.  It's necessary to keep lists linking
    the lines on actual plot frames and the series objects they correspond to so click events
    on the legend highlight the proper series.
    """
    def __init__(self, lines=None, labels=None, seriesList=None, location=cst.LEGEND_UPPER_RIGHT): #, parentPlot=None):

        # note a 'line' is a tuple of (axesIndex, lineIndex)
        if lines is None:
            lines = []
        if labels is None:
            labels = []
        if seriesList is None:
            serieslist = []
            
        self._lines = []
        self._labels = []
        self._series = []
        
        itemZip = zip(lines,labels,seriesList)
        
        for item in itemZip:
            self.addLegendEntry(*item)
        
        self.setLocation(location)
        #self.setParentPlot(parentPlot)
        
    def addLegendEntry(self, line, label, series=None):
        
        self._lines.append(line)
        self._labels.append(label)
        self._series.append(series)
        
    def deleteLegendEntry(self, line, label, series=None):
        self._lines.remove(line)
        self._labels.remove(label)
        self._series.remove(series)
        
    def getLines(self):
        return self._lines
    
    def getLabels(self):
        return self._labels
    
    def getSeries(self, index=None):
        if index is None:
            return self._series
        else:
            return self._series[index]
    
    def getLocation(self):
        return self._location
    
    def setLocation(self, location):
        self._location = location


class SeriesGroup(dc.GenericCollection):
    """
    Series group, used to group series on a plot.
    
    In addition to being an organizational tool, series groups have the additional function of allowing
    the application of a single series format to all series in the group in a single operation.
    
    """
    type = cst.SERIES_GROUP
    
    def __init__(self, collections=None, name=None, colour='b', weight=2, 
                 lineStyle='-', marker='', isEnabled=True, isDefaultCollection=False):
        dc.GenericCollection.__init__(self, collections=collections, name=name, 
                                      type=cst.SERIES_GROUP, memberClass=SeriesObject,
                                      isDefaultCollection=isDefaultCollection)
        
        self.setColour(colour)
        self.setWeight(weight)
        self.setLineStyle(lineStyle)
        self.setMarker(marker)
        
        # The visible state of a series group affects the visibility of all series in the group.
        # Series also track their individual visibility, providing two levels of user control over visibility.
        self.isEnabled(isEnabled)
        
        # Format linking is used to identify if series group formatting changes should be applied to
        #  all series in the group which also have their "isFormatLinked" flag set.
        self.isFormatLinked(False)
        
        #self._createDefaultSeriesObject()
        
    def createSeries(self, xData, yData, name, colour, weight, lineStyle, marker, isHidden,
                     xDataIndex, yDataIndex, axesIndex, sourceDAP, xDataType, yDataType):
        
        return self._new(SeriesObject,
                         xData=xData,
                         yData=yData,
                         name=name,
                         colour=colour,
                         weight=weight,
                         lineStyle=lineStyle,
                         marker=marker,
                         isHidden=isHidden,
                         xDataIndex=xDataIndex,
                         yDataIndex=yDataIndex,
                         axesIndex=axesIndex,
                         sourceDAP=sourceDAP,
                         xDataType=xDataType,
                         yDataType=yDataType)
    
    def setColour(self, colour):
        self._colour = colour
        for series in self.getSeries():
            # only set a member series' format if it is formatLinked
            if series.isFormatLinked():
                series.setColour(colour)
        
    def getColour(self):
        return self._colour
    
    def setWeight(self, weight):
        self._weight = weight
        for series in self.getSeries():
            # only set a member series' format if it is formatLinked
            if series.isFormatLinked():
                series.setWeight(weight)
    
    def getWeight(self):
        return self._weight
    
    def setLineStyle(self, style):
        self._lineStyle = style
        for series in self.getSeries():
            # only set a member series' format if it is formatLinked
            if series.isFormatLinked():
                series.setLineStyle(style)
    
    def getLineStyle(self):
        return self._lineStyle
    
    def setMarker(self, marker):
        self._marker = marker
        for series in self.getSeries():
            # only set a member series' format if it is formatLinked
            if series.isFormatLinked():
                series.setMarker(marker)
    
    def getMarker(self):
        return self._marker
      
    def getSeries(self):
        return self._getMembers()
    
    def getDefaultSeries(self):
        return self._getDefaultMember()
    
    def hasDefaultSeries(self):
        return self._hasDefaultMember()
    
    def setDefaultSeries(self, series):
        return self._setDefaultMember(series)
        
    def add(self, series):
        return self._add(series)
        
    def remove(self, series):
        return self._remove(series)
        
    def purgeSeries(self, series):
        # completely destroy a series in the series group
        return self._purge(series)
    
    def isFormatLinked(self, flag=None):
        if flag is None:
            return self._isFormatLinked
        else:
            self._isFormatLinked = flag
    
    def isEnabled(self, flag=None):
        if flag is None:
            if not hasattr(self, '_isEnabled'):
                # re-map isVisible to isEnabled when using old data files
                if hasattr(self, '_isVisible'):
                    self._isEnabled = self._isVisible
                else:
                    self._isEnabled = True
            return self._isEnabled
        else:
            self._isEnabled = flag
            
            
class SeriesObject(dc.GenericDataClass):
    """
    Series, the lines on a plot.
    
    """
    type = cst.SERIES
    
    def __init__(self, collections, xData, yData, name, colour='b', weight=2, 
                 lineStyle='-', marker='', isHidden=False, isVisible=True, isEnabled=True,
                 xDataIndex=0, yDataIndex=1, axesIndex=cst.X1Y1, sourceDAP=None,
                 xDataType=cst.RESULT_UNDEFINED, yDataType=cst.RESULT_UNDEFINED):
        
        # the sourceDAP and data indicies are required for the autonaming 
        #  which will occur in the __init__ to follow
        self.setSourceDAP(sourceDAP)
        self.setXDataIndex(xDataIndex)
        self.setYDataIndex(yDataIndex)
        self.setXDataType(xDataType)
        self.setYDataType(yDataType)
        
        dc.GenericDataClass.__init__(self, collections=collections, name=name, type=cst.SERIES)
        
        self.setXData(xData)
        self.setYData(yData)
        self.setColour(colour)
        self.setWeight(weight)
        self.setLineStyle(lineStyle)
        self.setMarker(marker)
        
        # Currently the only series to use the isHidden flag are the single point series used for data probe highlighting.
        self.isHidden(isHidden)
        
        self.isVisible(isVisible)
        self.isEnabled(isVisible)
        
        self.setAxesIndex(axesIndex)
        
        self.isFormatLinked(False)
        #self.setSeriesIndex(None)
        
    def setYDataType(self, type):
        self._yDataType = type
        
    def getYDataType(self):
        return self._yDataType
    
    def setXDataType(self, type):
        self._xDataType = type
        
    def getXDataType(self):
        return self._xDataType
        
    def copy(self):
        """
        Return a copy of the series object.
        
        """
        return SeriesObject(self.getMembershipList(),
                            self.getXData(),
                            self.getYData(),
                            self.getName(),
                            self.getColour(),
                            self.getWeight(),
                            self.getLineStyle(),
                            self.getMarker(),
                            self.isHidden(), 
                            self.isVisible(),
                            self.isEnabled(),
                            self.getXDataIndex(),
                            self.getYDataIndex(),
                            self.getAxesIndex(),
                            self.getSourceDAP(),
                            self.getXDataType(),
                            self.getYDataType())
        
    def copyFrom(self, series):
        """
        Copy all the data/visual parameters of the passed series over the parameters of this series.
        
        This method retains original memberships of self.
        
        """
        # set these 5 first, since they're used for autonaming
        self.setSourceDAP(series.getSourceDAP())
        self.setXDataIndex(series.getXDataIndex())
        self.setYDataIndex(series.getYDataIndex())
        self.setXDataType(series.getXDataType())
        self.setYDataType(series.getYDataType())
        
        self.setXData(series.getXData())
        self.setYData(series.getYData())
        
        if self.hasAutoName():
            self.setName()
        else:
            self.setName(series.getName())
            
        self.setColour(series.getColour())
        self.setWeight(series.getWeight())
        self.setLineStyle(series.getLineStyle())
        self.setMarker(series.getMarker())
        self.isHidden(series.isHidden())
        self.isVisible(series.isVisible())
        self.isEnabled(series.isEnabled())
        

    def setName(self, name=None):
        """
        Override the setName method to handle series names in a more complex manner.
        
        """
        # Flag passed to the unique name check to remove the passed name 
        #  from the comparison list if we know it's already in that list.
        alreadyAdded = True
        
        if name is None:
            # auto name this series if none is passed
            
            sourceDAP = self.getSourceDAP()
            
            # if the yAxis data is a coordinate, use the xAxis label
            if self.getYDataType() not in cst.COORDINATE_TYPES:
                # normally use the yAxis label
                self._baseName = sourceDAP.getLabel(self.getYDataIndex())
            else:
                self._baseName = sourceDAP.getLabel(self.getXDataIndex())
            
            # retain old iterator if the baseName is similar
            # (if the old name exists, and the new baseName is a part of it, set the baseName
            #  to be the trailing end of the old name, including trailing iterators)
            # This is done to maintain "temperature 2" as '2' when switching from simple to detailed labels,
            #  regardless of the order the series are encountered.
            if hasattr(self, '_name'):
                # only check the "later part" of the old name, if it's long enough
                #  (this is meant to avoid detecting the baseName in the detailed series rootName)
                # the baseName plus 4 (space + 3 digits) should be a safe value
                checkLength = len(self._baseName) + 4
                
                if self._baseName in self._name[-checkLength:]:
                    index = self._name[-checkLength:].find(self._baseName)
                    self._baseName = self._name[-checkLength:][index:]
            
            # extend to a detailed name if required
            plot = self.getPlot()
            if plot is not None and plot.hasDetailedSeriesLabels():
                #rootName = self.getDetailedRootName()
                self._name = self.getDetailedName()
            else:
                self._name = self._baseName
            
            self._autoName = True
            alreadyAdded = False
            
        else:
            self._baseName = name
            self._name = name
            self._autoName = False
            
        membershipList = self.getMembershipList()
        # if the series is a member of a series group, perform name uniqueness checks with all series in the group
        if len(membershipList) > 0:
            
            groupMembershipList = membershipList[0].getMembershipList()
            if len(groupMembershipList) > 0:
                # build a list of all other series names on the plot
                otherNames = [member.getName() for member in groupMembershipList[0].getSeries() if member is not self]
                
                if self._name in otherNames:
                    # generate a new name for the series, which is unique on the plot
                    self._name = ccf.generateNewName(baseName = self._name, 
                                                     currentNameList = otherNames,
                                                     isDefaultName = False)         
            

    def getDetailedName(self):
        '''
        Get the detailed name based on flags from the source plot.
        
        '''
        
        sourcePlot = self.getPlot()
        
        if sourcePlot:
            nameString = ''
            labelFlags = sourcePlot.getDetailedSeriesLabelFlags()
            
            sourceDAP = self.getSourceDAP()
            
            if labelFlags[0]:  # study
                nameString += '%s: ' % sourceDAP.getParentRun().getMembershipList()[0].getMembershipList()[0].getName()
            if labelFlags[1]:  # data group
                nameString += '%s: ' % sourceDAP.getParentRun().getMembershipList()[0].getName()
            if labelFlags[2]:  # data object
                nameString += '%s: ' % sourceDAP.getParentRun().getName()
            if labelFlags[3]:  # AP group
                nameString += '%s: ' % sourceDAP.getAnalysisPointGroups()[0].getName()
            if labelFlags[4]:  # AP
                nameString += '%s: ' % sourceDAP.getName()
        else:
            # lacking a source plot (which provides detailed formatting options); simply use the parentRun
            nameString = '%s: ' % sourceDAP.getParentRun().getName()
            
        nameString += self._baseName
        
        return nameString
    
    
    def getPlotData(self):
        """
        Returns the attributes of this series in a plotData object.
        
        The plotData object is the type of object the plotPanel expects when plotting a series.
        """
        return pdc.PlotData(self.getXData(),
                            self.getYData(),
                            self.getName(),
                            self.getColour(),
                            self.getWeight(),
                            self.getLineStyle(),
                            self.getMarker())
        
    def getLabels(self, getX=False, getY=False):
        """
        Returns the X/Y labels of the series.
        
        """
        getBoth = False
        
        # if neither was specified, return both
        if not getX and not getY:
            getX = True
            getY = True
        
        if getX and getY:
            getBoth = True
        
        dap = self._sourceDAP
        
        if dap is not None:
            if getX:
                xLabel = dap.getLabel(self._xDataIndex)
                xUnits = dap.getUnit(self._xDataIndex)
                
                # adjust the unit names if the plot isn't set to SI
                if self.getPlot().getUnitSystem() == uc.SYSTEM_USCS:
                    xUnits = uc.names[xUnits]
                
                # only include units in the label if some are defined
                if xUnits != '':
                    xLabel += ' (' + xUnits + ')'
                
            if getY:
                yLabel = dap.getLabel(self._yDataIndex)
                yUnits = dap.getUnit(self._yDataIndex)
                
                # adjust the unit names if the plot isn't set to SI
                if self.getPlot().getUnitSystem() == uc.SYSTEM_USCS:
                    yUnits = uc.names[yUnits]
                
                # only include units in the label if some are defined
                if yUnits != '':
                    yLabel += ' (' + yUnits + ')'
                
            if getBoth:
                # return a tuple of labels if both were requested
                return (xLabel, yLabel)
            elif getX:
                # otherwise, return single labels
                return xLabel
            else:
                return yLabel
        else:
            # if the series has no sourceDAP, return None
            return None
    
    def setXData(self, xData, logMask=False):
        if logMask:
            # for log plots, mask all data <= zero
            self._xData = numpy.ma.masked_where(xData <= 0, xData)
        else:
            # all non-log data is masked exactly at the CSV_NULL_VALUE
            #  (this is primarily to prevent plotting of padded/unincluded data from 2D run results)
            self._xData = numpy.ma.masked_where(xData == cst.CSV_NULL_VALUE, xData)
            #self._xData = xData
        
    def getXData(self):
        return self._xData
    
    def setYData(self, yData, logMask=False):
        if logMask:
            # for log plots, mask all data <= zero
            self._yData = numpy.ma.masked_where(yData <= 0, yData)
        else:
            # all non-log data is masked exactly at the CSV_NULL_VALUE
            #  (this is primarily to prevent plotting of padded/unincluded data from 2D run results)
            self._yData = numpy.ma.masked_where(yData == cst.CSV_NULL_VALUE, yData)
            #self._yData = yData
        
    def getYData(self):
        return self._yData
    
    def setColour(self, colour):
        self._colour = colour
        
    def getColour(self):
        return self._colour
    
    def setWeight(self, weight):
        self._weight = weight
        
    def getWeight(self):
        return self._weight
    
    def setLineStyle(self, lineStyle):
        self._lineStyle = lineStyle
        
    def getLineStyle(self):
        return self._lineStyle
    
    def setMarker(self, marker):
        self._marker = marker
        
    def getMarker(self):
        return self._marker
    
    def getSeriesGroup(self):
        """
        Return the series group for the series.
        
        """
        return self.getMembershipList()[0]
    
    def getPlot(self):
        """
        Return the plot object for the series.
        
        """
        
        # TODO: Assumes one plot per series
        
        seriesGroups = self.getMembershipList()
        
        if len(seriesGroups) == 1:
            plots = seriesGroups[0].getMembershipList()
            if len(plots) == 1:
                return plots[0]
            
        return None
    
    def hasAutoName(self):
        return self._autoName
    
    def isHidden(self, flag=None):
        if flag is None:
            return self._isHidden
        else:
            self._isHidden = flag
            
    def setXDataIndex(self, index):
        self._xDataIndex = index
        
    def getXDataIndex(self):
        return self._xDataIndex
    
    def setYDataIndex(self, index):
        self._yDataIndex = index
        
    def getYDataIndex(self):
        return self._yDataIndex
    
    def setAxesIndex(self, index=cst.X1Y1):
        self._axesIndex = index
        
    def getAxesIndex(self):
        return self._axesIndex
    
    def getXAxisIndex(self):
        return self._axesIndex // 4
    
    def getYAxisIndex(self):
        return self._axesIndex % 4
    
    def setSourceDAP(self, DAP):
        self._sourceDAP = DAP
        
    def getSourceDAP(self):
        return self._sourceDAP
    
    def isFormatLinked(self, flag=None):
        if flag is None:
            return self._isFormatLinked
        else:
            self._isFormatLinked = flag

    def isVisible(self, flag=None):
        if flag is None:
            return self._isVisible
        else:
            self._isVisible = flag
            
    def isEnabled(self, flag=None):
        if flag is None:
            if not hasattr(self, '_isEnabled'):
                self._isEnabled = self._isVisible
            return self._isEnabled
        else:
            self._isEnabled = flag

class PlotObject(dc.GenericCollection):
    """
    Plot object, a container for series (grouped in series groups) with additional plot formatting information.
    
    """
    type = cst.PLOT
    
    def __init__(self, collections=None, name=None, seriesGroups=None, 
                 unitSystem=None, type=cst.PLOT, isDefaultCollection=False):
        
        dc.GenericCollection.__init__(self, collections=collections, name=name, 
                                    type=type, memberClass=SeriesGroup,
                                    isDefaultCollection=isDefaultCollection)
    
        # set old plotOptions values (now uses private PlotObject attributes and get/set/has methods)
        
        self.setAxisFontSize(16)
        self.setLabelFontSize(20)
        self.hasGridlines(True)
        self.hasLegend(False)
        self.isLegendSuppressed(False)
        self.setXAxisNumberFormat(['',''])
        self.setXLabels()
        self.hasXCustomLabel([False, False])
        self.setXLimits([(0,1000),(0,1000)])
        self.hasXRangeSet([False, False])
        self.isXActive([True, False])
        self.isXLog([False, False])
        self.setXTickLabelFont([pdc.cmt_FontProperties(size=12.),pdc.cmt_FontProperties(size=12.)])
        self.setXAxisTitleFont([pdc.cmt_FontProperties(weight=600.),pdc.cmt_FontProperties(weight=600.)])
        self.setYAxisNumberFormat(['','','',''])
        self.setYLabels()
        self.hasYCustomLabel([False, False, False, False])
        self.setYLimits([(0,1000), (0,1000), (0,1000), (0,1000)])
        self.hasYRangeSet([False, False, False, False])
        self.isYActive([True, False, False, False])
        self.isYLog([False, False, False, False])
        self.setYTickLabelFont([pdc.cmt_FontProperties(size=12.),pdc.cmt_FontProperties(size=12.),pdc.cmt_FontProperties(size=12.),pdc.cmt_FontProperties(size=12.)])
        self.setYAxisTitleFont([pdc.cmt_FontProperties(weight=600.),pdc.cmt_FontProperties(weight=600.),pdc.cmt_FontProperties(weight=600.),pdc.cmt_FontProperties(weight=600.)])
        self.setXScaleAuto([cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM, 
                            cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM])
        self.setYScaleAuto([cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM, 
                            cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM, 
                            cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM, 
                            cst.AUTO_MINIMUM | cst.AUTO_MAXIMUM])
        self.setLegend(None)
        self.setUnitSystem(unitSystem)
        self.hasDetailedSeriesLabels(False)
        self.setDetailedSeriesLabelFlags([False, False, True, False, False])
        self.setPlotType(cst.PLOT_NORMAL)
        self.isVisible(True)
        
        self.initializeColourQueue()
        
        # add seriesGroups
        self.addSeriesGroups(seriesGroups)
        
    def isLegendSuppressed(self, value=None):
        if not hasattr(self, '_isLegendSuppressed'):
            self._isLegendSuppressed = False
            
        if value is None:
            return self._isLegendSuppressed
        else:
            self._isLegendSuppressed=value
            
    def getPlotGroup(self):
        plotGroups = self.getMembershipList()
        if len(plotGroups) == 1:
            return plotGroups[0]
        else:
            return None
        
    def generateNewGroupName(self):
        '''
        Generate a new group name.
        
        '''
        
        return self._generateNewMemberName()
    
    def addSeriesGroups(self, seriesGroups):
        self._addMembers(seriesGroups)
        
    def createSeriesGroup(self, name=None, colour='b', weight=2, lineStyle='-', marker=''):
        #return SeriesGroup(self, name=name, colour=colour, weight=weight, lineStyle=lineStyle, marker=marker)
        return self._new(SeriesGroup, name=name, colour=colour, weight=weight, 
                         lineStyle=lineStyle, marker=marker)
    
    def moveSeriesObjects(self, objects, targetGroup):
        """
        Move series objects from their existing series object group owner to another.
        
        """
        
        for object in objects:
            objectGroups = object.getMembershipList()
            
            if len(objectGroups) == 1:
                objectGroup = objectGroups[0]
                objectGroup.move(object, targetGroup)
            else:
                raise RuntimeError, "This series object is present in multiple "\
                      "series object groups, and moving it doesn't make sense "\
                      "in context of the current code structure."
            
    def getSeriesGroups(self):
        """
        Returns a list of all series groups on the plot.
        
        """
        return self._getMembers()
        
    def getSeries(self):
        """
        Return a list of all series on the plot.
        
        """
        
        seriesList = []
        
        seriesGroups = self._getMembers()
        
        for seriesGroup in seriesGroups:
            seriesList.append(seriesGroup.getSeries())
            
        return ccf.flatten(seriesList)
        
    def initializeColourQueue(self):
        self._colourQueue = dc.Queue()
        
        for colour in cst._colours:
            self._colourQueue.queue(colour)
    
    def getNextColour(self):
        return self._colourQueue.peek()
    
    def removeColour(self, colour):
        self._colourQueue.remove(colour)
        self._colourQueue.queue(colour)
        
    def purgeSeriesGroup(self, seriesGroup):
        return self._purge(seriesGroup)
        
    # General get/set/has methods
        
    def setAxisFontSize(self, fontSize):
        self._axisFontSize = fontSize
        
    def getAxisFontSize(self):
        return self._axisFontSize
    
    def setLabelFontSize(self, fontSize):
        self._labelFontSize = fontSize
    
    def getLabelFontSize(self):
        return self._labelFontSize
    
    def hasGridlines(self, flag=None):
        if flag is None:
            return self._hasGridlines
        else:
            self._hasGridlines = flag
    
    def hasLegend(self, flag=None):
        if flag is None:
            return self._hasLegend
        else:
            self._hasLegend = flag
            
    def hasDetailedSeriesLabels(self, flag=None):
        if flag is None:
            return self._hasDetailedSeriesLabels
        else:
            self._hasDetailedSeriesLabels = flag
            
    def setDetailedSeriesLabelFlags(self, flags):
        self._detailedSeriesLabelFlags = flags
        
    def getDetailedSeriesLabelFlags(self):
        return self._detailedSeriesLabelFlags
        
    def getDetailedSeriesLabelFlag(self, index):
        return self._detailedSeriesLabelFlags[index]
    
    def setPlotType(self, type):
        self._plotType = type
        
    def getPlotType(self):
        return self._plotType
    
    def getDefaultSeriesGroup(self):
        return self._getDefaultMember()
    
    def getLegend(self):
        return self._legend
            
    def setLegend(self, legend=None):
        self._legend = legend
        if legend is None:
            self.hasLegend(False)
        else:
            self.hasLegend(True)
        
    def getUnitSystem(self):
        return self._unitSystem
    
    def setUnitSystem(self, unitSystem=None):
        self._unitSystem = unitSystem
    
    def isDefaultUnitSystem(self, flag=None):
        if flag is None:
            if not hasattr(self, '_isDefaultUnitSystem'):
                self._isDefaultUnitSystem = True
            return self._isDefaultUnitSystem
        else:
            self._isDefaultUnitSystem = flag
            
    # X get/set/has methods
    
    def setXAxisNumberFormat(self, format=None):
        self._xAxisNumberFormat = format
        
    def getXAxisNumberFormat(self):
        return self._xAxisNumberFormat
    
    def setXLabels(self, labels=None):
        
        if labels is None:
            labels = [None,None]
            
        self._xLabels = labels
        
    def setXLabel(self, label, index):
        self._xLabels[index] = label
        
    def getXLabels(self):
        return self._xLabels
    
    def getXLabel(self, index):
        return self._xLabels[index]
    
    def setXLimits(self, limits, index=None):
        if index is None:
            self._xLimits = limits
        else:
            self._xLimits[index] = limits
        
    def getXLimits(self):
        return self._xLimits
    
    def hasXRangeSet(self, flags=None):
        if flags is None:
            return self._hasXRangeSet
        else:
            self._hasXRangeSet = flags
            
    def hasXCustomLabel(self, flags=None, index=None):
        if flags is None:
            return self._hasXCustomLabel
        else:
            if index is None:
                self._hasXCustomLabel = flags
            else:
                self._hasXCustomLabel[index] = flags
    
    def isXActive(self, flags=None, index=None):
        if flags is None:
            return self._isXActive
        else:
            if index is None:
                self._isXActive = flags
            else:
                self._isXActive[index] = flags
            
    def isXLog(self, flags=None, index=None):
        """
        Gets/Sets the Log/Linear state of X axes.
        
        You can either pass a list of the full axes state (ie; .isXLog([True, False])  ),
        or a single axis state and an axis index (ie; .isXLog(True, 0)  )
        """
        if flags is None:
            return self._isXLog
        else:
            if index is None:
                self._isXLog = flags
            else:
                self._isXLog[index] = flags
            
    def setXTickLabelFont(self, font=None):
        self._xTickLabelFont = font
        
    def getXTickLabelFont(self):
        return self._xTickLabelFont
    
    def setXAxisTitleFont(self, font=None):
        self._xAxisTitleFont = font
        
    def getXAxisTitleFont(self):
        return self._xAxisTitleFont
    
    def setXScaleAuto(self, flags, index=None):
        if index is None:
            self._xScaleAuto = flags
        else:
            self._xScaleAuto[index] = flags
        
    def getXScaleAuto(self):
        return self._xScaleAuto
    
    
    # Y get/set/has methods
    
    def setYAxisNumberFormat(self, format=None):
        self._yAxisNumberFormat = format
        
    def getYAxisNumberFormat(self):
        return self._yAxisNumberFormat
    
    def setYLabels(self, labels=None):
        if labels is None:
            labels = [None, None, None, None]
            
        self._yLabels = labels
        
    def setYLabel(self, label, index):
        self._yLabels[index] = label
        
    def getYLabels(self):
        return self._yLabels
    
    def getYLabel(self, index):
        return self._yLabels[index]
    
    def setYLimits(self, limits, index=None):
        if index is None:
            self._yLimits = limits
        else:
            self._yLimits[index] = limits
        
    def getYLimits(self):
        return self._yLimits
    
    def hasYRangeSet(self, flags=None):
        if flags is None:
            return self._hasYRangeSet
        else:
            self._hasYRangeSet = flags
            
    def hasYCustomLabel(self, flags=None, index=None):
        if flags is None:
            return self._hasYCustomLabel
        else:
            if index is None:
                self._hasYCustomLabel = flags
            else:
                self._hasYCustomLabel[index] = flags
    
    def isYActive(self, flags=None, index=None):
        if flags is None:
            return self._isYActive
        else:
            if index is None:
                self._isYActive = flags
            else:
                self._isYActive[index] = flags
            
    def isYLog(self, flags=None, index=None):
        """
        Gets/Sets the Log/Linear state of Y axes.
        
        You can either pass a list of the full axes state (ie; .isYLog([True, False, False, False])  ),
        or a single axis state and an axis index (ie; .isYLog(True, 0)  )
        """
        if flags is None:
            return self._isYLog
        else:
            if index is None:
                self._isYLog = flags
            else:
                self._isYLog[index] = flags
                
    def isVisible(self, flag=None):
        if flag is None:
            return self._isVisible
        else:
            self._isVisible = flag
            
    # isEnabled intentionally uses the same attribute as isVisible
    #  (it exists to give consistency to all checkable tree items)
    def isEnabled(self, flag=None):
        if flag is None:
            return self._isVisible
        else:
            self._isVisible = flag            
            
    def setYTickLabelFont(self, font=None):
        self._yTickLabelFont = font
        
    def getYTickLabelFont(self):
        return self._yTickLabelFont
    
    def setYAxisTitleFont(self, font=None):
        self._yAxisTitleFont = font
        
    def getYAxisTitleFont(self):
        return self._yAxisTitleFont    
        
    def setYScaleAuto(self, flags, index=None):
        if index is None:
            self._yScaleAuto = flags
        else:
            self._yScaleAuto[index] = flags
        
    def getYScaleAuto(self):
        return self._yScaleAuto
    
    def getPlotOptions(self):
        """
        Returns a pdc.PlotOptions object based on the attributes of this plot.
        
        """
        return pdc.PlotOptions(self.getAxisFontSize(),
                               self.getLabelFontSize(),
                               self.hasGridlines(),
                               self.hasLegend(),
                               self.getXAxisNumberFormat(),
                               self.getXLabels(),
                               self.getXLimits(),
                               self.hasXRangeSet(),
                               self.isXActive(),
                               self.isXLog(),
                               self.getXTickLabelFont(),
                               self.getXAxisTitleFont(),
                               self.getYAxisNumberFormat(),
                               self.getYLabels(),
                               self.getYLimits(),
                               self.hasYRangeSet(),
                               self.isYActive(),
                               self.isYLog(),
                               self.getYTickLabelFont(),
                               self.getYAxisTitleFont(),
                               self.getXScaleAuto(),
                               self.getYScaleAuto(),
                               self.getLegend(),
                               self.getUnitSystem(),
                               self.hasDetailedSeriesLabels())

