""" turtle dbus service

This file is part of Turtle.

Turtle 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.

Turtle 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 Turtle. If not, see <https://www.gnu.org/licenses/>. 
"""
import os
import sys
from pathlib import Path
import subprocess
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
import pygit2
from gi.repository import GLib
from turtlevcs import TURTLE_APP_ID


TURTLE_OBJECT_PATH = "/TurtleService"


class TurtleStatusChecker:
    """ turtle status checker """

    path = None
    repo = None

    def __init__(self, path):
        self.path = path
        self.repo = pygit2.Repository(path)

    def check_folder(self):
        """ check status of folder """
        status = pygit2.GIT_STATUS_CURRENT
        try:
            current_folder = self.repo.workdir
            new_repo_status = {}
            ignored_folders = []

            if Path(self.path) == Path(self.repo.workdir):
                new_repo_status = self.repo.status(ignored=True)
            else:
                current_folder = str(Path(self.path).relative_to(self.repo.workdir)) + "/"
                repo_status = self.repo.status(ignored=True)

                for key, value in repo_status.items():
                    if value == pygit2.GIT_STATUS_IGNORED:
                        if current_folder.startswith(key):
                            # folder is ignored so we can return value already
                            return pygit2.GIT_STATUS_IGNORED
                        else:
                            ignored_folders.append(key)
                    elif key.startswith(current_folder):
                        # item is in current folder
                        new_repo_status[key] = value

            status = self.__check_specific_folder(new_repo_status, current_folder, ignored_folders)
        except Exception as _ex:
            pass

        return status

    def __check_specific_folder(self, repo_status, current_folder, ignored_folders):
        status = pygit2.GIT_STATUS_CURRENT

        if len(repo_status) > 0 or len(ignored_folders) > 0:
            if current_folder in repo_status:
                if repo_status[current_folder] == pygit2.GIT_STATUS_IGNORED:
                    # folder is ignored, we can return at once
                    return pygit2.GIT_STATUS_IGNORED

            status_modified = [
                pygit2.GIT_STATUS_CONFLICTED,
                pygit2.GIT_STATUS_WT_TYPECHANGE,
                pygit2.GIT_STATUS_WT_MODIFIED,
                # we treat index changed status as regular wt changed status inside a folder
                pygit2.GIT_STATUS_INDEX_TYPECHANGE,
                pygit2.GIT_STATUS_INDEX_MODIFIED,
            ]

            try:
                values = repo_status.values()
                if any(status in status_modified for status in values):
                    status = pygit2.GIT_STATUS_WT_MODIFIED
                else:
                    wt_new_files = 0
                    index_new_files = 0
                    has_ignored_files = False
                    if pygit2.GIT_STATUS_WT_NEW in values:
                        wt_new_files = sum(i == pygit2.GIT_STATUS_WT_NEW for i in values)
                    if pygit2.GIT_STATUS_INDEX_NEW in values:
                        index_new_files = sum(i == pygit2.GIT_STATUS_INDEX_NEW for i in values)

                    if wt_new_files > 0 or index_new_files > 0 or len(ignored_folders) > 0:
                        files_in_folder = []
                        for current_path, subdirs, files in os.walk(self.path):
                            # do not consider ignored folders
                            current_path_relative = str(Path(current_path)
                                                        .relative_to(self.repo.workdir)) + "/"
                            if current_path_relative not in ignored_folders and not any(
                                current_path_relative
                                .endswith(ignored_folder) for ignored_folder in ignored_folders):
                                for file in files:
                                    full_file = current_path_relative + file
                                    if full_file not in ignored_folders:
                                        files_in_folder.append(current_path_relative + file)
                            else:
                                # ignore all subdirectories if current_path is ignored
                                subdirs.clear()
                                has_ignored_files = True

                        # only set new / ignored status if there are no other files in the folder
                        if wt_new_files + index_new_files == len(files_in_folder):
                            if index_new_files > 0:
                                status = pygit2.GIT_STATUS_INDEX_NEW
                            elif wt_new_files > 0:
                                status = pygit2.GIT_STATUS_WT_NEW
                            elif has_ignored_files:
                                status = pygit2.GIT_STATUS_IGNORED
            except Exception as _ex:
                pass
        else:
            content = os.listdir(current_folder)
            if len(content) == 0:
                status = pygit2.GIT_STATUS_INDEX_NEW

        return status


class TurtleService(dbus.service.Object):
    """ turtle dbus service """

    def __init__(self, mainloop, connection, object_path):
        dbus.service.Object.__init__(self, connection, object_path)
        self.mainloop = mainloop

    @dbus.service.method(TURTLE_APP_ID,
                         in_signature='s', out_signature='i')
    def status_for_path(self, path):
        """ calculate git status for path """
        try:
            checker = TurtleStatusChecker(path)
            status = checker.check_folder()
            return status
        except Exception as _ex:
            return -1

    @dbus.service.method(TURTLE_APP_ID,
                         out_signature='i')
    def get_pid(self):
        """ retrieve the pid of this process """
        return os.getpid()

    @dbus.service.method(TURTLE_APP_ID)
    def shutdown(self):
        """ exit the main loop and shutdown service """
        self.mainloop.quit()


class TurtleServiceConnector:
    """ turtle dbus service connector """

    session_bus = None

    def get_pid(self):
        """ get pid of current turtle service process """
        self._get_bus()

        turtle = self.session_bus.get_object(TURTLE_APP_ID, TURTLE_OBJECT_PATH)
        return turtle.get_pid(dbus_interface=TURTLE_APP_ID)

    def _get_bus(self):
        if self.session_bus is None:
            DBusGMainLoop(set_as_default=True)
            self.session_bus = dbus.SessionBus()

    def restart_service(self):
        """ restart the turtle service """
        try:
            self._get_bus()
            _turtle = self.session_bus.get_object(TURTLE_APP_ID, TURTLE_OBJECT_PATH)
        except dbus.exceptions.DBusException as _ex:
            _process = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE)

    def _status_for_path_from_service(self, path, reply_handler, error_handler, restart_service):
        try:
            turtle = self.session_bus.get_object(TURTLE_APP_ID, TURTLE_OBJECT_PATH)
            turtle.status_for_path(path,
                        dbus_interface=TURTLE_APP_ID,
                        reply_handler=reply_handler,
                        error_handler=error_handler)
        except dbus.DBusException as ex:
            print(f"dbus communication failed: {str(ex)}")
            if restart_service:
                self.restart_service()

    def shutdown(self):
        """ shutdown the turtle service """
        try:
            self._get_bus()
            turtle = self.session_bus.get_object(TURTLE_APP_ID, TURTLE_OBJECT_PATH)
            turtle.shutdown(dbus_interface=TURTLE_APP_ID)
        except dbus.DBusException as ex:
            print(f"dbus communication failed: {str(ex)}")


if __name__ == "__main__":
    DBusGMainLoop(set_as_default=True)
    _mainloop = GLib.MainLoop()
    _session_bus = dbus.SessionBus()
    _name = dbus.service.BusName(TURTLE_APP_ID, _session_bus)
    _object = TurtleService(_mainloop, _session_bus, TURTLE_OBJECT_PATH)
    _mainloop.run()
