#!/usr/bin/env python3
# ==============================================================================
#  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; either version 3 of the License, or
#  (at your option) any later version.
#
#  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 Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
# ==============================================================================

# Filesystem
from os import environ

from pathlib import Path

# Logging
import logging

# Regex
from re import search

# System
from argparse import ArgumentParser

from subprocess import run
from subprocess import PIPE
from subprocess import Popen

from shlex import split as shlex_split

try:
    from xdg.DesktopEntry import DesktopEntry

except ImportError as error:
    from sys import exit as sys_exit

    sys_exit("Cannot import python3-xdg: %s" % str(error))

# ==============================================================================
#   Functions
# ==============================================================================

def get_binary_path(binary):
    """ Get a list of available binary paths from $PATH variable

    This function get all the path from $PATH variable which match binary
    request.

    Parameters
    ----------
    binary : str or pathlib.Path
        Binary name or path

    Returns
    -------
    list
        List of available path

    Examples
    --------
    >>> get_binary_path("ls")
    [PosixPath('/usr/bin/ls')]
    """

    available = list()

    if type(binary) is str:
        binary = Path(binary).expanduser()

    if binary.exists():
        available.append(binary)

    for path in set(environ["PATH"].split(':')):
        binary_path = Path(path).joinpath(binary.name)

        if binary_path.exists():
            available.append(binary_path)

    return available

# ==============================================================================
#   Launcher
# ==============================================================================

def main():
    """ Main launcher
    """

    # Generate default arguments
    parser = ArgumentParser(
        description="A pretty launcher for Wine prefix",
        epilog="Copyleft 2018 - Kawa-Team", conflict_handler="resolve")

    parser.add_argument("-v", "--version", action="version",
        version="run-wine - Licence GPLv3", help="show the current version")

    parser_system = parser.add_argument_group("system arguments")
    parser_system.add_argument("--layout", action="store", metavar="LAYOUT",
        help="set keyboard layout")

    parser_wine = parser.add_argument_group("wine arguments")
    parser_wine.add_argument("--config", action="store_true",
        help="launch wine configuration")
    parser_wine.add_argument("--tricks", action="store_true",
        help="launch winetricks")
    parser_wine.add_argument("--esync", action="store_true",
        help="set esync environment")
    parser_wine.add_argument("--wine", action="store", metavar="PATH",
        help="set wine version folder (default: /usr)")
    parser_wine.add_argument("--prefix", action="store", metavar="PATH",
        help="set wine prefix (default: ~/.wine)")

    parser.add_argument("executable", nargs='?', type=str, action="store",
        metavar="EXECUTABLE", help="executable file to launch")

    args = parser.parse_args()

    # ----------------------------------------
    #   Logging module
    # ----------------------------------------

    process_error = False

    logging.basicConfig(format='%(message)s', level=logging.DEBUG)

    # ----------------------------------------
    #   Set default layout
    # ----------------------------------------

    wine_layout = args.layout
    previous_layout = args.layout

    loadkeys_binaries = get_binary_path("loadkeys")
    setxkbmap_binaries = get_binary_path("setxkbmap")

    # Get current layout with setxkbmap
    if wine_layout is not None and len(setxkbmap_binaries) > 0:
        process = Popen(shlex_split("setxkbmap -query"), stdout=PIPE)

        output, process_error = process.communicate()

        if len(output) > 0:
            result = search("layout:\s*(\w+)", output.decode("UTF-8"))

            if result and result.group(0) is not None:
                previous_layout = result.group(0).split()[-1]

                logging.debug(">>> Store %s keyboard layout" % previous_layout)

    # ----------------------------------------
    #   Wine prefix
    # ----------------------------------------

    path = args.prefix

    if path is None:
        path = "~/.wine"

    prefix = Path(path).expanduser().resolve()

    environ["WINEPREFIX"] = str(prefix)

    # ----------------------------------------
    #   Wine version
    # ----------------------------------------

    path = args.wine

    if path is None:
        path = "/usr"

    wine = Path(path).expanduser().resolve()

    if wine.exists():
        wine_loader = wine.joinpath("bin", "wine")
        wine_server = wine.joinpath("bin", "wineserver")

        if wine_loader.exists():
            environ["WINELOADER"] = str(wine_loader)

        if wine_server.exists():
            environ["WINESERVER"] = str(wine_server)

    # ----------------------------------------
    #   Wine esync environment
    # ----------------------------------------

    if args.esync:
        environ["WINEESYNC"] = '1'

    # ----------------------------------------
    #   Retrieve informations
    # ----------------------------------------

    logging.info(">>> Read metadata")

    metadata = (
        ("Layout", wine_layout),
        ("Wine Prefix", environ["WINEPREFIX"]),
        ("Wine Loader", environ["WINELOADER"]),
        ("Wine Server", environ["WINESERVER"]))

    for key, value in metadata:
        if value is not None and len(value) > 0:
            logging.info("%s: %s" % (key, value))

    # ----------------------------------------
    #   Wine application
    # ----------------------------------------

    process = None

    # System environ variables
    environ["WINEDEBUG"] = "-all"

    try:
        if not prefix.exists():
            prefix.mkdir(0o755, parents=True)

            run(shlex_split("wineboot -u"))

        # ----------------------------------------
        #   Wine configuration
        # ----------------------------------------

        if args.config:
            command = "winecfg"

            if wine is not None:
                winecfg_path = wine.joinpath("bin", "winecfg")

                if winecfg_path.exists():
                    command = str(winecfg_path)

            logging.info(">>> Start configuration tool")

            # Launch application
            process = Popen(shlex_split(command), stdout=PIPE, cwd=prefix)

            # Print application output in main stdout
            for line in iter(process.stdout.readline, bytes(str(), "UTF-8")):
                logging.info(line.decode("UTF-8")[:-1])

            # Wait until application terminate
            process.wait()

            # Get processus return code
            process_error = process.returncode

        # ----------------------------------------
        #   Winetricks
        # ----------------------------------------

        elif args.tricks:
            command = "winetricks"

            logging.info(">>> Start winetricks")

            # Launch application
            process = Popen(shlex_split(command), stdout=PIPE, cwd=prefix)

            # Print application output in main stdout
            for line in iter(process.stdout.readline, bytes(str(), "UTF-8")):
                logging.info(line.decode("UTF-8")[:-1])

            # Wait until application terminate
            process.wait()

            # Get processus return code
            process_error = process.returncode

        # ----------------------------------------
        #   Prepare application
        # ----------------------------------------

        elif args.executable is not None:
            filepath = args.executable

            # Check application path
            if filepath is not None:
                filepath = Path(filepath).expanduser()

            command = "wine \"%s\"" % filepath.name

            if "WINELOADER" in environ:
                command = "%s \"%s\"" % (environ["WINELOADER"], filepath.name)

            # Set specified keyboard layout
            if wine_layout is not None:
                logging.info(">>> Change layout to %s" % wine_layout)

                if len(setxkbmap_binaries) > 0:
                    run(shlex_split("setxkbmap %s" % wine_layout))

                elif len(loadkeys_binaries) > 0:
                    run(shlex_split("loadkeys %s" % wine_layout))

            # ----------------------------------------
            #   Start application
            # ----------------------------------------

            logging.info(">>> Start %s" % filepath.stem)

            # Launch application
            process = Popen(shlex_split(command),
                stdout=PIPE, cwd=prefix.joinpath(filepath.parent))

            # Print application output in main stdout
            for line in iter(process.stdout.readline, bytes(str(), "UTF-8")):
                logging.info(line.decode("UTF-8")[:-1])

            # Wait until application terminate
            process.wait()

            # Retrieve application return code
            process_error = process.returncode

    except KeyboardInterrupt:
        print() # Just to have a pretty output :D

        logging.warning(">>> Interrupt process")

        if process is not None:
            process.terminate()

    except Exception as error:
        logging.exception(">>> %s" % error)

        process_error = True

    # ----------------------------------------
    #   Close program
    # ----------------------------------------

    # Restore keyboard layout
    if wine_layout is not None and not wine_layout == previous_layout:
        logging.debug(">>> Restore keyboard layout to %s" % previous_layout)

        if len(get_binary_path("setxkbmap")) > 0:
            run(shlex_split("setxkbmap %s" % str(previous_layout)))

        elif len(get_binary_path("loadkeys")) > 0:
            run(shlex_split("loadkeys %s" % str(previous_layout)))

    return process_error


if __name__ == "__main__":
    main()
