"""
Dec 2022, NOT USED BY ANY OTHER CODE ?

Utilities to define analysis results.

These are keys in bAnalysis_ spike dict and columns in output reports
"""

import json
import os
from typing import Dict, List, Tuple

import numpy as np  # needed to convert np types to JSON types in save
import pandas as pd
import sanpy

from sanpy.sanpyLogger import get_logger
logger = get_logger(__name__)

defaultVal = float('nan')

# each key in analysisResultDict needs to have the same dict
def getDefaultDict():
    defaultDict = {
        'type': '',  # like: int, float, boolean, list
        'default': '',  # default value, can be 0, None, NaN, ...
        'units': '',  # real world units like point, mV, dvdt
        'depends on detection': '',  # organize documentation and refer to bDetect keys
        'error': '',  # if this analysis results can trigger an error
        'description': '',  # long description for documentation
        }
    return defaultDict.copy()

analysisResultDict = {}

key = 'analysisDate'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Date of analysis in yyyymmdd format.'

key = 'analysisTime'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Time of analysis in hh:mm:ss 24 hours format.'

key = 'modDate'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Modification date if AP is modified after detection.'

key = 'modTime'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Modification time if AP is modified after detection.'

key = 'analysisVersion'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Analysis version when analysis was run. See sanpy.analysisVersion'

key = 'interfaceVersion'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Interface version string when analysis was run. See sanpy.interfaceVersion'

key = 'file'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Name of raw data file analyzed'

key = 'detectionType'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = ''
analysisResultDict[key]['default'] = None
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Type of detection, either vm or dvdt. See enum sanpy.bDetection.detectionTypes'

key = 'cellType'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'User specified cell type'

key = 'sex'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'User specified sex'

key = 'condition'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'str'
analysisResultDict[key]['default'] = ''
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'User specified condition'

#
# start real analysis results
key = 'sweep'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = 0  # todo: not sure
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Sweep number of analyzed sweep. Zero based.'

key = 'epoch'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = np.nan  # todo: not sure
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Stimulus epoch number the spike occured in. Zero based.'

key = 'epochLevel'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = np.nan  # todo: not sure
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Epoch level (DAC) stimulus during the spike.'

key = 'sweepSpikeNumber'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = None  # todo: not sure
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Spike number within the sweep. Zero based.'

key = 'spikeNumber'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = None  # todo: not sure
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Spike number across all sweeps. Zero based.'

key = 'include'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'bool'
analysisResultDict[key]['default'] = True
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Boolean indication include or not. Can be set by user/programmatically  after analysis.'

key = 'userType'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = 0
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Integer indication user type. Can be set by user/programmatically  after analysis.'


key = 'errors'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'list'
analysisResultDict[key]['default'] = []
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'List of dictionary to hold detection errors for this spike'

#
# detection parameters
key = 'dvdtThreshold'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'dvdt'
analysisResultDict[key]['depends on detection'] = 'dvdtThreshold'
analysisResultDict[key]['description'] = 'AP Threshold in derivative dv/dt'

key = 'mvThreshold'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'
analysisResultDict[key]['depends on detection'] = 'mvThreshold'
analysisResultDict[key]['description'] = 'AP Threshold in primary recording mV'

# todo: what about solay ...
key = 'medianFilter'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = 0
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['depends on detection'] = 'medianFilter'
analysisResultDict[key]['description'] = 'Median filter to generate filtered vm and dvdt. Value 0 indicates no filter.'

key = 'halfHeights'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'list'
analysisResultDict[key]['default'] = []
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['depends on detection'] = 'halfHeights'
analysisResultDict[key]['description'] = 'List of int to specify half-heights like [10, 20, 50, 80, 90].'

#
# actual value coming out of detection !!!!
key = 'thresholdPnt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['description'] = 'AP threshold point'

key = 'thresholdSec'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'sec'
analysisResultDict[key]['description'] = 'AP threshold seconds'

key = 'thresholdVal'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'  # TODO: will be pA for voltage-clamp
analysisResultDict[key]['description'] = 'Value of Vm at AP threshold point.'

key = 'thresholdVal_dvdt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'dvdt'  # TODO: will be pA for voltage-clamp
analysisResultDict[key]['description'] = 'Value of dvdt at AP threshold point.'

key = 'dacCommand'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'  # TODO: will be pA for voltage-clamp
analysisResultDict[key]['description'] = 'Value of DAC command at AP threshold point.'

key = 'peakPnt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['depends on detection'] = '(onlyPeaksAbove_mV, peakWindow_ms)'
analysisResultDict[key]['description'] = 'AP peak point.'

key = 'peakSec'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'sec'
analysisResultDict[key]['description'] = 'AP peak seconds.'

key = 'peakVal'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV' # voltage-clamp'
analysisResultDict[key]['description'] = 'Value of Vm at AP peak point.'

key = 'peakHeight'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'  # voltage-clamp
analysisResultDict[key]['description'] = 'Difference between peakVal minus thresholdVal.'

key = 'timeToPeak_ms'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'ms' # voltage-clamp'
analysisResultDict[key]['description'] = 'Time to peak (ms) after TOP.'

key = 'preMinPnt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'  # voltage-clamp
analysisResultDict[key]['depends on detection'] = 'mdp_ms'
analysisResultDict[key]['description'] = 'Minimum before an AP taken from predefined window.'

key = 'preMinVal'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'  # voltage-clamp
analysisResultDict[key]['description'] = 'Minimum before an AP taken from predefined window.'

key = 'preLinearFitPnt0'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'  # voltage-clamp
analysisResultDict[key]['description'] = 'Point where pre linear fit starts. Used for EDD Rate'

key = 'preLinearFitPnt1'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'  # voltage-clamp
analysisResultDict[key]['description'] = 'Point where pre linear fit stops. Used for EDD Rate'

key = 'earlyDiastolicDuration_ms'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'ms'  # voltage-clamp
analysisResultDict[key]['description'] = 'Time (ms) between start/stop of EDD.'

key = 'preLinearFitVal0'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mv'  # voltage-clamp
analysisResultDict[key]['description'] = ''

key = 'preLinearFitVal1'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mv'
analysisResultDict[key]['description'] = ''

key = 'earlyDiastolicDurationRate'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mv/S'
analysisResultDict[key]['description'] = 'Early diastolic duration rate, the slope of the linear fit between start/stop of EDD.'

key = 'lateDiastolicDuration'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'Depreciated'

key = 'preSpike_dvdt_max_pnt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['description'] = 'Point corresponding to peak in dv/dt before an AP.'

key = 'preSpike_dvdt_max_val'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'
analysisResultDict[key]['description'] = 'Value of Vm at peak of dv/dt before an AP.'

key = 'preSpike_dvdt_max_val2'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'dv/dt'
analysisResultDict[key]['description'] = 'Value of dv/dt at peak of dv/dt before an AP.'

key = 'postSpike_dvdt_min_pnt'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['depends on detection'] = 'dvdtPostWindow_ms'
analysisResultDict[key]['description'] = 'Point corresponding to min in dv/dt after an AP.'

key = 'postSpike_dvdt_min_val'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'mV'
analysisResultDict[key]['description'] = 'Value of Vm at minimum of dv/dt after an AP.'

key = 'postSpike_dvdt_min_val2'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'dvdt'
analysisResultDict[key]['description'] = 'Value of dv/dt at minimum of dv/dt after an AP.'

key = 'isi_pnts'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['depends on detection'] = 'refractory_ms'
analysisResultDict[key]['description'] = 'Inter-Spike-Interval (points) with respect to previous AP.'

key = 'isi_ms'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'ms'
analysisResultDict[key]['description'] = 'Inter-Spike-Interval (ms) with respect to previous AP.'

key = 'spikeFreq_hz'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'Hz'
analysisResultDict[key]['description'] = 'AP frequency with respect to previous AP.'

key = 'cycleLength_pnts'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['description'] = 'Points between APs with respect to previous AP.'

key = 'cycleLength_ms'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'int'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'point'
analysisResultDict[key]['description'] = 'Time (ms) between APs with respect to previous AP.'

key = 'diastolicDuration_ms'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'float'
analysisResultDict[key]['default'] = defaultVal
analysisResultDict[key]['units'] = 'ms'
analysisResultDict[key]['description'] = 'Time (ms) between minimum before AP (preMinPnt) and AP time (thresholdPnt).'

key = 'widths'
analysisResultDict[key] = getDefaultDict()
analysisResultDict[key]['type'] = 'list'
analysisResultDict[key]['default'] = []
analysisResultDict[key]['units'] = ''
analysisResultDict[key]['description'] = 'A list of dict to hold half-height information for each half-height in detection halfHeights.'

# need to define keys width_<i> from halfHeights list [10, 20, 50, 80, 90]
for i in [10, 20, 50, 80, 90]:
    key = 'widths_' + str(i)
    analysisResultDict[key] = getDefaultDict()
    analysisResultDict[key]['type'] = 'int'
    analysisResultDict[key]['default'] = defaultVal
    analysisResultDict[key]['units'] = 'percent'
    analysisResultDict[key]['depends on detection'] = 'halfWidthWindow_ms'
    analysisResultDict[key]['description'] = f'Width (ms) at half-height {i} %.'

# now we have (AP threshold, AP peak), derive stats about each AP

def printDocs():
    """
    Print out human readable detection parameters and convert to markdown table.

    Requires:
        pip install tabulate

    See: bDetection.printDocs()
    """
    import pandas as pd
    from datetime import datetime

    logger.info('Ensure there are no errors')

    dictList = []
    for k, v in analysisResultDict.items():
        # iterating on getDefaultDict() to ensure all code above has valid k/v pairs
        #lineStr = k + '\t'
        oneDict = {
            'Name': k,
        }
        for k2 in getDefaultDict().keys():
            #print(f'  {k}: {k2}: {v[k2]}')
            #lineStr += f'{v[k2]}' + '\t'
            oneDict[k2] = v[k2]
        #
        #print(lineStr)

        dictList.append(oneDict)

        # check that k is in headerDefaultDict
        for k3 in v:
            if not k3 in getDefaultDict().keys():
                logger.error(f'Found extra key "{k}" in "analysisResultDict"')

    #
    df = pd.DataFrame(dictList)
    
    if 1:
        # to markdown for mkdocs md file
        #str = df.to_markdown()
        str = df.to_html()
        myDate = datetime.today().strftime('%Y-%m-%d')
        print(f'Generated {myDate} with sanpy.analysisVersion {sanpy.analysisVersion}')
        print(str)

    if 0:
        path = '/Users/cudmore/Desktop/sanpy-analysis-results.csv'
        print('saving:', path)
        df.to_csv(path, index=False)

class NumpyEncoder(json.JSONEncoder):
    """ Special json encoder for numpy types """
    def default(self, obj):
        if isinstance(obj, np.integer):
            return int(obj)
        elif isinstance(obj, np.floating):
            return float(obj)
        elif isinstance(obj, np.ndarray):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)

class analysisResultList:
    """Class encapsulating a list of analysisResultDict (one dict per spike).
    """
    def __init__(self):
        # one copy for entire list
        
        # TODO: put xxx in a function getAnalysisResltDict()
        self._dDict = analysisResultDict

        # list of analysisResultDict
        self._myList = []

        self._iterIdx = -1

    def setFromListDict(self, listOfDict : List[dict]):
        """Set analysis results from a list of dict.
        
        Used when loading sanpy.bAnalysis from h5 file.

        When we create self (during spike detect) we have a list of class analysisResult.
        When we save/load we have a list of dict.

        This is assuming we re-create self every time we do spike detection
        """
        
        # do not do this, we are an analysisResultList as a list of analysisResult
        # self._myList = listOfDict
        
        self._myList = []
        for oneDict in listOfDict:
            oneAnalysisResult = analysisResult(theDict=oneDict)
            self._myList.append(oneAnalysisResult)

    def analysisDate(self):
        if len(self) > 0:
            return self._myList[0]['analysisDate']
        else:
            return None

    def analysisTime(self):
        if len(self) > 0:
            return self._myList[0]['analysisTime']
        else:
            return None
        
    def old_save(self, saveBase):
        savePath = saveBase + '-analysis.json'

        analysisList = self.asList()

        #print(analysisList[0].print())
        print(self._myList[0])

        with open(savePath, 'w') as f:
            json.dump(analysisList, f, cls=NumpyEncoder, indent=4)

    def old_load(self, loadBase):
        loadPath = loadBase + '-analysis.json'

        if not os.path.isfile(loadPath):
            logger.error(f'Did not find file: {loadPath}')
            return

        with open(loadPath, 'r') as f:
            self._myList = json.load(f)

    def appendDefault(self):
        """Append a spike to analysis.
        
        Used in bAnalysis spike detection.
        """
        oneResult = analysisResult()
        self._myList.append(oneResult)

    def appendAnalysis(self, analysisResultList):
        for analysisResult in analysisResultList:
            # analysisResult is for one spike
            self._myList.append(analysisResult)

    def addAnalysisResult(self, theKey, theDefault=None):

        # go through list and add to each [i] dict
        for spike in self:
            spike.addNewKey(theKey, theDefault=theDefault)

    def asList(self):
        """
        Return underlying list.
        """
        #return [spike.asDict() for spike in self._myList]
        return [x.asDict() for x in self._myList]

    def asDataFrame(self):
        """
        Note: underlying _myList is a list of analysisResult
        """
        return pd.DataFrame(self.asList())

    def __getitem__(self, key):
        """
        Allow [] indexing with self[int].
        """
        try:
            #return self._dDict[key]['currentValue']
            return self._myList[key]
        except (IndexError) as e:
            logger.error(f'{e}')

    def __len__(self):
        """ Allow len() with len(this)
        """
        return len(self._myList)

    def __iter__(self):
        """
        Allow iteration with "for item in self"
        """
        return self

    def __next__(self):
        """
        Allow iteration with "for item in self"
        """
        self._iterIdx += 1
        if self._iterIdx >= len(self._myList):
            self._iterIdx = -1 # reset to initial value
            raise StopIteration
        else:
            return self._myList[self._iterIdx]

class analysisResult:
    def __init__(self, theDict=None):
        """Create an anlysis item (for one spike)
        
        Args:
            theDict: Pre-existing dict when we load form h5 file
        """
        # this is the raw definition of analysis results (See above)
        # pull from this to create key/value self.rDict
        #self._dDict = analysisResultDict
        defaultDict = analysisResultDict
        # this is simple key/value pairs as we will in detection
        self._rDict = {}
        for k,v in defaultDict.items():
            default = v['default']
            self._rDict[k] = default

        if theDict is not None:
            for k,v in theDict.items():
                self[k] = v  # calls __setitem__()

    # this was interfering with converting to DataFrame ???
    '''
    def __str__(self):
        printList = []
        for k,v in self._rDict.items():
            if isinstance(v, dict):
                for k2,v2 in v.items():
                    printList.append(f'  {k2} : {v2} {type(v2)}')
            else:
                printList.append(f'{k} : {v} {type(v)}')
        return '\n'.join(printList)
    '''

    def print(self):
        printList = []
        for k,v in self._rDict.items():
            if isinstance(v, list):
                for item in v:
                    for k2,v2 in item.items():
                        printList.append(f'  {k2} : {v2} {type(v2)}')
            else:
                printList.append(f'{k} : {v} {type(v)}')
        return '\n'.join(printList)

    def addNewKey(self, theKey, theDefault=None):
        """
        Add a new key to this spike.

        Returns: (bool) True if new key added, false if key already exists.
        """
        if theDefault is None:
            #theType = 'float'
            theDefault = float('nan')

        # check if key exists
        keyExists = theKey in self._rDict.keys()
        addedKey = False
        if keyExists:
             #key exists, don't modify
             #logger.warning(f'The key "{theKey}" already exists and has value "{self._rDict[theKey]}"')
             pass
        else:
            self._rDict[theKey] = theDefault
            addedKey = True

        #
        return addedKey

    def asDict(self):
        """
        Returns underlying dictionary
        """
        return self._rDict

    def __getitem__(self, key):
        # to mimic a dictionary
        ret = None
        try:
            #return self._dDict[key]['currentValue']
            ret = self._rDict[key]
        except (KeyError) as e:
            logger.error(f'Error getting key "{key}" exception:{e}')
            raise
        #
        return ret

    def __setitem__(self, key, value):
        # to mimic a dictionary
        try:
            #self._dDict[key]['currentValue'] = value
            self._rDict[key] = value
        except (KeyError) as e:
            logger.error(f'{e}')

    def items(self):
        # to mimic a dictionary
        return self._rDict.items()

    def keys(self):
        # to mimic a dictionary
        return self._rDict.keys()


def test():
    ar = analysisResultList()

    for k,v in ar.items():
        print(k,v)


    key = 'analysisVersion'
    print(f'key:{key} value:"{ar[key]}" type:{type(ar[key])}')

    key = 'errors'
    print(f'key:{key} value:"{ar[key]}" type:{type(ar[key])}')

def test2():
    # load abf
    path = 'data/19114000.abf'
    ba = sanpy.bAnalysis(path)

    # detect
    sweepNumber = 0
    detectionClass = ba.detectionClass
    detectionClass['verbose'] = True
    ba.spikeDetect2__(sweepNumber, detectionClass)

    '''
    printSpikeNum = 4
    print(f'== printing spike {printSpikeNum}')
    ba.printSpike(printSpikeNum)
    '''

    #ba.printErrors()

    sd = ba.getSpikeDictionaries()
    '''
    for idx, s in enumerate(sd):
        if idx == 2:
            print(s)
    '''

if __name__ == '__main__':
    #test()
    #test2()
    printDocs()
