#!/usr/bin/env python

#unit tests for undo code

import unittest
import copy
import math

import gtk

import undo

class Status:
    def __init__(self):
        self.count = 0
        
class UndoTest(unittest.TestCase):
    def setUp(self):
        pass
    
    def tearDown(self):
        pass
        
    def wait(self):
        gtk.main()
        
    def quitloop(self,f,status):
        if status == 0:
            gtk.main_quit()

    def assertUndoStatus(self,undoer,should_undo,should_redo):
        self.assertEqual(undoer.can_undo(),should_undo)
        self.assertEqual(undoer.can_redo(),should_redo)
        self.assertExternalAndInternalStatusMatch(undoer)

    def assertExternalAndInternalStatusMatch(self,undoer):
        self.assertEqual(self.undo_cb_status.count, undoer.can_undo())
        self.assertEqual(self.redo_cb_status.count, undoer.can_redo())
        
    def testCreateUndoRedo(self):        
        status = Status()
        self.undo_cb_status = Status()
        self.redo_cb_status = Status()
        
        def inc_status(x):
            status.count += x
            
        def dec_status(x):
            status.count -= x

        def set_undoable(sequence,state):
            #print "undo_status: %s" % state
            self.undo_cb_status.count = state

        def set_redoable(sequence,state):
            #print "redo_status: %s" % state
            self.redo_cb_status.count = state
            
        self.assertEqual(status.count,0)
        self.assertEqual(self.undo_cb_status.count,0)
        self.assertEqual(self.redo_cb_status.count,0)
                
        # create sequence
        undoer = undo.Sequence()
        undoer.connect('can-undo',set_undoable)
        undoer.connect('can-redo',set_redoable)
        
        self.assertEqual(undoer.can_undo(),False)
        self.assertEqual(undoer.can_redo(),False)
        self.assertExternalAndInternalStatusMatch(undoer)
        
        self.assertEqual(undoer.pos,0)
        self.assertEqual(len(undoer.history),0)
        
        # perform an action
        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        
        self.assertEqual(status.count,1)
        self.assertUndoStatus(undoer,True,False)        
        self.assertEqual(len(undoer.history),1)
        self.assertEqual(undoer.pos,1)

        # undo it
        undoer.undo()

        # check externals
        self.assertEqual(status.count,0)
        self.assertUndoStatus(undoer,False,True)
        
        # check internals
        self.assertEqual(len(undoer.history),1)
        self.assertEqual(undoer.pos,0)
        
        # redo it
        undoer.redo()

        # check externals
        self.assertEqual(status.count,1)
        self.assertUndoStatus(undoer,True,False)
        
        # check internals
        self.assertEqual(len(undoer.history),1)
        self.assertEqual(undoer.pos,1)

    def testUndoAndRedoAreAssociated(self):
        status = Status()

        def inc_status(x):
            status.count += x
            
        def dec_status(x):
            status.count -= x

        def make_status_string(s):
            status.count = s

        def make_status_int(i):
            status.count = i
                    
        self.assertEqual(status.count,0)

        # create sequence
        undoer = undo.Sequence()
        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        make_status_string("foo")
        undoer.do(make_status_string, "foo", make_status_int, 1)

        self.assertEqual(status.count,"foo")
        undoer.undo()
        self.assertEqual(status.count,1)
        undoer.undo()
        self.assertEqual(status.count,0)

    def testDoRemovesRedoStack(self):
        status = Status()

        def inc_status(x):
            status.count += x
            
        def dec_status(x):
            status.count -= x
                    
        self.assertEqual(status.count,0)

        # perform 3 actions, undo 2, do 1 again
        undoer = undo.Sequence()
        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        undoer.undo()
        undoer.undo()
        self.assertEqual(status.count,1)

        inc_status(1)
        undoer.do(inc_status,1,dec_status,1)
        self.assertEqual(len(undoer.history),2)
        self.assertEqual(undoer.can_redo(), False)
        self.assertEqual(status.count,2)

        # undo again, check we're back where we started
        undoer.undo()
        self.assertEqual(status.count,1)
        
    def testInvalidOperationsThrow(self):
        undoer = undo.Sequence()
        self.assertRaises(ValueError,undoer.undo)
        self.assertRaises(ValueError,undoer.redo)
        
def suite():
    return unittest.makeSuite(UndoTest,'test')

if __name__ == '__main__':
    unittest.main(defaultTest='suite')
