# 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 sys
import time
import logging
import subprocess

from awaitable import Awaitable

# Getting Makerbot service named Conveyor from MakerWare killed.
# It binds COM-ports to itself even for non-makerbot printers and we can not work

def detect_makerware_paths():
    logger = logging.getLogger(__name__)
    makerware_path = None
    if sys.platform.startswith('linux'):
        paths = ['/usr/share/makerbot/', '/usr/local/share/makerbot/']
        for path in paths:
            if os.path.isdir(path):
                makerware_path = path
    elif sys.platform.startswith('darwin'):
        darwin_default_path = '/Library/MakerBot/'
        if os.path.isdir(darwin_default_path):
            makerware_path = darwin_default_path
    elif sys.platform.startswith('win'):
        import winreg
        try:
            key = winreg.OpenKey('HKEY_LOCAL_MACHINE', 'SOFTWARE\\MakerBot\\MakerWare')
            makerware_path = str(winreg.QueryValueEx(key, 'InstallPath')[0])
        except Exception as e:
            print("No conveyor installed or some other winreg error" + str(e))
    else:
        raise EnvironmentError('Error. Undetectable or unsupported OS. \
                               Only GNU/LINUX, MAC OS X and MS Windows are supported.')
    if not makerware_path:
        logger.info('Could not define makerware path')
    return makerware_path

def get_conveyor_pid():
    conveyor_pid = None
    if sys.platform.startswith('win'):
        stdout = subprocess.run(['tasklist', '/svc'], stdout=subprocess.PIPE, universal_newlines=True).stdout
        tasks = stdout.split('\n')
        for task in tasks:
            # TODO: Second condition need tests on win with our soft(if script argument in split[0] and check backslash magic)
            try:
                if task.startswith('conveyor-svc.exe') or task.split()[0].endswith('/conveyor/server/__main__.py'):
                    conveyor_pid = task.split()[1]
                    # print conveyor_pid
                    # print task
            except IndexError:
                pass
    elif sys.platform.startswith('darwin'):
        stdout = subprocess.run(['ps', 'ax'], stdout=subprocess.PIPE, universal_newlines=True).stdout
        tasks = stdout.split('\n')
        for task in tasks:
            # TODO: make conveyor service die on linux with makerware
            try:
                if task.split()[4].endswith('conveyor-svc') or task.split()[5].endswith('/conveyor/server/__main__.py'):
                    conveyor_pid = task.split()[0]
                    # print conveyor_pid
                    # print task
            except IndexError:
                pass
    elif sys.platform.startswith('linux'):
        conveyor_pid = []  # There are 2 processes for conveyor at linux, so we should return both
        stdout = subprocess.run(['ps', 'ax'], stdout=subprocess.PIPE, universal_newlines=True).stdout
        tasks = stdout.split('\n')
        for task in tasks:
            if 'conveyor-svc' in task:
                conveyor_pid.append(task)  # Adding whole tasks to parse them later, for chmod path finding in killing function
    return conveyor_pid

# Getting Makerbot service named Conveyor from MakerWare killed.
# It binds COM-ports to itself even for non-makerbot printers and we can not work
def kill_existing_conveyor():
    wait_count = 5
    sleep_time = 1
    logger = logging.getLogger(__name__)
    pid = get_conveyor_pid()
    if pid:
        logger.info('Makerbot conveyor service is running. Shutting down...')
        if sys.platform.startswith('win'):
            subprocess.Popen(['taskkill', '/f', '/pid ' + pid])
        # At linux we have very bad and unfriendly conveyor behaviour, so little magic here
        elif sys.platform.startswith('linux'):
            pids = []
            conveyor_svc_path = None
            for id in pid:  # List of processes here, should be 2 processes as usual
                id = id.split()
                # if we get 'sudo' word in process string from 'ps ax|grep conveyor', then it means we got this string format:
                # sudo -u conveyor LD_LIBRARY_PATH=/usr/lib/makerbot/ /usr/bin/conveyor-svc --config /etc/conveyor.conf
                # this is the one of two conveyor processes, the second is like
                # /usr/bin/conveyor-svc --config /etc/conveyor.conf
                # The processes have respawn flag, so you could not just kill them.
                if 'sudo' in id:  # Convenient task string for parsing conveyor-svc path
                    conveyor_svc_path = id[8]  # '/usr/bin/conveyor-svc' string position
                    if conveyor_svc_path.startswith('/') and conveyor_svc_path.endswith('conveyor-svc'):
                        logger.info('Got conveyor service path: {0}. Applying "chmod -x"'.format(conveyor_svc_path))
                pids.append(id[0])  # Get pids to kill
            pids_sting = ' '.join(pids)
            # to kill it, we need to forbid executable rights for file /usr/bin/conveyor-svc
            # and then kill these two processes. For now it will stop conveyor for forever.
            # However you can turn in on again by executing these commands:
            # sudo chmod +x /usr/bin/conveyor-svc
            # sudo -u conveyor LD_LIBRARY_PATH=/usr/lib/makerbot/ /usr/bin/conveyor-svc --config /etc/conveyor.conf &
            # This process also will start second conveyor process and all should be fine
            if conveyor_svc_path and pids_sting:
                command = 'sudo chmod -x %s && sudo kill -9 %s' % (conveyor_svc_path, pids_sting)
                p = subprocess.Popen('xterm -e "{0}"'.format(command), shell=True)
                while p.poll() is None:  # Returns 0 when finished
                    time.sleep(0.1)
                # Returned code is 0 either user closes the console or enters pass.
                # But if console was closed, message and button to kill still on place and can be done again
                logger.info('Xterm process returned code: ') + str(p.returncode)
            else:
                logger.info('Cannot get conveyor path or pids:\nconveyor_path: {0}\nconveyor_pids: {1}'.format(str(conveyor_svc_path), str(pids)))
        elif sys.platform.startswith('darwin'):
            makerware_path = detect_makerware_paths()
            command = os.path.join(makerware_path, 'stop_conveyor_service')
            command_to_stop = "osascript -e '" + 'do shell script "sudo ' + command + '" with administrator privileges' + "'"
            os.popen(command_to_stop)
        for i in range(wait_count):
            if get_conveyor_pid():
                logger.info('Conveyor still alive, awaiting %s time' % str(i + 1))
                time.sleep(sleep_time)
            else:
                logger.info('Makerbot Conveyor Service successfully killed.')
                return True
        logger.info('Could not kill Makerbot Conveyor Service. Please stop it manually and restart program.')

class ConveyorKillWaiter(Awaitable):

    NAME_FOR_LOGGING = 'Makerware killer'

    def __init__(self, app):
        Awaitable.__init__(self, app)

    def check_function(self):
        return not bool(get_conveyor_pid())

    def kill(self):
        logging.getLogger(__name__).info('Attempting to stop Makerbot conveyor service')
        kill_existing_conveyor()
