""" Draws a table with rows and columns and allows scrolling etc """

import sys
import window 

debug_file = None

class Table:

    """ Allows you to manage a table with rows.  To use it, you may
    need to overload this object and override certain methods like
    get_row_height() or (most often) get_col_widths().

    At a minimum, however, you have to create data in the self.data
    array.  This should be a list of lists, each item in the outer
    list representing a row and each item in the innter list
    representing a column in that row.  Each row should have same
    number of columns.

    By default each row is given equal spacing; if you want to change
    that, you must overload get_col_widths().  The cols are separated
    by the string self.colsep, which you can modify to your hearts
    delight. """

    def __init__ (self, win, height, width, y, x):
        self.data = SimpleTableDataSource ( (), ((0,)))  # bogus default source
        self.toprow = 0
        if height > 0: self.dispheight = height
        else: self.dispheight = win.height - 1
        if width > 0: self.dispwidth = width
        else: self.dispwidth = win.width - 1
        self.win = win
        self.x = x
        self.y = y
        self.colsep = " | "
        pass

    # Things you can call when data is ready or changes:

    def set_col_separator (self, sep):
        self.colsep = sep
        pass

    def set_data_source (self, source):

        """ Sets the data sources associated with the table.  You must
        call update_data() before you display the table to get the new
        data. """
        
        self.data = source
        pass

    # Typical interactive selection:

    def interactive (self, keymap, adddefaultkeys):

        """ A simple interactive loop that displays the table and waits
        for key presses.  The keys in the keymap should map to functions
        to call, or None to indicate that interactive() should return """

        if adddefaultkeys:
            keymap[window.KEY_DOWN] = self.row_down
            keymap[window.KEY_UP] = self.row_up
            keymap[window.KEY_NPAGE] = self.page_down
            keymap[window.KEY_PPAGE] = self.page_up
            keymap[ord('q')] = None
            pass

        while 1:
            self.display ()
            ch = self.win.getch ()
            try:
                func = keymap [ ch ]
                if not func: return
                func ()
            except KeyError: pass
            pass

        pass

    # Displaying:

    def display (self):

        """ Draws the table at the given position in the window.  Uses
        the current row to determine which portion to fit.  The table
        will be drawn in a rectangle with its left hand corner at
        (y,x) of size (height,width). """

        win = self.win
        x = self.x
        y = self.y

        # Extract some useful stuff and store it somewhere handy
        height = self.dispheight
        width = self.dispwidth
        numrows = self.data.get_num_rows ()
        numcols = self.data.get_num_cols ()
        
        # Decide whether to put table headers and adjust height of table
        # as a result
        headers = self.data.get_col_headers ()
        if headers:
            assert len (headers) == numcols
            height -= 1
            pass

        # Extract dimension information of table from data source:
        sepspace = (numcols - 1) * len (self.colsep)
        colwidths = self.data.get_col_widths (width - sepspace)

        # Compute top row to display.  Remember to wrap toprow if needed,
        # and then ensure that we don't display data beyond the end of the
        # table.
        toprow = self.toprow
        if toprow >= numrows:
            if numrows: toprow = toprow % numrows
            else: toprow = 0
            pass
        while toprow < 0: toprow += numrows

        # Display headers if any
        initx = x
        col = 0
        for header in headers:
            colwidth = colwidths[col]
            header = win.str (header)
            if len (header) > colwidth: header = header[:colwidth]
            if col and self.colsep:
                win.addstr (y, x, self.colsep, window.A_NORMAL)
                x += len (self.colsep)
                pass
            win.addstr (y, x, " " * colwidth)        # clear first 
            win.addstr (y, x, header, window.A_BOLD|window.A_UNDERLINE)
            x += colwidth
            col += 1
            pass
        if headers: y += 1

        # Update data for all rows on the display
        x = initx
        for row in range (toprow, toprow+height):
            for col in range (numcols):
                # Obtain string data and display attr for cell,
                # truncating as needed:
                colwidth = colwidths[col]
                if row < numrows:
                    coldata = win.str (self.data.get_cell (row, col))
                    if len (coldata) > colwidth: coldata = coldata[:colwidth]
                    attr = self.data.get_cell_attr (row, col)
                    pass
                else:
                    coldata = ""   # otherwise empty string!
                    attr = window.A_NORMAL
                    pass

                # Display a separator before the cell if needed:
                if col and self.colsep:
                    if debug_file:
                        debug_file.write ("sep: col=%d y=%d x=%d sep=%s\n"%
                                          (col,y,x,self.colsep))
                        pass
                    win.addstr (y, x, self.colsep, window.A_NORMAL)
                    x += len (self.colsep)
                    pass

                # Display the string
                win.addstr (y, x, " " * colwidth)  # clear first
                win.addstr (y, x, coldata, attr)
                x += colwidth
                pass
            y += 1
            x = initx
            pass

        # Delete the attribute we created during this display to ensure
        # they are not accidentally used elsewhere:
        pass

    # Controlling which data is displayed:

    def row_down (self):
        self.toprow += 1
        pass

    def row_up (self):
        self.toprow -= 1
        pass

    def page_down (self):
        self.toprow += self.dispheight >> 1
        pass

    def page_up (self):
        self.toprow -= self.dispheight >> 1
        pass

    def center_on (self, idx):
        hh = self.dispheight / 2
        if debug_file: debug_file.write ('center_on: idx:%d hh:%d\n' %
                                         (idx,hh))

        self.toprow = idx - hh
        numrows = self.data.get_num_rows()
        if self.toprow + self.dispheight >= numrows:
            self.toprow = numrows - self.dispheight
            pass
        if self.toprow < 0: self.toprow = 0
        pass

    pass

class TableDataSource:

    """ A mostly abstract data source.  This illustrates the data
    source API for tables.  There are other data sources around, or
    you can craft your own, but this one will remain the canonical
    definition of the interface.

    Note that these methods must be cheap as they are called repeatedly
    while the table is being drawn. """

    # Methods with abstract implementations

    def get_num_rows (self):

        """ Number of rows in the table """
        
        return abstract

    def get_num_cols (self):

        """ Number of cols in each row of the table """
        
        return abstract

    def get_cell (self, row, col):

        """ Data at coordinates row,col """
        
        return abstract

    # Methods with default implementations
    
    def get_col_headers (self):

        """ If this returns a non-empty list, the items will be displayed
        as headers on top of the table.  Default is no headers. """

        return ()

    def get_cell_attr (self, row, col):

        """ Determines how the cell is displayed """

        return window.A_NORMAL

    def get_col_widths (self, availwidth):

        """ Returns a list of widths.  The length of this list MUST be
        get_num_cols(); this should be derived from self.data, which
        should be filled in by now.  The default impl chooses equally
        sized columns.  

        The parameter 'availwidth' is the number of characters you
        have in total to work with.  The sum of all the widths you
        return should equal 'availwidth'.

        This method should be overloaded to provide custom widths for
        various columns. """

        numcols = self.get_num_cols ()
        indcolwidth = availwidth / numcols
        res = [indcolwidth] * (numcols)    # repeat for each column
        res[0] += (availwidth % numcols)   # give any extra space to first col
        
        if debug_file: debug_file.write ('get_col_widths: %s\n'%
                                         repr(res))
        
        return res

class SimpleTableDataSource (TableDataSource):

    """ A simple data source that is parameterized by a self.data,
    which is a list of lists, one for each row in the table, and a
    list of headers. """

    def __init__ (self, headers, data):
        self.data = data
        self.headers = headers
        pass

    def get_num_rows (self):

        """ Number of rows in the table """
        
        return len (self.data)

    def get_num_cols (self):

        """ Number of cols in each row of the table """
        
        return len (self.data[0])

    def get_col_headers (self):

        """ If this returns a non-empty list, the items will be displayed
        as headers on top of the table """

        return self.headers

    def get_cell (self, row, col):

        """ Data at coordinates row,col """
        
        return self.data[row][col]

    def get_cell_attr (self, row, col):

        """ Determines how the cell is displayed """

        return window.A_NORMAL

    pass

def test (win):
    rows = win.height/2
    cols = win.width-1
    table = Table (win, rows, cols, rows//2, 0)
    data = []
    for i in range (200):
        data.append ( (i, 'Data #%d' % i) )
        pass
    datasrc = SimpleTableDataSource (('One', 'Two'), data)
    table.set_data_source (datasrc)
    table.set_col_separator (" : ")  # for fun
    table.interactive ({}, True)
    pass
    
    
if __name__ == "__main__":
    debug_file = open ('x.debug.out', 'w')
    
    window.start (test)
    pass
