"""

There is one State object per reftool instantiation.  It contains all the
state such as the list of tests, etc.

"""

import sys
import tests, actions
from path import path
from oath.progress import progress_meter
from oath.msgbox import msgbox
from oath.viewer import viewer
from oath.table import TableDataSource, Table
from oath.choicebox import choose_from
import oath.window
import difflib

class State ( TableDataSource ):

    def __init__ (self, scr, onlydiffs):
        self.scr = scr # an oath window
        self.tests = tests.gather_tests (scr, onlydiffs, path ("."))

        # Determine maximum number of categories
        self.maxcategory = 0
        for test in self.tests:
            if len (test.categories) > self.maxcategory:
                self.maxcategory = len (test.categories)
                pass
            pass

        # Sorting order:
        #  this is the order in which to consider the keys.  So if this is
        #  1 0 2, then we consider category 1, then 0, then 2.
        self.sort_order = list (range (self.maxcategory))

        self.sort_by (1)

        self.currow = 0

        pass

    # --------------------------------------------------
    # Data Source

    def get_num_rows (self):

        """ Number of rows in the table """

        return len (self.tests)

    def get_num_cols (self):

        """ Number of cols in each row of the table;
        we have one for each category, and one more for the action
        associated with it """

        return self.maxcategory + 1

    def get_cell (self, row, col):

        """ Data at coordinates row,col """

        test = self.tests[row]
        
        if col < self.maxcategory:
            return test.column (col)

        if test.action: return test.action.character
        return " "

    def get_cell_attr (self, row, col):

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

        if row == self.currow:
            return oath.window.A_BOLD
        return oath.window.A_NORMAL

    #if row == self.currow:
    #return self.scr.color ('SELECTED')
    #return self.scr.color ('NORMAL')

    def row_down (self):
        self.currow += 1
        if self.currow >= len (self.tests):
            self.currow = 0
            pass
        self.table.center_on (self.currow)
        return

    def row_up (self):
        self.currow -= 1
        if self.currow < 0:
            self.currow = len (self.tests) - 1
            pass
        self.table.center_on (self.currow)
        return

    def page_down (self):
        self.currow += self.scr.height >> 1
        if self.currow >= len (self.tests):
            self.currow = len (self.tests)-1
            pass
        self.table.center_on (self.currow)
        return

    def page_up (self):
        self.currow -= self.scr.height >> 1
        if self.currow < 0:
            self.currow = 0
            pass
        self.table.center_on (self.currow)
        return


    # --------------------------------------------------
    # Main Menu

    def main (self):

        """ Takes control """

        if not self.tests:
            msgbox (self.scr, "No tests found.")
            return

        # The main loop displays a table of the tests:
        self.table = Table (self.scr, -1, -1, 0, 0)
        self.table.set_data_source (self)
        keymap = {
            oath.window.KEY_DOWN: self.row_down,
            oath.window.KEY_UP: self.row_up,
            oath.window.KEY_NPAGE: self.page_down,
            oath.window.KEY_PPAGE: self.page_up,
            ord ('n'): self.row_down,
            ord ('p'): self.row_up,
            ord ('u'): self.update_ref,
            ord ('z'): self.clear_ref,
            ord ('1'): self.sort_by_1,
            ord ('2'): self.sort_by_2,
            ord ('3'): self.sort_by_3,
            ord ('4'): self.sort_by_4,
            ord ('5'): self.sort_by_5,
            ord ('6'): self.sort_by_6,
            ord ('7'): self.sort_by_7,
            ord ('8'): self.sort_by_8,
            ord ('9'): self.sort_by_9,
            ord ('d'): self.diff, 
            ord ('v'): self.view_files, 
            ord ('q'): None
            }
        while 1:
            try:
                self.table.interactive (keymap, False)
            except KeyboardInterrupt:
                continue
            break
        self.table = None

        self.execute ()

        pass

    # --------------------------------------------------
    # display differences

    def diff (self):

        # Allow the user to move back and forth between tests while in
        # diff mode with the 'n' and 'p' keys, or view related files
        xtracmds = {
            ord('n'):self.row_down,
            ord('p'):self.row_up,
            ord('u'): self.update_ref,
            ord('z'): self.clear_ref,
            ord('v'):self.view_files
            }
        res = 1
        while res:
            difftext = self.tests[self.currow].compute_diff ()
            res = viewer (self.scr,
                    difftext,
                    0, 0, self.scr.height, self.scr.width,
                    xtracmds)
            if res: res()
            pass
        pass

    # --------------------------------------------------
    # view files

    def view_files (self):
        curtest = self.tests[self.currow]
        filelist = curtest.associated_paths + [curtest.out_path,
                                               curtest.ref_path]
        choices = [ (f,f) for f in filelist if f.exists() or 1 ]
        viewfile = choose_from (self.scr, 'View Which File', choices)
        fdata = viewfile.bytes ()
        viewer (self.scr, fdata, 0, 0, self.scr.height, self.scr.width, None)
        pass

    # --------------------------------------------------
    # Sorting
    
    def sort_by_1 (self):
        return self.sort_by (1)

    def sort_by_2 (self):
        return self.sort_by (2)

    def sort_by_3 (self):
        return self.sort_by (3)

    def sort_by_4 (self):
        return self.sort_by (4)

    def sort_by_5 (self):
        return self.sort_by (5)

    def sort_by_6 (self):
        return self.sort_by (6)

    def sort_by_7 (self):
        return self.sort_by (7)

    def sort_by_8 (self):
        return self.sort_by (8)

    def sort_by_9 (self):
        return self.sort_by (9)

    def sort_by (self, catidx):

        """ Given a category, reorders the list of tests so that they
        are sorted according to that category """

        catidx -= 1
        
        if catidx < self.maxcategory:

            # promote the given category to the front of the list
            self.sort_order.remove (catidx)
            self.sort_order.insert (0, catidx)

            # create a sorting array with the data ordered according to
            # priority
            sortable = []
            for test in self.tests:
                entry = [ test.column (cat) for cat in self.sort_order ]
                entry.append (test)
                sortable.append (entry)
                pass
            sortable.sort ()

            # extract the test back out from the sorting array
            self.tests = [ entry[-1] for entry in sortable ]
            pass
        pass
    
    # --------------------------------------------------
    # Creation of Actions

    def update_ref (self):
        curtest = self.tests[self.currow]
        curtest.set_action (actions.UpdateRefAction (curtest))
        self.row_down ()
        return

    def clear_ref (self):
        curtest = self.tests[self.currow]
        curtest.set_action (None)
        self.row_down ()
        return

    # --------------------------------------------------
    # Execution of Actions

    def _execute (self, errors, actions):

        idx = 0
        actlen = len (actions)
        for act in actions:
            try:
                act.execute ()
            except:
                errors.append ("Could not %s: %s"
                               % (act, sys.exc_info()[1]))
                pass
            yield (idx, actlen)
            idx += 1
            pass
        pass

    def execute (self):

        """ Executes and clears the actions on all tests in our system """

        errors = []
        actions = [ test.action for test in self.tests if test.action ]
        
        if actions:
            progress_meter (self.scr, 30,
                            "Updating ref files",
                            self._execute (errors, actions))
            pass

        if errors:
            viewer (self.scr,
                    "Errors Occurred:\n(Hit Q when done)\n\n%s"
                    % "\n".join (errors),
                    0, 0, self.scr.height, self.scr.width,
                    None)
            pass

        return

    pass

def _main (scr):
    onlydiffs = True
    if "-a" in sys.argv: onlydiffs = False
    state = State (scr, onlydiffs)
    return state.main ()

def main ():
    oath.window.start (_main)
    #oath.window.start (_main,
    #                   colors={'NORMAL':(oath.window.COLOR_WHITE,
    #                                     oath.window.COLOR_BLACK),
    #                           'SELECTED':(oath.window.COLOR_WHITE,
    #                                       oath.window.COLOR_GREEN)})
    pass
