#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# firefox_profile_runner.py
#
# Copyright (C) 2021-2022 Franco Masotti (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
#
# 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, see <http://www.gnu.org/licenses/>.
#
#
r"""A menu to run different Firefox profiles in sandboxes."""

import copy
import os
import shlex
import shutil
import sys

import fpyutils
import yaml
from yad import YAD


def ask_profile_question(profile_names: list, type: str, yad) -> str:
    prf: str
    profiles: list = copy.deepcopy(profile_names)
    profiles.sort()
    data: list = profiles
    text: str = 'Select a profile'
    window_title: str = 'Firefox profile selection'
    width: int = 640
    height: int = 480
    profile: str = None

    if type == 'entry':
        profile = yad.Entry(
            label=text,
            title=window_title,
            width=width,
            height=height,
            fixed=True,
            center=True,
            quoted=True,
            no_markup=True,
            use_completion='true',
            data=data,
        )
    elif type == 'list':
        data = [[x] for x in data]
        profile = yad.List(
            text=text,
            title=window_title,
            width=width,
            height=height,
            fixed=True,
            center=True,
            colnames=[("profile", "TEXT")],
            quoted=True,
            no_markup=True,
            data=data,
        )

    if profile is None:
        sys.exit(0)
    else:
        if type == 'entry':
            prf = profile
        else:
            prf = profile[0]

    return prf


def build_command(profile: dict, firefox_executable: str,
                  firejail_executable: str) -> str:
    command = str()

    firefox_command = firefox_executable + ' -P ' + shlex.quote(
        profile['firefox']['profile_name'])
    for o in profile['firefox']['options']:
        firefox_command += ' ' + shlex.quote(o)

    if profile['firejail']['enabled']:

        firejail_command = firejail_executable

        for o in profile['firejail']['options']:
            firejail_command += ' ' + shlex.quote(o)

        command = firejail_command + ' ' + firefox_command

    else:
        command += firefox_command

    return command


def show_configuration_error(error: str, yad):
    message = 'Check configuration or arguments'
    yad.execute(args=[
        '--title="Configuration error"', '--width=640', '--height=480',
        '--text="' + message + '\n\n' + repr(error) +
        '"', '--button="Ok:0"', '--no-markup'
    ])


def show_profile_message(profile: str, sandbox_enabled: bool, load: bool, yad):
    sandbox_status = 'disabled'
    if sandbox_enabled:
        sandbox_status = 'enabled'

    if load:
        message = 'Loading profile **' + profile + '** with sandbox **' + sandbox_status + '**. Please wait...'
        title = 'Loading Firefox profile'
    else:
        message = 'Quit profile **' + profile + '** with sandbox **' + sandbox_status + '**.'
        title = 'Exited from Firefox profile'

    yad.execute(args=[
        '--title="' + title + '"', '--width=640', '--height=480', '--text="' +
        message + '"', '--timeout=5', '--timeout-indicator=bottom',
        '--button="Ok:0"', '--no-markup'
    ])


def binaries_exist(binaries: dict) -> bool:
    binaries_present = False
    if (shutil.which(binaries['firefox']) is not None
            and shutil.which(binaries['firejail']) is not None):
        binaries_present = True

    return binaries_present


def check_configuration_structure(configuration: dict) -> bool:
    ok = True
    if ('binaries' in configuration and 'firefox' in configuration['binaries']
            and 'firejail' in configuration['binaries']
            and isinstance(configuration['binaries']['firefox'], str)
            and isinstance(configuration['binaries']['firejail'], str)):
        ok = True
    else:
        ok = False
    if (ok and 'message' in configuration
            and 'start' in configuration['message']
            and 'end' in configuration['message']
            and 'errors' in configuration['message']
            and isinstance(configuration['message']['start'], bool)
            and isinstance(configuration['message']['end'], bool)
            and isinstance(configuration['message']['errors'], bool)):
        ok = True
    else:
        ok = False
    if (ok and 'profile_list_type' in configuration
            and isinstance(configuration['profile_list_type'], str)
            and configuration['profile_list_type'] in ['entry', 'list']):
        ok = True
    else:
        ok = False
    if (ok and 'profiles' in configuration
            and isinstance(configuration['profiles'], dict)):

        # At least one profile must be present.
        if len(configuration['profiles']) <= 0:
            ok = False
        else:
            profiles = configuration['profiles']
            profiles_keys = list(profiles.keys())

            i = 0
            while ok and i < len(profiles_keys):
                prf = profiles[profiles_keys[i]]

                if not isinstance(prf, dict):
                    ok = ok & False

                if (ok and 'firefox' in prf and 'firejail' in prf
                        and 'profile_name' in prf['firefox']
                        and isinstance(prf['firefox']['profile_name'], str)
                        and 'options' in prf['firefox']
                        and isinstance(prf['firefox']['options'], list)
                        and 'enabled' in prf['firejail']
                        and isinstance(prf['firejail']['enabled'], bool)
                        and 'options' in prf['firejail']
                        and isinstance(prf['firejail']['options'], list)):
                    ok = ok & True

                    j = 0
                    options = prf['firefox']['options']
                    while ok and j < len(options):
                        if isinstance(options[j], str):
                            ok = ok & True
                        else:
                            ok = ok & False
                        j += 1

                    j = 0
                    options = prf['firejail']['options']
                    while ok and j < len(options):
                        if isinstance(options[j], str):
                            ok = ok & True
                        else:
                            ok = ok & False
                        j += 1
                else:
                    ok = ok & False

                i += 1
    else:
        ok = False

    return ok


if __name__ == '__main__':

    def main():
        yad = YAD()

        try:
            configuration_file = shlex.quote(sys.argv[1])
            try:
                config = yaml.load(open(configuration_file, 'r'),
                                   Loader=yaml.SafeLoader)
            except yaml.parser.ParserError as e:
                show_configuration_error(str(e), yad)
                sys.exit(1)

            if not check_configuration_structure(config):
                raise ValueError
            if not binaries_exist(config['binaries']):
                raise FileNotFoundError
        except (IndexError, FileNotFoundError, ValueError,
                FileNotFoundError) as e:
            if config['message']['errors']:
                show_configuration_error(str(e), yad)
            sys.exit(1)

        profile = ask_profile_question(list(config['profiles'].keys()),
                                       config['profile_list_type'], yad)
        command = build_command(config['profiles'][profile],
                                config['binaries']['firefox'],
                                config['binaries']['firejail'])

        pid = os.fork()
        if pid > 0:
            if config['message']['start']:
                show_profile_message(
                    profile,
                    config['profiles'][profile]['firejail']['enabled'], True,
                    yad)
        else:
            r = fpyutils.shell.execute_command_live_output(command)
            if config['message']['errors'] and r != 0:
                show_configuration_error('error: returned ' + str(r), yad)
            elif config['message']['end']:
                show_profile_message(
                    profile,
                    config['profiles'][profile]['firejail']['enabled'], False,
                    yad)
        try:
            pid, status = os.waitpid(pid, 0)
        except ChildProcessError as e:
            print(e)
            sys.exit(1)

    main()
