#!/usr/bin/env python3
# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.
# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import os
import re
import sys
import tempfile
import subprocess
import zipfile

# fix of broken paths for windows
if not sys.path:
    sys.path = []
path = os.getcwd()
if path not in sys.path:
    sys.path.insert(0, path)

import paths
import platforms
from importlib import reload


windows_process_name = 'pythonw.exe'
unix_process_name_re = re.compile(r".*python.*(launcher\.py|app\.py)")
pid_file_name = "3dprinteros.pid"
tmp_path = tempfile.gettempdir()
pid_file_path = os.path.join(tmp_path, pid_file_name)
update_file_path = paths.UPDATE_FILE_PATH
post_update_script_file_name = 'post_update_script.py'


def install_update():
    print('Updating 3DPrinterOS')
    try:
        z = zipfile.ZipFile(update_file_path)
        z.extractall(os.path.dirname(os.path.realpath(__file__)))
        z.close()
    except zipfile.BadZipfile:
        os.remove(update_file_path)
        print('Update failed!\nReason: bad zip file')
        return True
    except Exception as e:
        print('Update failed!\nReason: error while extracting.\nDetails: ' + str(e))
    else:
        os.remove(update_file_path)
        print('Update successful!')
        reload(paths)
        reload(platforms)
        open(os.path.join(paths.CURRENT_SETTINGS_FOLDER, 'update_complete'), "w")
        if os.path.exists(post_update_script_file_name):
            subprocess.run([sys.executable, post_update_script_file_name])
            os.remove(post_update_script_file_name)
        return True


def run_command(command, **kwargs):
    process = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True, **kwargs)
    lines_list = process.stdout.split("\n")
    lines = [line for line in lines_list if len(line.split()) > 1]
    return lines


def get_nix_processes():
    if os.path.islink('/bin/ps'):
        # we are in busybox, so ps got no ax arguments, but they are no longer necessary
        return run_command(['ps'])
    else:
        return run_command(['ps', 'ax'])


def get_win_processes():
    return run_command(["tasklist", "/svc"], creationflags=subprocess.CREATE_NO_WINDOW)


def check_process_name_by_pid(pid):
    print("Checking process name")
    if sys.platform.startswith('win'):
        for task in get_win_processes():
            words = task.split()
            current_task_pid = words[1]
            if current_task_pid == pid:
                return words[0] == windows_process_name
    elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
        for task in get_nix_processes():
            words = task.split()
            if words[0] == pid and len(words) > 5:
                process = words[4] + words[5]
                return unix_process_name_re.match(process)
    else:
        raise RuntimeError("Your OS is not supported by 3DPrinterOS")


def get_process_list():
    print("Getting process list")
    if sys.platform.startswith('win'):
        processes = get_win_processes()
        pids = [x.split()[1] for x in processes]
    elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
        processes = get_nix_processes()
        pids = [x.split()[0] for x in processes]
    else:
        raise RuntimeError("Your OS is not supported by 3DPrinterOS")
    return pids


def execute_custom_args():
    if len(sys.argv) > 1:
        args = sys.argv[1:]
        if '--restore_default_settings' in args:
            import config
            os.remove(config.Config.USER_SETTINGS_FILE)
            print("Default settings were restored")
        if '--logout' in args:
            import user_login
            user_login.UserLogin.logout()
            print("Logout successful")
        if '--clear_logs' in args:
            import log
            log.clear_logs()
            print("Log files were erased")
        if '--erase_all_data' in args:
            for root, dirs, files in os.walk(paths.CURRENT_SETTINGS_FOLDER, topdown=False):
                for name in files:
                    os.remove(os.path.join(root, name))
                for name in dirs:
                    os.rmdir(os.path.join(root, name))
            os.rmdir(paths.CURRENT_SETTINGS_FOLDER)
            print("3DPrinterOS's data folder was erased")
        sys.exit(0)


def restart():
    print("Restarting 3DPrinterOS")
    python = sys.executable
    args = [python, python, __file__]
    if platforms.get_platform() == "win":
        args[1] = '"' + args[1] + '"'
        args[2] = '"' + args[2] + '"'
    os.execl(*args)


def run():
    if sys.version_info < (3,6,0):
        print("Error: your Python version is too old. Please use Python 3.6+")
        sys.exit(1)
    execute_custom_args()
    print("Launching 3DPrinterOS")
    with open(pid_file_path, "w") as f:
        f.write(str(os.getpid()))
    if os.path.exists(update_file_path):
        if not install_update():
            print("Update failed. Can't start.")
            return
    import app
    printeros_app = app.App()
    try:
        os.remove(pid_file_path)
    except FileNotFoundError:
        pass
    if not getattr(printeros_app, "poweroff_flag", False):
        if getattr(printeros_app, "restart_flag", False):
            restart()


def open_browser_tab():
    import app
    app.App.open_browser(force=True)


if __name__ == "__main__":
    #check_and_add_diag_autostart()
    try:
        with open(pid_file_path) as pid_file:
            pid = pid_file.read()
    except (IOError, OSError):
        pid = None
    if pid and pid in get_process_list() and check_process_name_by_pid(pid):
        print("3DPrinterOS is already running")
        open_browser_tab()
    else:
        run()
