import datetime
import time
import gobject

class Timer(gobject.GObject):
    (STATE_IDLE, STATE_RUNNING, STATE_PAUSED, STATE_FINISHED) = xrange(4)
    
    __gsignals__ = {'time-changed':
                        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
                    'state-changed':
                        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())}
    
    def __init__(self):
        gobject.GObject.__init__(self)
        self._state = Timer.STATE_IDLE
        self._duration_seconds = 0
        self._remaining_seconds = 0
        self._end_time = 0
        self._name = ''
    
    def set_duration(self, seconds):
        """Set the duration of the timer in seconds."""
        assert self._state == Timer.STATE_IDLE
        self._duration_seconds = seconds
        
    def get_duration(self):
        """Return the duration of the timer in seconds."""
        return self._duration_seconds
        
    def set_name(self, name):
        """Set the name of the timer."""
        assert self._state == Timer.STATE_IDLE
        self._name = name
    
    def get_name(self):
        """Return the name of the timer."""
        return self._name

    def start(self):
        """Start or resume the timer.
        
        This method should only be called when the timer is IDLE or PAUSED.
        
        """
        assert self._state == Timer.STATE_IDLE or self._state == Timer.STATE_PAUSED
        self._timer_transition_to_state(Timer.STATE_RUNNING)
        
    def stop(self):
        """Pause the timer.
        
        This method should only be called when the timer is RUNNING.
        
        """
        assert self._state == Timer.STATE_RUNNING
        self._timer_transition_to_state(Timer.STATE_PAUSED)

    def reset(self):
        """Reset the timer.
        
        This method should only be called when the timer is not IDLE.
        
        """
        assert self._state != Timer.STATE_IDLE
        self._timer_transition_to_state(Timer.STATE_IDLE)
        
    def get_state(self):
        """Return the current state."""
        return self._state
        
    def get_remaining_time(self):
        """Return the remaining time in seconds."""
        return max(0, self._remaining_seconds)
        
    def get_end_time(self):
        """Return a datetime object representing the end time.
        
        This method should only be called when the timer is RUNNING or FINISHED.
        
        """
        assert self._state == Timer.STATE_RUNNING or self._state == Timer.STATE_FINISHED
        return datetime.datetime.fromtimestamp(self._end_time)
    
    def _timer_set_state(self, state):
        self._state = state
        self.emit('state-changed')
        
    def _timer_transition_to_state(self, dest_state):
        cur_time = int(time.time())
        
        if dest_state == Timer.STATE_IDLE:
            self._end_time = 0
            self._set_remaining_time(self._duration_seconds)
        elif dest_state == Timer.STATE_RUNNING:
            assert self._duration_seconds >= 0
            
            if self._state == Timer.STATE_IDLE:
                self._end_time = cur_time + self._duration_seconds
                self._set_remaining_time(self._duration_seconds)
            elif self._state == Timer.STATE_PAUSED:
                self._end_time = cur_time + self._remaining_seconds
                
            gobject.timeout_add(500, self._on_timeout)
        elif dest_state == Timer.STATE_PAUSED:
            self._set_remaining_time(self._end_time - cur_time)
            self._end_time = 0
        elif dest_state == Timer.STATE_FINISHED:
            pass
        else:
            assert False
            
        self._timer_set_state(dest_state)

    def _on_timeout(self):
        if self._state != Timer.STATE_RUNNING:
            return False # remove timeout source
    
        new_remaining = self._end_time - int(time.time())
        if self._remaining_seconds != new_remaining:
            self._set_remaining_time(new_remaining)
        
        if self._remaining_seconds < 0:
            self._timer_transition_to_state(Timer.STATE_FINISHED)
            return False # remove timeout source
        return True # keep timeout source
        
    def _set_remaining_time(self, new_remaining):
        self._remaining_seconds = new_remaining
        self.emit('time-changed')
