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

import config
import platforms


def remove_non_existed_modules(camera_modules_dict):
    working_dir = os.path.dirname(os.path.abspath(__file__))
    verified_camera_modules = {}
    for camera_name,module in camera_modules_dict.items():
        if not module or os.path.exists(os.path.join(working_dir, module)):
            verified_camera_modules[camera_name] = module
    return verified_camera_modules


class CameraController:

    CAMERA_MODULES = { "Dual camera": "dual_cam.py",
                       "Multi camera": "multi_cam.py",
                       "Pi camera": "rpi_cam.py",
                       "Disable camera": None }

    CAMERA_MODULES = remove_non_existed_modules(CAMERA_MODULES)
    HD_CAMERA_NAME = "hdcamera" #NOTE this is dummy. Remove in production
    SUBPROC_STOP_TIMEOUT = 6

    def __init__(self, app):
        self.app = app
        self.logger = logging.getLogger(__name__)
        self.mac = app.user_login.http_connection.mac
        self.camera_process = None
        self.current_camera_name = "Disable camera"
        self.terminating_count = 0
        self.start_camera_process()

    def load_token(self):
        if config.get_settings()['protocol']['user_login']:
            self.token = self.app.user_login.user_token
        else:
            auth_tokens = self.app.user_login.load_printer_auth_tokens()
            if auth_tokens:
                self.token = auth_tokens[-1][1] #TODO add ability to get proper auth_token for each usb_info
            else:
                self.token = None

    def start_camera_process(self, camera_name=None, token=None):
        self.logger.info('Launching camera subprocess')
        if not token:
            self.load_token()
            token = self.token
        if not token:
            self.logger.info("No token to start the camera process")
            return False
        if not self.mac:
            self.mac = ""
        settings = config.get_settings()
        camera_name_default = settings['camera']['default']
        if not camera_name:
            camera_name = camera_name_default
        module_name = self.CAMERA_MODULES[camera_name]
        if camera_name_default != camera_name:
            settings['camera']['default'] = camera_name
            config.Config.instance().save_settings(settings)
        if not settings["camera"]["enabled"]:
            self.logger.info("Can't launch camera - disabled in config")
            return False
        if module_name:
            cam_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), module_name)
            start_command = [sys.executable, cam_path, token, self.mac]
            try:
                self.camera_process = subprocess.Popen(start_command, close_fds=True)
            except Exception as e:
                self.logger.warning('Could not launch camera due to error:\n' + str(e))
            else:
                self.current_camera_name = camera_name
                self.token = token
                self.logger.info('Camera started: ' + camera_name)
                return True

    def restart_camera(self, token=None):
        if self.current_camera_name != 'Disable camera':
            new_camera_name = self.current_camera_name
        else:
            new_camera_name = config.get_settings()['camera']['default']
        self.logger.info('Restarting camera module: ' + new_camera_name)
        self.stop_camera_process()
        return self.start_camera_process(new_camera_name)

    def get_current_camera_name(self):
        return self.current_camera_name

    def switch_camera(self, new_camera_name, token=None):
        if not token:
            token = self.token
        if not config.get_settings()['camera']['switch_type'] or not new_camera_name:
            new_camera_name = config.get_settings()['camera']['default']
        self.logger.info('Switching camera module from %s to %s' % (self.current_camera_name, new_camera_name))
        self.stop_camera_process()
        return self.start_camera_process(camera_name=new_camera_name, token=token)

    def stop_camera_process(self):
        self.logger.info('Terminating camera process...')
        counter = self.SUBPROC_STOP_TIMEOUT
        while counter and self.camera_process:
            try:
                self.camera_process.terminate()
                time.sleep(0.1)
                if self.camera_process.poll() != None:
                    self.camera_process = None
                    break
            except (OSError, AttributeError):
                self.camera_process = None
                break
            counter -= 1
            time.sleep(1)
        if self.camera_process:
            self.logger.info('Sending kill signal to camera process...')
            try:
                self.camera_process.kill()
            except:
                pass
            time.sleep(1) # give subprocess a time to go down
        self.logger.info('...camera process terminated.')
        self.current_camera_name = "Disable camera"
        self.camera_process = None
