#!/usr/bin/env python3
"""
2012.12.15 table-data stored in pytables.
i.e.
    reader = PyTablesMatrixFile(path=filename, mode='r')
    reader = PyTablesMatrixFile(filename, mode='r')
    for row in reader:
        ...
    tableObject = reader.getTableObject(tableName=tableName)
    for row in tableObject:
        ...
    
    dtypeList = [('locus_id','i8'),('chr', HDF5MatrixFile.varLenStrType), ('start','i8'), ('stop', 'i8'), \
                ('score', 'f8'), ('MAC', 'i8'), ('MAF', 'f8')]
    headerList = [row[0] for row in dtypeList]
    dtype = numpy.dtype(dtypeList)
    
    writer = PyTablesMatrixFile(path=filename, mode='w', dtype=dtype)
    writer = PyTablesMatrixFile(filename, mode='w', dtype=dtype)
    
    if writer:
        tableObject = writer.createNewTable(tableName=tableName, dtype=dtype)
        tableObject.setColIDList(headerList)
    elif outputFname:
        writer = PyTablesMatrixFile(outputFname, mode='w', dtype=dtype, tableName=tableName)
        writer.writeHeader(headerList)
        tableObject = writer.getTableObject(tableName=tableName)
    cellList = []
    for data_obj in self.data_obj_ls:
        dataTuple = self._extractOutputRowFromDataObject(data_obj=data_obj)
        cellList.append(dataTuple)
    tableObject.writeCellList(cellList)
    if closeFile:
        writer.close()
        del writer
    
    #each number below is counting bytes, not bits
    dtypeList = [('locus_id','i8'),('chromosome', HDF5MatrixFile.varLenStrType), ('start','i8'), ('stop', 'i8'), \
                ('score', 'f8'), ('MAC', 'i8'), ('MAF', 'f8')]
    if writer is None and filename:
        writer = PyTablesMatrixFile(filename, mode='w', dtypeList=dtypeList, tableName=tableName)
        tableObject = writer.getTableObject(tableName=tableName)
    elif writer:
        tableObject = writer.createNewTable(tableName=tableName, dtypeList=dtypeList)

"""

import sys, os
import csv
import tables
import numpy
from palos.utils import PassingData, PassingDataList
from palos.ProcessOptions import  ProcessOptions
from palos.io.MatrixFile import MatrixFile
from palos.io.HDF5MatrixFile import HDF5MatrixFile, YHTableInHDF5Group, addAttributeDictToYHTableInHDF5Group 
from tables.file import _checkfilters

class YHTable(tables.Table, YHTableInHDF5Group):
    """
    2012.12.16 API is very similar to HDF5MatrixFile
    2012.12.16 adapted from http://pytables.github.com/cookbook/simple_table.html
    """
    #mimics the sqlalchemy
    query = tables.Table.read_where
    
    def __init__(self, parentNode=None, tableName=None,\
                description=None, rowDefinition=None,\
                title='', filters=None, \
                expectedrows=52000, chunkshape=None, byteorder=None, **keywords):
        """
    description
          An IsDescription subclass or a dictionary where the keys are the field
          names, and the values the type definitions. In addition, a pure NumPy
          dtype is accepted.  And it can be also a recarray NumPy object,
          RecArray numarray object or NestedRecArray. If None, the table metadata
          is read from disk, else, it's taken from previous parameters.
    rowDefinition is a backup of description, to make the class compatible with HDF5MatrixFile
        """
        self.tableName = tableName
        self.parentNode = parentNode
        
        if tableName in self.parentNode: # existing table
            description = None
        elif description is None: # pull the description from the attrs
            if rowDefinition is not None:	#a backup of description
                description = rowDefinition
            else:
                description = dict(self._get_description())
        
        if filters is None:
            filters = tables.Filters(complib="blosc", complevel=5, shuffle=True)
        
        #YHTableInHDF5Group.__init__(self, newGroup=0)
        #if self.mode=='w':
        tables.Table.__init__(self, self.parentNode, tableName,
                        description=description, title=title,
                        filters=filters,
                        expectedrows=expectedrows,
                        _log=False, **keywords)
        
        self._c_classId = self.__class__.__name__
        
        self._processRowIDColID()

    def _processRowIDColID(self):
        """
        2012.12.16 similar to SNPData.processRowIDColID()
        """
        self.rowIDList = []
        self.rowID2rowIndex = {}
        self.colIDList = []
        self.colID2colIndex = {}
        for i in range(len(self.colnames)):
            colID = self.colnames[i]
            self.colIDList.append(colID)
            self.colID2colIndex[colID] = i
        
        self.no_of_rows = self.nrows	#a counter used in self.writeOneCell, self.nrows does not get updated until flush.
        
        
    
    def _get_description(self):
        # pull the description from the attrs
        for attr_name in dir(self):
            if attr_name[0] == '_':
                continue
            try:
                attr = getattr(self, attr_name)
            except:
                continue
            if isinstance(attr, tables.Atom):
                yield attr_name, attr

    def insert_many(self, data_generator, attr=False):
        row = self.row
        cols = self.colnames
        if not attr:
            for d in data_generator:
                for c in cols:
                    row[c] = d[c]
                row.append()
        else:
            for d in data_generator:
                for c in cols:
                    row[c] = getattr(d, c)
                row.append()
        self.flush()
    
    def setColIDList(self, colIDList=None, **keywords):
        """
        """
        pass
    
    def setRowIDList(self, rowIDList=None, **keywords):
        pass
    
    def writeHeader(self, headerList=None, tableIndex=None, tableName=None):
        """
        2012.11.16
            only the first group
        """
        pass
    
    def writeOneCell(self, oneCell=None, cellType=1):
        """
        #2013.1.9 make sure enough columns in oneCell
        2012.12.16
            each cell is either a tuple/list or object with column-name attributes
            cellType
                1: list or tuple, in the order of table columns, not including first "id" column
                2: object with attributes whose names are same as those of the table columns
                3: a dictionary with key=column-name, value=column-value, access by oneCell.get(colname, None)
                4: raw dictionary. access by oneCell[colname]
        """
        row = self.row
        for i in range(len(self.colnames)):	#assuming data in oneCell is in the same order as tableObject.colnames
            colname = self.colnames[i]
            if colname=='id':	#auto-increment the ID column if it exists
                self.no_of_rows += 1
                row[colname] = self.no_of_rows
            else:
                if cellType==1:	#list/tuple
                    if "id" in self.colinstances:	#colinstances maps the name of a column to its Column (see The Column class) 
                            #or Cols (see The Cols class) instance.
                        cellColIndex = i-1					#oneCell does not include id
                    else:	#no "id" column, so same index
                        cellColIndex = i
                    if cellColIndex<len(oneCell):	#2013.1.9 make sure enough columns in oneCell
                        value = oneCell[cellColIndex]
                    else:
                        continue
                elif cellType==2:	#object with attributes
                    value = getattr(oneCell, colname, None)
                elif cellType==3:	#dictionary
                    value = oneCell.get(colname, None)
                elif cellType==4:	#canonical dictionary
                    value = oneCell[colname]
                else:
                    value = None
                if value is not None:
                    row[colname] = value
        row.append()
    
    def appendOneRow(self, oneCell=None, cellType=1):
        """
        2013.1.9
        """
        self.writeOneCell(oneCell=oneCell, cellType=cellType)
    
    def writeCellList(self, cellList=None, cellType=1):
        """
        """
        for oneCell in cellList:
            self.writeOneCell(oneCell, cellType=cellType)
        self.flush()
    
    def getCellDataGivenRowColID(self, rowID=None, colID=None):
        """
        """
        pass
        """
        rowIndex = self.getRowIndex(rowID)
        colIndex = self.getColIndex(colID)
        
        cellData = None
        if rowIndex is not None and colIndex is not None:
            cellData = self.[rowIndex][colIndex]
        return cellData
        """
    
    def addAttribute(self, name=None, value=None, overwrite=True):
        """
        
        """
        if hasattr(self._v_attrs, name):
            sys.stderr.write("Warning: h5Group %s already has attribute %s=%s.\n"%(self.name, name, value))
            if overwrite:
                setattr(self._v_attrs, name, value)
            else:
                return False
        else:
            setattr(self._v_attrs, name, value)
        #pass the HDF5Group attributes to this object itself , it ran into "can't set attribute error". conflict with existing property
        #object.__setattr__(self, name, value)
        #setattr(self, name, value)
        return True
    
    def getAttribute(self, name=None, defaultValue=None):
        return getattr(self._v_attrs, name, defaultValue)
    
    def getAttributes(self):
        dc = {}
        for attributeName in self._v_attrs._f_list():
            dc[attributeName] = getattr(self._v_attrs, attributeName)
        return dc
    
    #2102.12.20 overwrite YHTableInHDF5Group.next()
    #next = tables.Table.next	tables.Table doesn't have next
    
    
    def close(self):
        """
        2012.12.20 do nothing
        """
        pass


class YHSingleTableFile(YHTable):
    """
    2012.12.16 API is very similar to HDF5MatrixFile
    2012.12.16 adapted from http://pytables.github.com/cookbook/simple_table.html
    """
    #mimics the sqlalchemy	
    def __init__(self, path=None, mode='r', \
                groupName=None, tableName=None,\
                description=None,
                title='', filters=None, rowDefinition=None,\
                expectedrows=512000, **keywords):
        """
        rowDefinition is backup of description, to make it compatible with HDF5MatrixFile
        """
        self.path = path
        self.mode = mode
        self.groupName = groupName
        self.tableName = tableName
        
        self.hdf5File = tables.openFile(path, mode)
        self.uservars = None
        
        if groupName is None:
            groupName = 'default'
        
        self.parentNode = self.hdf5File._getOrCreatePath('/' + groupName, True)
        
        YHTable.__init__(self, self.parentNode, tableName,
                        description=description, rowDefinition=rowDefinition, \
                        title=title,
                        filters=filters,
                        expectedrows=expectedrows,
                        _log=False, **keywords)
        
        self._c_classId = self.__class__.__name__
    
    def getTableObject(self, tableIndex=None, tableName=None, groupName=None):
        """
        2012.12.18 compatible with HDF5MatrixFile
        """
        return self

class transplant:
    """
    2012.12.20 from http://code.activestate.com/recipes/81732/
    """
    def __init__(self, method, host, method_name=None):
        self.host = host
        self.method = method
        setattr(host, method_name or method.__name__, self)	#calling method_name is effectively calling this transplant class

    def __call__(self, *args, **kwargs):
        nargs = [self.host]
        nargs.extend(args)
        return apply(self.method, nargs, kwargs)

def funcToMethod(func, newClass, method_name=None, instance=None):
    """
    2012.12.20 from http://code.activestate.com/recipes/81732/,
        to attach func to newClass with new method_name and instance
    """
    import new
    method = new.instancemethod(func, instance, newClass)
    if not method_name:
        method_name=func.__name__
    instance.__dict__[method_name]=method

#def turnTableIntoYHTable():
#	for functionName in ['getAttributes', 'getAttribute', 'addAttribute', 'addAttributeDict']:
#		function = getattr(YHTable, functionName)
#		funcToMethod(function, tables.Table, functionName)

#turnTableIntoYHTable()


def armTableObjectWithYHTableMethods(tableObject=None):
    """
    2012.12.20 function to arm a tableObject (read from a pytables file) with functions from YHTable and its parental class YHTableInHDF5Group
        almost like type-cast (python doesn't support type cast) 
    """
    import types
    for functionName in ['_processRowIDColID', 'addAttribute', 'addAttributeDict', 'addObjectAttributeToSet', \
                        'addObjectListAttributeToSet', 'close', 'constructColName2IndexFromHeader', \
                        'getAttribute', 'getAttributes',  'getCellDataGivenRowColID', \
                        'getColIndex', 'getColIndexGivenColHeader', \
                        'getListAttributeInStr', \
                        'getRowIndex', \
                        'writeOneCell', 'writeCellList', 'setColIDList', 'setRowIDList']:
        if functionName in tableObject.__dict__:	#make sure not overwriting tables.Table's own function
            continue 
        if functionName in YHTable.__dict__:	#YHTable has precedence
            ClassToBorrow = YHTable
        elif functionName in YHTableInHDF5Group.__dict__:
            ClassToBorrow = YHTableInHDF5Group
        else:
            continue
        function = ClassToBorrow.__dict__[functionName]
        
        #function = getattr(YHTable, functionName)
        ## use getattr() from above to get the function will result in this error:
        ###"TypeError: unbound method getAttribute() must be called with YHTable instance as first argument (got Table instance instead)"
        
        tableObject.__dict__[functionName] = types.MethodType(function, tableObject, tableObject.__class__)
        
        #transplant(function, tableObject)	#another way of doing this, won't work because these functions are still bound to their old class
        
        #function.im_class = tables.Table	#read-only attributes for bound methods
        #func.im_func=fun
        #function.im_self = tableObject
        #funcToMethod(function, tables.Table, functionName, instance=tableObject)
    tableObject._processRowIDColID()
    return tableObject

class YHFile(tables.File, HDF5MatrixFile):
    """
    2012.12.18 not ready. a pytables file that could contain multiple tables.
    2012.12.16 API is very similar to HDF5MatrixFile
    i.e.
    
    #open a file with a defined table structure
    writer = YHFile(self.outputFname, mode='w', rowDefinition=CountAssociationLocusTable)
    ...
    
    #read a file
    reader = YHFile(path, mode='r')
    ...
    
    #open a file without passing the table structure.
    writer = YHFile(self.outputFname, mode='w')
    # create a table with proper structure
    writer.createNewTable(rowDefinition=CountAssociationLocusTable)
    ...
    """
    def __init__(self, path=None, mode='r', \
                tableName=None, groupNamePrefix='group', tableNamePrefix='table',\
                rowDefinition=None, filters=None, expectedrows=500000, \
                autoRead=True, autoWrite=True, \
                debug=0, report=0, **keywords):
        self.path = path
        self.header = None
        self.mode = mode
        self.tableName = tableName
        self.groupNamePrefix = groupNamePrefix
        self.tableNamePrefix = tableNamePrefix
        self.rowDefinition = rowDefinition
        self.expectedrows = expectedrows
        self.autoRead = autoRead	#whether to automatically read in data if mode is in reading mode
        self.autoWrite = autoWrite	#whether to automatically create table objects if mode is in writing mode
        
        self.debug = debug
        self.report = report
        
        self.combinedColIDList = None	#same as header
        self.combinedColID2ColIndex = None
        
        #to get table objects fast, without retrieving them from their parental groups.
        self.tableObjectList = []
        self.tablePath2Index = {}
        
        #self.hdf5File = tables.openFile(self.path, self.mode)
        #self.root = self.hdf5File.root
        if filters is None:
            filters = tables.Filters(complib="blosc", complevel=5, shuffle=True)
        
        tables.File.__init__(self, self.path, mode=self.mode, title='', rootUEP='/', filters=filters,\
                            **keywords)
        
        if self.mode=='r' or self.mode=='a':
            self._setupCombinedColumnIDMapper()
            if self.autoRead:
                self._readInData(tableName=self.tableName)
        
        if self.autoWrite and self.mode=='w' and self.rowDefinition is not None:
            self.createNewTable(tableName=self.tableName, rowDefinition=self.rowDefinition, expectedrows=self.expectedrows)
        
        self._c_classId = self.__class__.__name__
    
    def _createNewGroup(self, groupName=None, **keywords):
        """
        2012.12.16 this would also create a new table object within the newly-created group
            return tableObject
            ** deprecated
        """
        if groupName is None or self.root.__contains__(groupName):
            groupName = self._getNewGroupName()
        groupObject = self.createGroup(self.root, groupName)
        return groupObject
    
    def _appendNewTable(self, tableObject=None):
        """
        2012.12.20
        """
        if tableObject is not None:
            tablePath = tableObject._v_pathname	##the tableName preceds with "/"
            if tablePath in self.tablePath2Index:	 
                sys.stderr.write("ERROR, table %s already in self.tablePath2Index, index=%s.\n"%(tablePath,\
                                                                            self.tablePath2Index.get(tablePath)))
                sys.exit(3)
            self.tablePath2Index[tablePath] = len(self.tablePath2Index)
            self.tableObjectList.append(tableObject)
    
    def _fetchTableObject(self, tablePath):
        """
        2012.12.20
        """
        tableObject = None
        if tablePath in self.tablePath2Index:
            tableObject = self.tableObjectList[self.tablePath2Index.get(tablePath)]
        return tableObject
    
    def createNewTable(self, tableName=None, rowDefinition=None, title="",
                    filters=None, expectedrows=50000,
                    chunkshape=None, byteorder=None):
        """
        2012.12.20 this would also create a new group object if no group exists.
            symlink to createTable()
        """
        groupObject = self._getGroupObject()
        if not groupObject:	#create a new one if it's not available.
            groupName = self._getNewGroupName()
            groupObject = self.createGroup(self.root, groupName)
        
        if tableName is None:
            tableName = self._getNewTableName(groupObject=groupObject)
        if rowDefinition is None:
            rowDefinition = self.rowDefinition
        return self.createTable(where=groupObject, tableName=tableName, description=rowDefinition, \
                            rowDefinition=rowDefinition, title=title, filters=filters, expectedrows=expectedrows, \
                            chunkshape=chunkshape, byteorder=byteorder, createparents=False)
    
    def createTable(self, where=None, tableName=None, description=None, rowDefinition=None, title="",
                    filters=None, expectedrows=50000,
                    chunkshape=None, byteorder=None,
                    createparents=False):
        """
        2012.12.20 overwrite the parent (stock) function
        
        Create a new table with the given name in where location.

        Parameters
        ----------
        where : str or Group
            The parent group from which the new table will hang. It can be a
            path string (for example '/level1/leaf5'), or a Group instance
            (see :ref:`GroupClassDescr`).
        name : str
            The name of the new table.
        description : Description
            This is an object that describes the table, i.e. how
            many columns it has, their names, types, shapes, etc.  It
            can be any of the following:

                * *A user-defined class*: This should inherit from the
                  IsDescription class (see :ref:`IsDescriptionClassDescr`) where
                  table fields are specified.
                * *A dictionary*: For example, when you do not know beforehand
                  which structure your table will have).
                * *A Description instance*: You can use the description
                  attribute of another table to create a new one with the same
                  structure.
                * *A NumPy dtype*: A completely general structured NumPy dtype.
                * *A NumPy (structured) array instance*: The dtype of this
                  structured array will be used as the description.  Also, in
                  case the array has actual data, it will be injected into the
                  newly created table.
                * *A RecArray instance (deprecated)*: Object from the numarray
                  package.  This does not give you the possibility to create a
                  nested table.  Array data is injected into the new table.
                * *A NestedRecArray instance (deprecated)*: If you want to have
                  nested columns in your table and you are using numarray, you
                  can use this object. Array data is injected into the new
                  table.

        title : str
            A description for this node (it sets the TITLE HDF5 attribute
            on disk).
        filters : Filters
            An instance of the Filters class (see :ref:`FiltersClassDescr`) that
            provides information about the desired I/O filters to be applied
            during the life of this object.
        expectedrows : int
            A user estimate of the number of records that will be in the table.
            If not provided, the default value is EXPECTED_ROWS_TABLE (see
            :file:`tables/parameters.py`). If you plan to create a bigger table
            try providing a guess; this will optimize the HDF5 B-Tree creation
            and management process time and memory used.
        chunkshape
            The shape of the data chunk to be read or written in a single HDF5
            I/O operation. Filters are applied to those chunks of data. The rank
            of the chunkshape for tables must be 1. If None, a sensible value
            is calculated based on the expectedrows parameter (which is
            recommended).
        byteorder : str
            The byteorder of data *on disk*, specified as 'little' or 'big'.
            If this is not specified, the byteorder is that of the platform,
            unless you passed an array as the description, in which case
            its byteorder will be used.
        createparents : bool
            Whether to create the needed groups for the parent path to exist
            (not done by default).

        See Also
        --------
        Table : for more information on tables

        """
        if rowDefinition is None:
            rowDefinition = self.rowDefinition
        if description is None:	#2012.12.20 rowDefinition is backup of description
            description = rowDefinition
        parentNode = self._getOrCreatePath(where, createparents)
        _checkfilters(filters)
        tableObject =  YHTable(parentNode=parentNode, tableName=tableName,
                     description=description, title=title,
                     filters=filters, expectedrows=expectedrows,
                     chunkshape=chunkshape, byteorder=byteorder)
        if tableObject._v_pathname not in self.tablePath2Index:
            self._appendNewTable(tableObject)
        else:
            sys.stderr.write("Error: table path name %s already in this PyTables file.\n"%(tableObject._v_pathname))
            sys.exit(3)
        return tableObject
    
    def _getNewGroupName(self, groupNamePrefix=None):
        """
        2012.11.19
        """
        if not groupNamePrefix:
            groupNamePrefix = self.groupNamePrefix
        i = len(self.root._v_groups)
        groupName = "%s%s"%(groupNamePrefix, i)
        while self.root.__contains__(groupName):	#stop until a unique name shows up
            i += 1
            groupName = "%s%s"%(groupNamePrefix, i)
        return groupName

    def _getNewTableName(self, tableNamePrefix=None, groupObject=None):
        """
        2012.12.15
        """
        if groupObject is None:
            groupObject = self._getGroupObject()
        if self.tableName:
            tableName = self.tableName
        else:
            if not tableNamePrefix:
                tableNamePrefix = self.tableNamePrefix
            i = len(groupObject._v_leaves)
            tableName = "%s%s"%(tableNamePrefix, i)
        while groupObject.__contains__(tableName):	#stop until a unique name shows up
            i += 1
            tableName = "%s%s"%(tableNamePrefix, i)
        return tableName
    
    def _readInData(self, tableName=None, tableObject=None):
        """
        2012.12.16
        """
        pass
    
    def _setupCombinedColumnIDMapper(self,):
        """
        2012.12.16
            use the first table only
        """
        self.combinedColID2ColIndex = {}
        tableObject = self.getTableObject()
        self.header = []
        self.combinedColIDList = self.header
        for colID in tableObject.colnames:
            if colID in self.combinedColID2ColIndex:
                sys.stderr.write("Error: column ID %s already used in column %s.\n"%(colID, self.combinedColID2ColIndex.get(colID)))
                sys.exit(3)
            else:
                self.combinedColID2ColIndex[colID] = len(self.combinedColID2ColIndex)
                self.header.append(colID)
        return self.combinedColID2ColIndex
    """
    def constructColName2IndexFromHeader(self):
        #2012.11.22 overwrite parent function
        return self.combinedColID2ColIndex
    
    def getColIndexGivenColHeader(self, colHeader=None):
        #2012.11.15
        #	this is from the combined column header list.
        return self.combinedColID2ColIndex.get(colHeader)
    """
    
    def _getGroupObject(self, groupIndex=None, groupName=None):
        """
        """
        groupObject = None
        if groupName:
            groupObject = self.getNode("/", name=groupName, classname='Group')
        else:	#return first group
            if len(self.root._v_groups.values())>0:
                groupObject = self.root._v_groups.values()[0]	#first group
        return groupObject
    
    def getTableObject(self, tableIndex=None, tableName=None, groupName=None):
        """
        2012.12.16
        """
        if tableName is None and self.tableName:
            tableName = self.tableName
        if tableIndex is None and tableName is None and self.tableObjectList:	#return the 1st one.
            return self.tableObjectList[0]
        groupObject = self._getGroupObject(groupIndex=None, groupName=groupName)
        tableObject = None	
        if tableName:
            tablePath = groupObject._v_pathname + '/' + tableName
            if tablePath in self.tablePath2Index:	#already cached
                return self._fetchTableObject(tablePath)
            else:
                tableObject = groupObject._f_getChild(tableName)
        else:
            tableObject = groupObject._v_leaves.values()[0]	#first leaf
        if not hasattr(tableObject, 'getAttribute'):
            tableObject = armTableObjectWithYHTableMethods(tableObject)
            self._appendNewTable(tableObject)
        return tableObject
    
    @property
    def nrows(self):
        """
        2012.12.21
        """
        tableObject = self.getTableObject()
        return tableObject.nrows
    
    def __iter__(self):
        """
        2012.12.23
        don't just "return self" and remove the next() because using "yield" is making a generator itself
            i.e.
            for row in yhFile:
                print row
            
            Python first calls obj=yhFile.__iter__(), then every "for" step, it calls next(obj).
             
            In this __iter__(), "yield something" returns a newObj = iter(something)
                (AKA a generator), the in each "for" step, python calls next(newObj).
        2012.12.23 have to overwrite tables.File's __iter__() otherwise it's returning hdf5 group objects (/, /group0, etc.) iteratively
        return self
    
    def next(self):
        2012.12.20 iterate through just the first table
        
        """
        
        row = None
        pdata = PassingDataList()
        tableObject = self.getTableObject(tableName=self.tableName)
        for row in tableObject:
            try:
                yield row	#yield row returns a generator
            except:
                raise StopIteration
        """
        #2012.12.16
        #	go through each leaf (table & array), iteratively.
        #	usually the file has only one leaf (=table)
        for groupObject in self.walkGroups("/"):
            for leafNode in  groupObject._f_walkNodes('Leaf'):
                for row in leafNode:
                    yield row
        """
        """
            for colID  in self.header:	#iteration over header is in the same order as the ascending order of colIndex
                colIndex = self.combinedColID2ColIndex.get(colID)
                if colIndex < len(row):
                    setattr(pdata, colID, row[colIndex])
        self.rowIndexCursor += 1
        return pdata
        """
    
    def writeHeader(self, headerList=None, tableIndex=None, tableName=None):
        """
        2012.11.16
            only the first group
        """
        pass
    
    def writeOneCell(self, oneCell=None, tableIndex=None, tableName=None, cellType=1):
        """
        2012.12.16
            mimic csv's writerow()
            cellType
                1: list or tuple, in the order of table columns, not including first "id" column
                2: object with attributes whose names are same as those of the table columns
                3: a dictionary with key=column-name, value=column-value
        """
        tableObject = self.getTableObject(tableIndex=tableIndex, tableName=tableName)
        tableObject.writeOneCell(oneCell, cellType=cellType)
        """
        row = tableObject.row
        for i in range(len(tableObject.colnames)):	#assuming data in oneCell is in the same order as tableObject.colnames
            colname = tableObject.colnames[i]
            row[colname] = oneCell[i]
        row.append()
        """
    
    def appendOneRow(self, oneCell=None, tableIndex=None, tableName=None, cellType=1):
        """
        2013.1.9
        """
        self.writeOneCell(oneCell=oneCell, tableIndex=tableIndex, tableName=tableName, cellType=cellType)
    
    def writeCellList(self, cellList=None, tableIndex=None, tableName=None, cellType=1):
        """
        """
        tableObject = self.getTableObject(tableIndex=tableIndex, tableName=tableName)
        tableObject.writeCellList(cellList, cellType=cellType)
        """
        for oneCell in cellList:
            self.writeOneCell(oneCell, tableIndex=tableIndex, tableName=tableName)
        tableObject.flush()
        """
    
    def setColIDList(self, colIDList=None, tableIndex=None, tableName=None):
        """
        """
        pass
        
    def setRowIDList(self, rowIDList=None, tableIndex=None, tableName=None):
        """
        """
        pass

def castPyTablesRowIntoPassingData(rowPointer=None):
    """
    2012.12.21 rowPointer from PyTables iteration is like a C pointer to the current row (no real content).
        need to convert it to a real object if you try to store its content in memory and use it later. 
    
    rowPointer has these methods: 
        ['__class__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__format__', '__getattribute__', 
        '__getitem__', '__hash__', '__init__', '__iter__', '__new__', '__next__', '__pyx_vtable__', '__reduce__', 
        '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 
        '_fillCol', '_flushBufferedRows', '_flushModRows', '_getUnsavedNrows', '_iter', 'append', 'fetch_all_fields', 
        'next', 'nrow', 'table', 'update']
        
        However, errors like this crop up during copying of these rowPointer. 

  File "/usr/lib/python2.7/copy_reg.py", line 93, in __newobj__
    return cls.__new__(cls, *args)
  File "tableExtension.pyx", line 706, in tables.tableExtension.Row.__cinit__ (tables/tableExtension.c:6910)
TypeError: __cinit__() takes exactly 1 positional argument (0 given)

    2012.12.21 could not use PassingDataList. because of these errors.

  File "/usr/lib/python2.7/copy.py", line 257, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python2.7/copy.py", line 182, in deepcopy
    rv = reductor(2)
TypeError: 'NoneType' object is not callable

    """
    pdata = PassingData()	
    for colname in rowPointer.table.colnames:
        setattr(pdata, colname, rowPointer[colname])
    return pdata


def castPyTablesEntryIntoPassingData(entry=None):
    """
    2013.3.11 entry is one cell in the array,that is returned from readWhere() query.
        The array is a numpy data structure: array([(1L, '1', '', 2)], 
            dtype=[('id', '<u8'), ('name', '|S512'), ('scientific_name', '|S512'), ('ploidy', '<u2')])
        
    """
    pdata = PassingData()
    for i in range(len(entry.dtype.names)):
        colname = entry.dtype.names[i]
        setattr(pdata, colname, entry[i])
    return pdata