#
# Copyright © 2023 Red Hat, Inc
#
# 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 2 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., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
#   Olivier Fourdan <ofourdan@redhat.com>
#

""" Common routines for wlheadless modules """

import configparser
import importlib
import os
import subprocess
import socket
from shutil import rmtree
from signal import signal, SIGTERM, SIG_IGN
from tempfile import mkdtemp
from time import sleep


class WlheadlessCommon:

    """
    Common routines for wlheadless
    """


    def __call__(self):
        return self


    def __init__(self):
        try:
            os.setpgrp()
        except Exception as error:
            print(f'Warning, failed to create process group: {error}\n')
        self.xdg_runtime_dir = None
        if not os.getenv('XDG_RUNTIME_DIR'):
            self.xdg_runtime_dir = mkdtemp()
            os.environ['XDG_RUNTIME_DIR'] = self.xdg_runtime_dir


    def get_command_output(self, command):
        try:
            output = subprocess.check_output(command)
        except Exception as error:
            print(f'Failed to run command: {error}\n')
            return []
        return (output.decode()).split()


    def load_compositor(self, compositor):
        try:
            module = importlib.import_module('wlheadless.' + compositor)
            Wlheadless = getattr(module, 'Wlheadless')
            return Wlheadless()
        except ModuleNotFoundError as error:
            print(f'Cannot load modules: {error}\n')
            return None


    def get_compositor(self):
        """ Read the configuration for the default compositor """
        dirs = []
        home = os.getenv('HOME', '')
        xdg_config_home = home + '/.config'
        xdg_data_home = home + '/.local/share'
        xdg_data_dirs = '/usr/local/share/:/usr/share/'
        dirs.extend(os.getenv('XDG_CONFIG_HOME', xdg_config_home).split(':'))
        dirs.extend(os.getenv('XDG_DATA_HOME', xdg_data_home).split(':'))
        dirs.extend(os.getenv('XDG_DATA_DIRS', xdg_data_dirs).split(':'))
        config = configparser.ConfigParser()
        for d in dirs:
            if config.read(os.path.join(d, 'wlheadless', 'wlheadless.conf')):
                break
        return config.get('DEFAULT', 'Compositor', fallback = 'weston')


    def run_command(self, command):
        """ Helper function to spawn a command """
        try:
            with subprocess.Popen(command) as proc:
                proc.wait()
                return proc.returncode
        except Exception as error:
            print(f'Failed to run the command: {error}\n')
            return -1
        except KeyboardInterrupt:
            return 0


    def __try_connect(self, display = None):
        """ Try opening a connection with the Wayland compositor """
        if not display:
            display = os.getenv('WAYLAND_DISPLAY')
        path = os.path.join(os.getenv('XDG_RUNTIME_DIR'), display)
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
        sock.connect(path)
        sock.close()


    def wait_compositor(self):
        """ Waits for the compositor to start """
        for _ in range(0, 150):
            try:
                self.__try_connect()
                return 0
            except Exception:
                sleep(1)
        print('Failed to connect to the compositor!\n')
        return -1


    def run_compositor(self, compositor):
        """ Starts the Wayland compositor """
        try:
            proc = subprocess.Popen(compositor)
            return proc.pid
        except Exception as error:
            print(f'Failed to start the compositor: {error}\n')
            return -1
        except KeyboardInterrupt:
            return -1


    def __cleanup_tempdir(self):
        """ Removes our temporary XDG_RUNTIME_DIR directory if empty. """
        if self.xdg_runtime_dir:
            try:
                os.waitpid(-1, 0)
            except ChildProcessError:
                pass
            try:
                rmtree(self.xdg_runtime_dir)
            except OSError as error:
                print(f'Error removing directory {self.xdg_runtime_dir}: {error}\n')


    def cleanup(self):
        """ Clean-up function """
        handler = signal(SIGTERM, SIG_IGN)
        try:
            os.killpg(os.getpgid(0), SIGTERM)
        except Exception as error:
            print(f'Warning, failed to kill process group: {error}\n')
        signal(SIGTERM, handler)
        self.__cleanup_tempdir()
