#!/usr/bin/env python3

import collections
import glob
import urllib.request
import gettext
import gi
import json
import locale
import logging
import os
import subprocess
import sys
import webbrowser

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, Gdk

_SHARE_MANJARO = "/usr/share/manjaro"
_HELLO_DATA_DIR = f"{_SHARE_MANJARO}/manjaro-hello"
_HELLO_PREF_FILE = f"{_HELLO_DATA_DIR}/preferences.json"


class EmbedManager:
    """manage included applications"""
    def __init__(self, *args):
        self.apps = []
        self.count = 0
        for app in args:
            self.apps.append(app)

    def get_modules(self, window: Gtk.Window):
        for app in self.apps:
            app.load(window)
        self.count = sum((1 for x in self.apps if x.loaded))

    def display(self, window: Gtk.Window):
        for app in self.apps:
            app.display(window)


class Embed:
    """abstact class for include app"""
    def __init__(self):
        """ abstact class initialisation """
        self.name = "app" + self.__class__.__name__[5:]
        self.loaded = False
        self.box = None

    def load(self, window: Gtk.Window) -> bool:
        """ load modules if installed"""
        raise Exception('abstract method')
    
    def on_btn_clicked(self, btn, window: Gtk.Window):
        """Event for applications button."""
        name = btn.get_name()
        window.builder.get_object("stack").set_visible_child_name(name + "page")

    def display(self, window: Gtk.Window):
        """ show btn and add page"""
        window.builder.get_object(self.name).set_visible(self.loaded)
        if self.loaded:
            window.builder.get_object("stack").add_named(self.box, self.name + "page")

class EmbedLayouts(Embed):
    """GNOME Layout Switcher"""
    def load(self, window: Gtk.Window) -> bool:
        try:
            # import layoutswitcherlib
            from layoutswitcherlib.layoutsbox import LayoutBox
            try:
                self.box = LayoutBox(window, usehello=True)
                grid = Gtk.Grid()
                grid.set_margin_start(15)
                image = get_icon_image("go-previous", Gtk.IconSize.BUTTON)
                back_btn=Gtk.Button(label=None, image=image)
                back_btn.set_name("home")
                back_btn.connect("clicked", self.on_btn_clicked,window)
                grid.attach (back_btn, 0, 0, 1, 1)
                self.box.pack_start(grid, expand=False, fill=False, padding=10)
                self.box.reorder_child(grid,0)
                self.box.show_all()
            except Exception as err:
                logging.error("Error in embedded application -> 'layoutswitcherlib'")
                logging.error(err)
        except ModuleNotFoundError:
            logging.info(f"Plugin 'layoutswitcherlib' not available.")
        self.loaded = self.box is not None
        return self.loaded

class EmbedBrowser(Embed):
    """Application-utility"""
    def load(self, window: Gtk.Window) -> bool:
        try:
            from application_utility import application_utility
            from application_utility.translation import i18n
            from application_utility.browser.application_browser import ApplicationBrowser
            from application_utility.browser.exceptions import NoAppInIsoError
            from application_utility.browser import alpm
            from application_utility.browser import data
            from application_utility.config.hello_config import HelloConfig
            try:
                conf = HelloConfig(application="manjaro-hello")
                grid = Gtk.Grid()
                grid.set_margin_start(5)
                grid.set_margin_end(5)
                grid.set_margin_top(5)
                grid.set_margin_bottom(5)
                image = get_icon_image("go-previous", Gtk.IconSize.BUTTON)
                back_btn=Gtk.Button(label=None, image=image)
                back_btn.set_name("home")
                back_btn.connect("clicked", self.on_btn_clicked,window)
                grid.attach (back_btn, 0, 1, 1, 1)
                app=ApplicationBrowser(conf, window)
                app.info_bar_title.pack_start(grid, expand=False, fill=False, padding=10)
                app.info_bar_title.reorder_child(grid,0)
                app.show_all()
                self.box = app
            except Exception as err:
                logging.error("Error in embedded application -> 'application-utility'")
                logging.error(err)
        except ModuleNotFoundError as err:
            logging.info(f"Plugin 'application-utility' not available.")
        self.loaded = self.box is not None
        return self.loaded


class Hello(Gtk.Window):
    """Hello"""

    def __init__(self):
        Gtk.Window.__init__(self, title="Manjaro Hello", border_width=6)
        self.app = "manjaro-hello"
        screen = Gdk.Screen.get_default()

        self.dev = "--dev" in sys.argv
        if self.dev:
            # dont load hardcoded path in devmode
            project_dir = os.getcwd()
            self.preferences = read_json(f"{project_dir}/data/preferences.json")
            self.preferences["data_path"] = f"{project_dir}/data"
            self.preferences["desktop_path"] = f"{project_dir}/{self.app}.desktop"
            self.preferences["locale_path"] = f"{project_dir}/locale"
            self.preferences["ui_path"] = f"{project_dir}/ui/{self.app}.glade"
            self.preferences["style_path"] = f"{project_dir}/ui/style.css"
            logging.debug(f"Using dev preferences: {self.preferences}")
        else:
            self.preferences = read_json(f"{_HELLO_PREF_FILE}")
            logging.debug(f"Using system preferences: {self.preferences}")

        # Get saved infos
        self.usr_prefs = read_json(self.preferences["save_path"])
        if not self.usr_prefs:
            self.usr_prefs = {"locale": None}

        # Import Css
        provider = Gtk.CssProvider()
        provider.load_from_path(self.preferences["style_path"])
        Gtk.StyleContext.add_provider_for_screen(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        # Init window
        self.builder = Gtk.Builder.new_from_file(self.preferences["ui_path"])
        self.builder.connect_signals(self)
        self.window = self.builder.get_object("window")
        
        # Subtitle of headerbar
        self.builder.get_object("headerbar").props.subtitle = ' '.join(get_lsb_infos())

        # Load images
        if os.path.isfile(self.preferences["logo_path"]):
            logo = GdkPixbuf.Pixbuf.new_from_file(self.preferences["logo_path"])
            self.window.set_icon(logo)
            self.builder.get_object("distriblogo").set_from_pixbuf(logo)
            self.builder.get_object("aboutdialog").set_logo(logo)

        for btn in self.builder.get_object("social").get_children():
            icon_path = self.preferences["data_path"] + "/img/" + btn.get_name() + ".png"
            self.builder.get_object(btn.get_name()).set_from_file(icon_path)

        for widget in self.builder.get_object("homepage").get_children():
            if isinstance(widget, Gtk.Button) and \
                    widget.get_image_position() is Gtk.PositionType.RIGHT:
                img = Gtk.Image.new_from_file(
                    self.preferences["data_path"] + "/img/external-link.png")
                img.set_margin_start(2)
                widget.set_image(img)

        # Create pages
        # load pageas for language
        self.pages = os.listdir(f"/{self.preferences["data_path"]}/pages/{self.preferences["default_locale"]}")
        for page in self.pages:
            scrolled_window = Gtk.ScrolledWindow()
            viewport = Gtk.Viewport(border_width=10)
            label = Gtk.Label(wrap=True)
            image = get_icon_image("go-previous", Gtk.IconSize.BUTTON)
            back_btn=Gtk.Button(label=None, image=image)
            back_btn.set_name("home")
            back_btn.connect("clicked", self.on_btn_clicked)

            grid = Gtk.Grid()
            grid.attach (back_btn, 0, 1, 1, 1)
            grid.attach(label, 1, 2, 1, 1)
            viewport.add(grid)
            scrolled_window.add(viewport)
            scrolled_window.show_all()
            self.builder.get_object("stack").add_named(scrolled_window, page + "page")

        # Init translation
        self.default_texts = {}
        gettext.bindtextdomain(self.app, f"{self.preferences["locale_path"]}/")
        gettext.textdomain(self.app)
        self.builder.get_object("languages").set_active_id(self.get_best_locale())

        # Set autostart switcher state
        self.autostart = os.path.isfile(fix_path(self.preferences["autostart_path"]))
        self.builder.get_object("autostart").set_active(self.autostart)

        # Live systems
        if (os.path.exists(self.preferences["live_path"]) and
                os.path.isfile(self.preferences["installer_path"])):
            # show install label
            self.builder.get_object("installlabel").set_visible(True)
            # show install button
            self.builder.get_object("install").set_visible(True)
            # check if manjaro system rescue is installed
            if (os.path.exists(self.preferences["live_path"]) and
                    os.path.isfile(self.preferences["rescue_path"])):
                # show grub-rescue button
                self.builder.get_object("rescue").set_visible(True)
        # Installed systems
        else:
            manager = EmbedManager(EmbedBrowser(), EmbedLayouts())
            manager.get_modules(self)
            manager.display(self)
            de = os.environ.get("DESKTOP_SESSION", "unknown")
            # check desktop plasma or gnome
            if de == "plasma" and os.path.isfile(self.preferences["plasmawelcome_path"]):
                # enable Plasma Welcome button
                self.builder.get_object("deWelcome").set_visible(True)
                self.builder.get_object("deWelcome").set_label("Plasma Welcome")
            elif de == "gnome" and os.path.isfile(self.preferences["gnometour_path"]):
                # enable Gnome Tour button
                self.builder.get_object("deWelcome").set_visible(True)
                self.builder.get_object("deWelcome").set_label("GNOME Tour")

        self.window.show()

    def get_best_locale(self):
        """Choose locale, based on user's preferences.
        :return: locale to use
        :rtype: str
        """
        path = self.preferences["locale_path"] + "/{}/LC_MESSAGES/" + self.app + ".mo"

        if os.path.isfile(path.format(self.usr_prefs["locale"])):
            # return usr_preference
            return self.usr_prefs["locale"]
        elif self.usr_prefs["locale"] == self.preferences["default_locale"]:
            # return default
            return self.preferences["default_locale"]
        else:
            # decide which locale to use
            locale.setlocale(locale.LC_ALL, '')
            sys_locale = locale.getlocale()[0]

            # If user's locale is supported
            if os.path.isfile(path.format(sys_locale)):
                if "_" in sys_locale:
                    return sys_locale.replace("_", "-")
                else:
                    return sys_locale
            # If two first letters of user's locale is supported (ex: en_US -> en)
            elif os.path.isfile(path.format(sys_locale[:2])):
                return sys_locale[:2]
            else:
                return self.preferences["default_locale"]

    def set_locale(self, use_locale):
        """Set locale of ui and pages.
        :param use_locale: locale to use
        :type use_locale: str
        """
        try:
            translation = gettext.translation(self.app,
                                              self.preferences["locale_path"],
                                              [use_locale],
                                              fallback=True)
            translation.install()
        except OSError:
            return

        self.usr_prefs["locale"] = use_locale

        # Real-time locale changing

        elts = {
            "comments": {"aboutdialog"},
            "label": {
                "autostartlabel",
                "development",
                "donate",
                "firstcategory",
                "forum",
                "install",
                "installlabel",
                "rescue",
                "involved",
                "mailling",
                "readme",
                "release",
                "secondcategory",
                "thirdcategory",
                "welcomelabel",
                "welcometitle",
                "wiki"
            },
            "tooltip_text": {
                "about",
                "development",
                "donate",
                "forum",
                "mailling",
                "wiki"
            }
        }
        for method in elts:
            if method not in self.default_texts:
                self.default_texts[method] = {}
            for elt in elts[method]:
                if elt not in self.default_texts[method]:
                    self.default_texts[method][elt] = getattr(
                        self.builder.get_object(elt), "get_" + method)()
                getattr(self.builder.get_object(elt), "set_" + method)(_(self.default_texts[method][elt]))

        # Change content of pages
        for page in self.pages:
            child = self.builder.get_object("stack").get_child_by_name(page + "page")
            label = child.get_children()[0].get_children()[0].get_children()[0]
            label.set_markup(self.get_page(page))

    def set_autostart(self, autostart):
        """Set state of autostart.
        :param autostart: wanted auto start state
        :type autostart: bool
        """
        try:
            if autostart and not os.path.isfile(fix_path(self.preferences["autostart_path"])):
                os.symlink(self.preferences["desktop_path"],
                           fix_path(self.preferences["autostart_path"]))
            elif not autostart and os.path.isfile(fix_path(self.preferences["autostart_path"])):
                os.unlink(fix_path(self.preferences["autostart_path"]))
            # Specific to i3
            i3_config = fix_path("~/.i3/config")
            if os.path.isfile(i3_config):
                i3_autostart = "exec --no-startup-id " + self.app
                with open(i3_config, "r+") as file:
                    content = file.read()
                    file.seek(0)
                    if autostart:
                        file.write(content.replace("#" + i3_autostart, i3_autostart))
                    else:
                        file.write(content.replace(i3_autostart, "#" + i3_autostart))
                    file.truncate()
            self.autostart = autostart
        except OSError as error:
            print(error)

    def get_page(self, name):
        """Read page according to language.
        :param name: name of page (filename)
        :type name: str
        :return: text to load
        :rtype: str
        """
        filename = f"{self.preferences["data_path"]}/pages/{self.usr_prefs["locale"]}/{name}"
        if not os.path.isfile(filename):
            filename = f"{self.preferences["data_path"]}/pages/{self.preferences["default_locale"]}/{name}"
        try:
            with open(filename, "r") as fil:
                return fil.read()
        except OSError:
            return _("Can't load page.")

    # Handlers
    def on_languages_changed(self, combobox):
        """Event for selected language."""
        self.set_locale(combobox.get_active_id())

    def on_action_clicked(self, action, _=None):
        """Event for differents actions."""
        name = action.get_name()
        if name == "install":
            subprocess.Popen(["calamares_polkit"])
        elif name == "rescue":
            subprocess.Popen(["manjaro-rescue"])
        elif name == "autostart":
            self.set_autostart(action.get_active())
        elif name == "about":
            dialog = self.builder.get_object("aboutdialog")
            dialog.run()
            dialog.hide()
        elif name == "deWelcome":
            de = os.environ.get("DESKTOP_SESSION", "unknown")

            if de == "plasma":
                 subprocess.Popen(["plasma-welcome"])
            elif de == "gnome":
                subprocess.Popen(["gnome-tour"])

    def on_btn_clicked(self, btn):
        """Event for applications button."""
        name = btn.get_name()
        self.builder.get_object("stack").set_visible_child_name(name + "page")

    def on_link_clicked(self, link, _=None):
        """Event for clicked link."""
        Gtk.show_uri_on_window(None, self.preferences["urls"][link.get_name()], Gdk.CURRENT_TIME)

    def on_delete_window(self, *args):
        """Event to quit app."""
        write_json(self.preferences["save_path"], self.usr_prefs)
        Gtk.main_quit(*args)


def fix_path(path):
    """Make good paths.
    :param path: path to fix
    :type path: str
    :return: fixed path
    :rtype: str
    """
    if "~" in path:
        path = path.replace("~", os.path.expanduser("~"))
    return path


def read_json(path) -> dict or None:
    """Read content of a json file.
    :param path: path to read
    :type path: str
    :return: json content
    :rtype: str
    """
    path = fix_path(path)
    try:
        with open(path, "r") as fil:
            return json.load(fil)
    except OSError:
        return None


def write_json(path, content):
    """Write content in a json file.
    :param path: path to write
    :type path: str
    :param content: content to write
    :type path: str
    """
    path = fix_path(path)
    try:
        with open(path, "w") as fil:
            json.dump(content, fil)
    except OSError as error:
        print(error)


def get_lsb_infos():
    """Read information from the lsb-release file.
    :return: args from lsb-release file
    :rtype: dict"""
    lsb = {}
    try:
        with open("/etc/lsb-release") as lsb_release:
            for line in lsb_release:
                if "=" in line:
                    var, arg = line.rstrip().split("=")
                    if not arg:
                        continue
                    var = var.replace("DISTRIB_","")
                    lsb[var] = arg.strip('"')
    except (OSError, KeyError) as error:
        print(error)
        return 'not Manjaro', '0.0'
    return lsb["CODENAME"], lsb["RELEASE"]

def get_icon_image(icon_name, icon_size):
    icon_theme = Gtk.IconTheme.get_default()

    if icon_theme.has_icon(icon_name):
        pixbuf = icon_theme.load_icon(icon_name, icon_size, 0)
        if pixbuf:
            return Gtk.Image.new_from_pixbuf(pixbuf)
        else:
            return Gtk.Image.new_from_icon_name(icon_name, icon_size)
    return None

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    hello = Hello()
    hello.connect('delete-event', Gtk.main_quit)
    hello.connect("destroy", Gtk.main_quit)
    Gtk.main()

