#! /usr/bin/env python
# (c) Copyright 2009-2012. CodeWeavers, Inc.

import os

# Portable which(1) implementation
def which(path, app):
    """Looks for an executable in the specified directory list.

    path is an os.pathsep-separated list of directories and app is the
    executable name. If app contains a path separator then path is ignored.
    If the file is not found, then None is returned.
    """
    if os.path.isabs(app):
        if os.path.isfile(app) and os.access(app, os.X_OK):
            return app
    elif os.sep in app or (os.altsep and os.altsep in app):
        app_path = os.path.join(os.getcwd(), app)
        if os.path.isfile(app_path) and os.access(app_path, os.X_OK):
            return app_path
    else:
        for directory in path.split(os.pathsep):
            if directory == "":
                continue
            app_path = os.path.join(directory, app)
            if os.path.isfile(app_path) and os.access(app_path, os.X_OK):
                return app_path
    return None

import sys
def locate_cx_root():
    """Locate where CrossOver is installed.

    We start by locating our own python script file and walking back up the
    path, traversing symbolic links on the way. Then we verify what we have
    found the right directory by checking for the presence of the cxmenu
    script.
    """
    # pylint: disable-msg=I0011,W0601,W0603
    global CX_ROOT
    if "CX_DEVELOP_ROOT" in os.environ:
        CX_ROOT = os.environ["CX_DEVELOP_ROOT"]
        return

    # figure out argv0
    argv0 = which(os.environ["PATH"], sys.argv[0])
    if not argv0:
        argv0 = sys.argv[0]
        if not os.path.isabs(argv0):
            argv0 = os.path.join(os.getcwd(), argv0)

    # traverse the symbolic links
    dir0 = os.path.dirname(argv0)
    while True:
        if dir0.endswith("/lib"):
            bindir = dir0[0:-3] + "bin"
        else:
            bindir = dir0
        landmark = os.path.join(bindir, "cxmenu")
        if os.path.isfile(landmark):
            break
        if not os.path.islink(argv0):
            break
        argv0 = os.readlink(argv0)
        if not os.path.isabs(argv0):
            argv0 = os.path.join(dir0, argv0)
        dir0 = os.path.dirname(argv0)

    # compute CX_ROOT
    CX_ROOT = os.path.dirname(os.path.normpath(bindir))

    # check CX_ROOT
    landmark = os.path.join(CX_ROOT, "bin", "cxmenu")
    if not os.path.isfile(landmark) or not os.access(landmark, os.X_OK):
        sys.stderr.write("%s:error: could not find CrossOver in '%s'\n" % (os.path.dirname(sys.argv[0]), CX_ROOT))
        sys.exit(1)

    sys.path.append(os.path.join(CX_ROOT, "lib", "python"))

locate_cx_root()
import cxutils
cxutils.CX_ROOT = CX_ROOT


import traceback

import checkgtk
if checkgtk.check_gtk() != checkgtk.OK:
    sys.exit(1)

import gtk
import gtk.glade
gtk.gdk.threads_init()

import bottlequery
import cxguitools
import pyop

import appdetector
import c4profilesmanager

import cxlog
import cxopt

# for localization
from cxutils import cxgettext as _

_RECREATE_BUTTON = 1


class AssocEditDialog(object):
    def __init__(self, bottlename):
        self.bottlename = bottlename

        try:
            self.gladepath = os.environ["CX_GLADEPATH"]
        except KeyError:
            self.gladepath = os.path.join(cxutils.CX_ROOT, "lib", "python", "glade")

        self.gladefile = os.path.join(self.gladepath, "cxassocedit.glade")

        gtk.glade.set_custom_handler(cxguitools.CustomWidgets.custom_widget_handler)
        if not gtk.glade.textdomain() == "crossover":
            locale_path = os.path.join(CX_ROOT, "share", "locale")
            gtk.glade.bindtextdomain("crossover", locale_path)
            gtk.glade.textdomain("crossover")
        self.xml = gtk.glade.XML(self.gladefile)
        self.xml.signal_autoconnect(self)

        self.dialog = self.xml.get_widget('assoc-edit-dialog')
        self.edit_widget = self.xml.get_widget('assoc-editor')
        self.common_only_checkbox = self.xml.get_widget('common-only')

        self.dialog.set_title(_('Associations in %(bottlename)s') % {'bottlename':bottlename})

        properties = bottlequery.get_bottle_properties(bottlename)
        if properties['managed']:
            self.xml.get_widget("recreate_button").hide()
            self.xml.get_widget("apply_button").hide()
            self.xml.get_widget("cancel_button").hide()
            self.xml.get_widget("ok_button").hide()
        else:
            self.xml.get_widget("close_button").hide()

        self.edit_widget.set_bottle(bottlename, properties['managed'])

        self.edit_widget.refresh(self.enable_buttons, self.refresh_fail)

        self.edit_widget.show() # FIXME: Why is this necessary?
        self.dialog.show()

        self.common_eassocs = set()

        self.edit_widget.set_filter(self.assoc_is_common)

        # Get the list of installed applications so we know which
        # associations are "common".
        operation = GetInstalledAppsOp(self)
        pyop.sharedOperationQueue.enqueue(operation)

    def got_installed_apps(self, apps):
        self.common_only_checkbox.set_sensitive(True)
        for app in apps.itervalues():
            profile = app.profile
            if profile:
                # Ignore use_if and take all the installer profiles into
                # account, even if they are not for the current profile!
                for inst_profile in profile.installer_profiles:
                    self.common_eassocs.update(inst_profile.default_eassocs)
                    self.common_eassocs.update(inst_profile.alt_eassocs)
        self.edit_widget.set_filter(self.assoc_is_common)

    def assoc_is_common(self, eassoc):
        return eassoc in self.common_eassocs

    def on_common_only_toggled(self, togglebutton):
        if togglebutton.get_active():
            self.edit_widget.set_filter(self.assoc_is_common)
        else:
            self.edit_widget.set_filter(None)

    def disable_buttons(self):
        self.dialog.set_response_sensitive(_RECREATE_BUTTON, False)
        self.dialog.set_response_sensitive(gtk.RESPONSE_CANCEL, False)
        self.dialog.set_response_sensitive(gtk.RESPONSE_APPLY, False)
        self.dialog.set_response_sensitive(gtk.RESPONSE_OK, False)

    def enable_buttons(self):
        self.dialog.set_response_sensitive(_RECREATE_BUTTON, True)
        self.dialog.set_response_sensitive(gtk.RESPONSE_CANCEL, True)
        self.dialog.set_response_sensitive(gtk.RESPONSE_APPLY, True)
        self.dialog.set_response_sensitive(gtk.RESPONSE_OK, True)

    def ok_finished(self):
        self.dialog.hide()
        gtk.main_quit()

    def apply_fail(self, error):
        self.enable_buttons()
        cxguitools.CXMessageDlg(
            primary=_("Could not save changes to associations"),
            secondary=error,
            parent=self.dialog,
            message_type=gtk.MESSAGE_ERROR)

    def quit_message_box(self, _response):
        gtk.main_quit()

    def refresh_fail(self, error):
        self.dialog.hide()
        cxguitools.CXMessageDlg(
            primary=_("Could not read associations for %(bottlename)s") % {'bottlename': self.bottlename},
            secondary=error,
            parent=self.dialog, response_function=self.quit_message_box,
            message_type=gtk.MESSAGE_ERROR)

    def on_response(self, _dialog, response_id):
        if response_id == gtk.RESPONSE_CANCEL:
            gtk.main_quit()

        elif response_id == gtk.RESPONSE_APPLY:
            self.disable_buttons()
            self.edit_widget.commit(self.enable_buttons, self.apply_fail)

        elif response_id == gtk.RESPONSE_OK:
            self.disable_buttons()
            self.edit_widget.commit(self.ok_finished, self.apply_fail)

        elif response_id == _RECREATE_BUTTON:
            self.disable_buttons()
            self.edit_widget.recreate_assocs(self.enable_buttons, self.apply_fail)

    def on_delete(self, _widget, _event):
        gtk.main_quit()

class GetInstalledAppsOp(pyop.PythonOperation):
    def __init__(self, dialog):
        pyop.PythonOperation.__init__(self)
        self.dialog = dialog

    def main(self):
        try:
            self.installed_apps = appdetector.fast_get_installed_applications(
                self.dialog.bottlename,
                c4profilesmanager.profilesSet())
            self.succeeded = True
        except Exception:
            self.succeeded = False
            cxlog.warn("Error getting installed apps: \n%s" % traceback.format_exc())

    def finish(self):
        if self.succeeded:
            self.dialog.got_installed_apps(self.installed_apps)

def main():
    # Parse the command line options
    opt_parser = cxopt.Parser(usage="%prog [--bottle BOTTLE] [--help]",
                              description="A graphical editor that lets you choose which Windows associations to export to the native desktop environment.")
    opt_parser.add_option("--bottle", dest="bottle", help="Use the specified bottle. If this option is not used, fallback to $CX_BOTTLE and then to 'default'")
    (options, args) = opt_parser.parse_args()

    # Do more checks and provide defaults
    if args:
        opt_parser.error("unexpected argument '%s'" % args[0])
    if options.bottle is None:
        if 'CX_BOTTLE' in os.environ:
            options.bottle = os.environ['CX_BOTTLE']
        else:
            options.bottle = 'default'

    # Start the GUI
    cxguitools.set_default_icon()
    AssocEditDialog(options.bottle)
    gtk.main()

if __name__ == '__main__':
    sys.exit(main())
