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

import config


class BaseGPIOInterface:

    def __init__(self):
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.info("GPIO interface check...")
        self.enabled = False
        self.loop_thread = None
        if config.get_settings().get('gpio').get('enabled'):
            self.init()

    def init(self):
        self.enabled = False
        self.logger.info("Enabling GPIO interface")
        self.active_buttons = []
        self.cancel_callbacks = []
        self.pull = config.get_settings().get('gpio').get('pull')
        self.bouncetime = config.get_settings().get('gpio').get('bouncetime', 200)
        self.trigger = config.get_settings().get('gpio').get('trigger')
        if self.setup_gpio(self.pull, self.trigger, self.bouncetime):
            buttons = config.get_settings()['gpio'].get('buttons', {})
            self.in_pins = {}
            for button in buttons:
                action_name = button
                pin_number = buttons[button]
                self.in_pins[pin_number] = {'action': action_name, 'handle': self.setup_input(pin_number, self.pull)}
                self.logger.info("GPIO event registered: pin%d=%s" % (pin_number, action_name))
            self.enabled = True
            self.start_loop_thread()
            self.logger.info("GPIO interface init success")

    def setup_gpio(self, pull_str=None, trigger_str=None, _=None):
        return False

    def setup_input(self, pin_number, pull):
        pass

    def read_level(self, pin_number):
        pass
    
    def start_loop_thread(self):
        if self.loop_thread and self.loop_thread.is_alive():
            self.enabled = False
            self.loop_thread.join()
            self.enabled = True
        self.loop_thread = threading.Thread(target=self.loop)
        self.loop_thread.start()

    def loop(self):
        self.prev_levels = {}
        self.event_detected = {}
        while self.enabled:
            for pin in self.in_pins:
                try:
                    level = self.read_level(pin)
                except RuntimeError:
                    self.logger.warning("GPIO error on reading input of pin: %d" % pin)
                else:
                    if pin in self.prev_levels:
                        prev_level = self.prev_levels[pin]
                        if self.trigger == 'falling' or self.trigger == 'both':
                            if not level and prev_level:
                                self.event_detected[pin] = True
                        if self.trigger == 'rising' or self.trigger == 'both':
                            if level and not prev_level:
                                self.event_detected[pin] = True
                    else:
                        self.prev_levels[pin] = level
            time.sleep(self.bouncetime/1000)

    def check_events(self, printer_interfaces):
        if self.enabled:
            for pin_num in self.in_pins:
                action_name = self.in_pins[pin_num]['action']
                if self.event_detected.get(pin_num):
                    self.event_detected[pin_num] = False
                    for pi in printer_interfaces:
                        self.run_method(pi, action_name)

    def run_method(self, pi, method_name):
        method = getattr(pi, method_name, None)
        if method:
            try:
                result = method()
            except Exception:
                self.logger.exception(f"GPIO event caused exception {method_name} for {pi}:")
            else:
                self.logger.info(f'GPIO event call {method_name} for {pi}. Result: {result}')
        else:
            self.logger.error("GPIO event error: no method %s" % method_name)

    def close(self):
        self.enabled = False

    def toggle(self):
        if self.enabled:
            self.close()
        else:
            self.init()
        settings = config.get_settings()
        settings['gpio']['enabled'] = self.enabled
        config.Config.instance().save_settings(settings)
