# Copyright (C) 2008-2009 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

def die(message):
    """
    Try to display a message if the software requirements are not satisfied and
    shut down the application afterwards.
    """
    
    try:
        from subprocess import call
        call(["zenity", "--error", "--text", message])
    except (ImportError, OSError):
        pass
    
    sys.stderr.write(message)
    sys.exit(1)

if sys.version_info < (2, 4):
    die("You need to install Python 2.4 or greater to run LottaNZB.")

try:
    import pygtk
    pygtk.require("2.0")
    
    import gtk
    
    assert gtk.pygtk_version[:2] >= (2, 12)
except (ImportError, AssertionError):
    die("You need to install PyGTK 2.12 or newer to run LottaNZB.")

try:
    from kiwi.__version__ import version as kiwi_version
    from kiwi import log as kiwi_log
    from kiwi.environ import environ
except ImportError:
    die("You need to install Kiwi to run LottaNZB.\n"
        "The package is usually called 'python-kiwi'.")

if kiwi_version < (1, 9, 9):
    die("Your Kiwi installation is out-of-date.\n"
        "LottaNZB requires version 1.9.9 or greater.")

import os
import atexit
import threading
import locale
import gettext

import logging
log = logging.getLogger(__name__)

from os.path import isdir, expanduser
from shutil import move
from optparse import OptionParser

from lottanzb import __version__, resources
from lottanzb.util import (
    GObject,
    GObjectSingletonMeta,
    gproperty,
    FileLock,
    _
)

from lottanzb.config import LottaConfigRoot, LoadingError, ConfigNotFoundError

class App(GObject):
    __metaclass__ = GObjectSingletonMeta
    
    is_quitting = gproperty(type=bool, default=False)
    debug = gproperty(type=bool, default=False)
    
    backend = gproperty(type=object)
    mode_manager = gproperty(type=object)
    plugin_manager = gproperty(type=object)
    log = gproperty(type=object)
    main_window = gproperty(type=object)
    
    mode_selection_dialog = gproperty(
        type    = object,
        nick    = "Dialog displayed if LottaNZB cannot enter a usage mode",
        blurb   = "If the configuration file exists and is valid and the usage "
                  "mode can be entered without any problem, this property will "
                  "be None. It's reset to None as soon as the dialog is "
                  "closed by the user."
    )
    
    config = gproperty(type=object)
    lock = gproperty(type=object)
    
    # If set to True, the main window will not be shown when LottaNZB is
    # launched.
    silent_start = False
    
    def __init__(self):
        GObject.__init__(self)
        
        self.setup_resources()
        self.setup_translation()
        self.setup_logging()
    
    @staticmethod
    def setup_resources():
        old_config_dir = expanduser("~/.lottanzb")
        new_config_dir = resources.get_config()
        
        if isdir(old_config_dir) and not isdir(new_config_dir):
            log.info(_("Moving configuration directory from %s to %s..."),
                old_config_dir, new_config_dir)
            
            move(old_config_dir, new_config_dir)
        
        resources.create_user_dirs()
        
        environ.add_resource("glade", resources.get_glade())
        environ._add_extensions("glade",  ".glade", ".ui")
    
    @staticmethod
    def setup_translation():
        domain = "lottanzb"
        
        for module in (gettext, locale):
            try:
                 module.bindtextdomain(domain, resources.get_locale())
                 module.bind_textdomain_codeset(domain, "UTF-8")
                 module.textdomain(domain)
            except AttributeError:
                 pass
    
    @staticmethod
    def setup_logging():
        kiwi_log.set_log_level("lottanzb.*", logging.INFO)
        
        try:
            kiwi_log.set_log_file(resources.get_config("log"), "lottanzb.*")
        except AttributeError:
            logging.basicConfig(
                format="%(asctime)s %(name)-20s %(message)s",
                datefmt="%T",
                level=logging.INFO
            )
    
    def load_config(self):
        # Always apply the default configuration values first.
        # If the configuration file happens to be empty or incomplete, no
        # exception is raised when invoking config.load() and this would
        # eventually cause other exceptions to be raised.
        self.config = LottaConfigRoot(resources.get_config("lottanzb.conf"))
        
        try:
            self.config.load()
        except ConfigNotFoundError:
            # Don't log an error message if the configuration doesn't exist yet
            # This is the case during the first launch of LottaNZB.
            pass
        except LoadingError, error:
            log.error(str(error))
    
    def launch(self):
        # Lazy imports.
        from lottanzb.log import Log
        from lottanzb.modes import ModeManager
        from lottanzb.plugins import PluginManager
        from lottanzb.backend import Backend
        
        self.log = Log()
        logging.getLogger("lottanzb").addHandler(self.log)
        
        log.info(_("Starting LottaNZB %s...") % __version__)
        
        self.load_config()
        
        self.backend = Backend(self.config.backend)
        
        self.mode_manager = ModeManager(self.config.modes)
        self.plugin_manager = PluginManager(self, self.config.plugins)
        self.plugin_manager.load_plugins()
        
        atexit.register(self.on_quit)
        
        # Ask the user to select an usage mode or review the information if
        # LottaNZB cannot enter the usage mode in the configuration file
        # or if a usage mode is left unexpectedly.
        self.mode_manager.load(on_failure=self.handle_mode_loading_error)
        self.mode_manager.connect(
            "unexpected-mode-leaving",
            self.handle_mode_error
        )
    
    def handle_mode_error(self, mode_manager, error_message):
        from lottanzb.gui.modes import selection
        
        if error_message:
            log.error(error_message)
        
        if self.lock.own_lock():
            def on_closed(*args):
                if not mode_manager.active_mode:
                    self.quit(1)
                
                self.mode_selection_dialog = None
                
                # If there is an error at startup, the main window is kept from
                # being displayed. If the user fixes the configuration and
                # closes the dialog, display the main window.
                if self.main_window:
                    self.main_window.show()
            
            self.mode_selection_dialog = selection.Dialog(error_message)
            self.mode_selection_dialog.toplevel.connect("destroy", on_closed)
            self.mode_selection_dialog.show()
        else:
            # The user is probably configuring the application at this point of
            # time using another instance. We cannot show any window. Quit
            # silently.
            self.quit(1)
    
    def handle_mode_loading_error(self, exception):
        self.handle_mode_error(self.mode_manager, str(exception))
    
    def show_gui(self):
        from lottanzb.gui import main
        
        log.debug(_("Launching GUI..."))
        
        self.main_window = main.Window(self.config.gui.main)
        
        # Don't display the main window if the user hasn't chosen an usage mode
        # yet.
        if not self.mode_selection_dialog and not self.silent_start:
            self.main_window.show()
        
        gtk.main()
    
    def get_active_threads(self):
        return threading.enumerate()
    
    def stop_all_threads(self, block=False):
        for thread in self.get_active_threads():
            try:
                thread.stop()
            except:
                pass
        
        if block:
            self.wait_for_all_threads()
    
    def wait_for_all_threads(self):
        for thread in self.get_active_threads():
            try:
                if thread is not threading.currentThread():
                    thread.join()
            except:
                pass
    
    def quit(self, code=0):
        sys.exit(code)
    
    def on_quit(self):
        self.is_quitting = True
        
        self.config.save()
        self.mode_manager.leave_mode()
        self.stop_all_threads(True)
        
        try:
            self.lock.release()
        except FileLock.ReleaseError:
            pass
        
        log.debug(_("Exiting..."))
        
        if gtk.main_level():
            gtk.main_quit()
    
    def main(self, args):
        parser = OptionParser(
            usage="%prog [FILES and/or NEWZBINIDS...]",
            version="LottaNZB %s" % __version__
        )
        
        parser.add_option("-d", "--debug", action="store_true", dest="debug", \
            help="show debug messages", default=False)
        
        options, args = parser.parse_args()
        
        # Initialize console logging
        if options.debug:
            kiwi_log.set_log_level("*", logging.DEBUG)
            self.debug = True
        
        # Check if another instance of LottaNZB is already running
        self.lock = FileLock("/tmp/lottanzb.lock")
        
        try:
            self.lock.acquire()
        except FileLock.LockError:
            pass
        
        files = []
        newzbin_ids = []
        
        for arg in args:
            try:
                newzbin_ids.append(int(arg))
            except ValueError:
                files.append(arg)
        
        command_line_action = bool(len(files) or len(newzbin_ids))
        
        if command_line_action or self.lock.own_lock():            
            self.launch()
            
            if command_line_action:
                def enqueue_items(mode_manager, *args):
                    if mode_manager.active_mode:
                        for file in files:
                            self.backend.enqueue(file)
                        
                        for newzbin_id in newzbin_ids:
                            self.backend.newzbinEnqueue(newzbin_id)
                        
                        if not self.lock.own_lock():
                            self.quit()
                
                if self.mode_manager.active_mode:
                    enqueue_items(self.mode_manager)
                else:
                    self.mode_manager.connect_async("notify::active-mode", \
                        enqueue_items)
            
            if self.lock.own_lock():
                self.show_gui()
            else:
                gtk.main()
        else:
            # We've got nothing to do
            log.error(_("LottaNZB is already running on this computer."))
            self.quit(1)
    
    def run(self):
        try:
            sys.exit(self.main(sys.argv))
        except KeyboardInterrupt:
            raise SystemExit
