# Copyright (C) 2008 LottaNZB Development Team
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

import sys
import types

import logging
log = logging.getLogger(__name__)

from lottanzb.util import Thread, GObject, gproperty, _
from lottanzb.config import GConfigSection
from lottanzb.modes import standalone, local_frontend, remote_frontend

mode_list = [standalone.Mode, local_frontend.Mode, remote_frontend.Mode]

from lottanzb.modes.ui import SelectionDialog
from lottanzb.modes.base import Mode

class Config(GConfigSection):
    """Represents the configuration section 'modes'"""
    
    # This section only contains a single option: The string representation of
    # the currently active usage mode, as defined by each Mode's name.
    active = gproperty(type=str)
    
    # Subsections. Every single mode has its own place to store config data.
    standalone = gproperty(type=object)
    local_frontend = gproperty(type=object)
    remote_frontend = gproperty(type=object)
    
    def set_property(self, key, value):
        """Ensures that the 'active' option never holds an invalid value."""
        
        if key == "active" and value:
            # Makes it possible to directly assign a Mode based class or an
            # instance of it to the 'active' option.
            try:
                value = value.get_name()
            except:
                pass
            
            if not value in self:
                # Resetting the mode name is probably the better idea than
                # raising an exception.
                value = ""
        
        GConfigSection.set_property(self, key, value)

class ModeManager(GObject):
    active_mode = gproperty(type=object)
    
    def __init__(self, config):
        self.config = config
        
        GObject.__init__(self)
    
    def show_selection_window(self, error_message="", on_cancel_warning=""):
        if error_message: 
            log.error(error_message)
        
        def handle_closed(*args):
            if not self.active_mode:
                if on_cancel_warning:
                    log.warning(on_cancel_warning)
                
                sys.exit(1)
        
        dialog = SelectionDialog(self, error_message)
        dialog.toplevel.connect("destroy", handle_closed)
        dialog.show()
    
    def set_mode(self, mode, on_success=None, on_failure=None):
        def handle_completed(thread):
            if thread.exception:
                if callable(on_failure):
                    on_failure(thread.exception)
                
                self.reenter_mode()
            else:
                self.active_mode = mode
                
                if mode:
                    self.config.active = mode
                    self.config[self.config.active] = mode.config
                
                if callable(on_success):
                    on_success()
        
        thread = ModeActionThread(mode, old_mode=self.active_mode)
        thread.connect("completed", handle_completed)
        thread.start()
    
    def reenter_mode(self):
        self.set_mode(self.active_mode)
    
    def leave_mode(self):
        self.set_mode(None)
    
    def load(self):
        if self.config.active:
            cls = get_mode_class(self.config.active)
            mode = cls(self.config[self.config.active])
            
            if mode.init_error:
                self.show_selection_window()
            else:
                def on_failure(exception):
                    self.show_selection_window(error_message=str(exception))
                
                return self.set_mode(mode, on_failure=on_failure)
        else:
            log.debug("Launching usage mode selection window...")
            
            self.show_selection_window(on_cancel_warning=_("No usage mode has "
                "been selected. Shutting down LottaNZB..."))

class ModeActionThread(Thread):
    """Entering and leaving usage modes takes place within this util.Thread, 
    so that the UI isn't blocked.
    """
    
    def __init__(self, mode, old_mode=None):
        Thread.__init__(self)
        
        # As we can't handle exceptions directly within the thread, they are
        # stored in this property.
        self.exception = None
        
        # An instance of Mode or None, if there's nothing but to leave the
        # current usage mode.
        self.mode = mode
        
        self.old_mode = old_mode
    
    def run(self):
        try:
            if self.old_mode:
                try:
                    self.old_mode.leave()
                except:
                    log.error(_("Could not leave current usage mode '%s'.") % self.old_mode)
                    raise
            
            if self.mode:
                try:
                    self.mode.enter()
                except:
                    log.error(_("Could not enter usage mode '%s'.") % self.mode)
                    raise
        
        except Exception, e:
            self.exception = e
        else:
            if self.old_mode:
                if not self.mode:
                    log.info(_("Left usage mode '%s'.") % self.old_mode)
                elif self.old_mode.__class__ is self.mode.__class__:
                    log.info(_("Reentered usage mode '%s'.") % self.mode)
                else:
                    log.info(_("Changed usage mode from '%s' to '%s'.") % (self.old_mode, self.mode))
            elif self.mode:
                log.info(_("Set usage mode to '%s'.") % self.mode)
        
        self.emit("completed")

def get_mode_class(name):
    """Turns a mode string into the corresponding modes.*.Mode class."""
    
    for mode_class in mode_list:
        if mode_class.get_name() == name:
            return mode_class
