from core.Constants import Constants
from core.controllers.SessionStateController import SessionStateController
from core.models.session.Application import Application
from core.models.session.ApplicationVersion import ApplicationVersion
from core.models.session.SessionProfile import SessionProfile
from core.models.session.SessionState import SessionState
from core.observers.ProfileObserver import ProfileObserver
from core.services.WebServiceApiService import WebServiceApiService
from pathlib import Path
from typing import Optional
import os
import re
import shutil
import stat
import subprocess


class ApplicationController:

    @staticmethod
    def get(code: str):
        return Application.find(code)

    @staticmethod
    def get_all():
        return Application.all()

    @staticmethod
    def launch(version: ApplicationVersion, profile: SessionProfile, port_number: int = None, profile_observer: Optional[ProfileObserver] = None):

        persistent_state_path = f'{profile.get_data_path()}/persistent-state'

        if not os.path.isdir(persistent_state_path) or len(os.listdir(persistent_state_path)) == 0:
            shutil.copytree(f'{version.get_installation_path()}/resources/initial-state', persistent_state_path)

        display = ApplicationController.__find_unused_display()

        base_initialization_file_template = open(f'/{Constants.SP_DATA_HOME}/.init.ptpl', 'r').read()
        base_initialization_file_contents = base_initialization_file_template.format(display=display, time_zone=profile.location.time_zone, sp_data_home=Constants.SP_DATA_HOME)

        application_initialization_file_template = open(f'/{version.get_installation_path()}/.init.ptpl', 'r').read()
        application_initialization_file_contents = application_initialization_file_template.format(application_version_home=version.get_installation_path(), port_number=port_number or -1, home=Constants.HOME, profile_data_path=profile.get_data_path(), config_home=Constants.CONFIG_HOME, application_version_number=version.version_number)

        session_state = SessionStateController.get_or_new(profile.id)
        SessionStateController.update_or_create(session_state)

        initialization_file_contents = base_initialization_file_contents + application_initialization_file_contents
        initialization_file_path = f'{session_state.get_state_path()}/.init'

        Path(initialization_file_path).touch(exist_ok=True, mode=0o600 | stat.S_IEXEC)

        with open(initialization_file_path, 'w') as initialization_file:

            initialization_file.write(initialization_file_contents)
            initialization_file.close()

        fork_process_id = os.fork()

        if not fork_process_id:

            process = subprocess.Popen(('xinit', initialization_file_path, '--', '/usr/bin/Xephyr', '-ac', '-title', f'Simplified Privacy - {profile.name or "Unnamed Profile"}', '-screen', profile.resolution, '-no-host-grab', display), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)

            session_state = SessionState(session_state.id, session_state.network_port_numbers, [process.pid])
            SessionStateController.update_or_create(session_state)

            os.waitpid(process.pid, 0)

            from core.controllers.ProfileController import ProfileController
            ProfileController.disable(profile, False, profile_observer=profile_observer)

    @staticmethod
    def _sync(proxies: Optional[dict] = None):

        applications = WebServiceApiService.get_applications(proxies)

        Application.truncate()
        Application.save_many(applications)

    @staticmethod
    def __find_unused_display():

        file_names = os.listdir('/tmp/.X11-unix')
        active_displays = []

        for file_name in file_names:

            match_object = re.search(r'X(\d+)', file_name)

            if match_object:

                detected_display = int(match_object.group(1))

                if 170 <= detected_display <= 198:
                    active_displays.append(detected_display)

        if len(active_displays) > 0:

            unused_display = sorted(active_displays)[-1] + 1
            return f':{str(unused_display)}'

        else:
            return ':170'