import inspect
import pprint

import sqlite3
from flask import make_response, g
import datetime
from datetime import datetime
from flask.views import MethodView
from flask_classy import FlaskView, route

from time import localtime, strftime
from functools import wraps, update_wrapper


from props import *

from hardware import *

import time
import uuid


class NotificationAPI(object):
    pass

class ActorAPI(object):

    def init_actors(self):
        self.app.logger.info("Init Actors")
        t = self.cache.get("actor_types")
        for key, value in t.iteritems():
            value.get("class").api = self
            value.get("class").init_global()

        for key in self.cache.get("actors"):
            self.init_actor(key)

    def init_actor(self, id):
        try:
            value = self.cache.get("actors").get(int(id))
            cfg = value.config.copy()
            cfg.update(dict(api=self, id=id, name=value.name))
            cfg.update(dict(api=self, id=id, name=value.name))
            clazz = self.cache.get("actor_types").get(value.type).get("class")
            value.instance = clazz(**cfg)
            value.instance.init()
            value.state = 0
            value.power = 100
        except Exception as e:
            self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name,
                        type="danger", timeout=None)
            self.app.logger.error("Initializing of Actor %s failed" % id)

    def switch_actor_on(self, id, power=None):
        actor = self.cache.get("actors").get(id)

        if actor.state == 1:
            return

        actor.instance.on(power=power)
        actor.state = 1
        if power is not None:

            actor.power = power
        self.emit("SWITCH_ACTOR", actor)

    def actor_power(self, id, power=100):
        actor = self.cache.get("actors").get(id)
        actor.instance.set_power(power=power)
        actor.power = power
        self.emit("SWITCH_ACTOR", actor)

    def switch_actor_off(self, id):
        actor = self.cache.get("actors").get(id)

        if actor.state == 0:
            return
        actor.instance.off()
        actor.state = 0
        self.emit("SWITCH_ACTOR", actor)

class SensorAPI(object):

    def init_sensors(self):
        '''
        Initialize all sensors
        :return: 
        '''

        self.app.logger.info("Init Sensors")

        t = self.cache.get("sensor_types")
        for key, value in t.iteritems():
            value.get("class").init_global()

        for key in self.cache.get("sensors"):
            self.init_sensor(key)

    def stop_sensor(self, id):

        try:
            self.cache.get("sensors").get(id).instance.stop()
        except Exception as e:

            self.app.logger.info("Stop Sensor Error")
            pass


    def init_sensor(self, id):
        '''
        initialize sensor by id
        :param id: 
        :return: 
        '''

        def start_active_sensor(instance):
            '''
            start active sensors as background job
            :param instance: 
            :return: 
            '''
            instance.execute()

        try:
            if id in self.cache.get("sensor_instances"):
                self.cache.get("sensor_instances").get(id).stop()
            value = self.cache.get("sensors").get(id)

            cfg = value.config.copy()
            cfg.update(dict(api=self, id=id, name=value.name))
            clazz = self.cache.get("sensor_types").get(value.type).get("class")
            value.instance = clazz(**cfg)
            value.instance.init()
            if isinstance(value.instance, SensorPassive):
                # Passive Sensors
                value.mode = "P"
            else:
                # Active Sensors
                value.mode = "A"
                t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance)

        except Exception as e:

            self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None)
            self.app.logger.error("Initializing of Sensor %s failed" % id)

    def receive_sensor_value(self, id, value):
        self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id])
        self.save_to_file(id, value)

    def save_to_file(self, id, value, prefix="sensor"):
        filename = "./logs/%s_%s.log" % (prefix, str(id))
        formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
        msg = str(formatted_time) + "," +str(value) + "\n"

        with open(filename, "a") as file:
            file.write(msg)

    def log_action(self, text):
        filename = "./logs/action.log"
        formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
        with open(filename, "a") as file:
            text = text.encode("utf-8")
            file.write("%s,%s\n" % (formatted_time, text))

    def shutdown_sensor(self, id):
        self.cache.get("sensors")[id].stop()


    def get_sensor_value(self, id):
        try:
            id = int(id)
            return float(self.cache.get("sensors")[id].instance.last_value)
        except Exception as e:

            return None

class CacheAPI(object):

    def get_sensor(self, id):
        try:
            return self.cache["sensors"][id]
        except:
            return None

    def get_actor(self, id):
        try:
            return self.cache["actors"][id]
        except:
            return None

class CraftBeerPi(ActorAPI, SensorAPI):

    cache = {
        "init": {},
        "config": {},
        "actor_types": {},
        "sensor_types": {},
        "sensors": {},
        "sensor_instances": {},
        "init": [],
        "background":[],
        "step_types": {},
        "controller_types": {},
        "messages": [],
        "plugins": {},
        "fermentation_controller_types": {},
        "fermenter_task": {}
    }
    buzzer = None
    eventbus = {}


    # constructor
    def __init__(self, app, socketio):
        self.app = app
        self.socketio = socketio


    def emit(self, key, data):
        self.socketio.emit(key, data, namespace='/brew')

    def notify(self, headline, message, type="success", timeout=5000):
        self.beep()
        msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
        self.emit_message(msg)

    def beep(self):
        if self.buzzer is not None:
            self.buzzer.beep()


    def add_cache_callback(self, key,  method):
        method.callback = True
        self.cache[key] = method

    def get_config_parameter(self, key, default):
        cfg = self.cache.get("config").get(key)

        if cfg is None:
            return default
        else:
            return cfg.value

    def set_config_parameter(self, name, value):
        from modules.config import Config
        with self.app.app_context():
            update_data = {"name": name, "value": value}
            self.cache.get("config")[name].__dict__.update(**update_data)
            c = Config.update(**update_data)
            self.emit("UPDATE_CONFIG", c)


    def add_config_parameter(self, name, value, type, description, options=None):
        from modules.config import Config
        with self.app.app_context():
            c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options})
            if self.cache.get("config") is not None:
                self.cache.get("config")[c.name] = c

    def clear_cache(self, key, is_array=False):
        if is_array:
            self.cache[key] = []
        else:
            self.cache[key] = {}

    # helper method for parsing props
    def __parseProps(self, key, cls):
        name = cls.__name__
        self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
        tmpObj = cls()
        members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
        for m in members:
            if isinstance(tmpObj.__getattribute__(m), Property.Number):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append(
                    {"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), Property.Text):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append(
                    {"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), Property.Select):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append(
                    {"name": m, "label": t.label, "type": "select",  "configurable": True, "options": t.options, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor",  "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})

        for name, method in cls.__dict__.iteritems():
            if hasattr(method, "action"):
                label = method.__getattribute__("label")
                self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})


        return cls


    def actor(self, cls):
        return self.__parseProps("actor_types", cls)



    def actor2(self, description="", power=True, **options):

        def decorator(f):
            print f()
            print f
            print options
            print description
            return f
        return decorator

    def sensor(self, cls):
        return self.__parseProps("sensor_types", cls)

    def controller(self, cls):
        return self.__parseProps("controller_types", cls)

    def fermentation_controller(self, cls):
        return self.__parseProps("fermentation_controller_types", cls)

    def get_controller(self, name):
        return self.cache["controller_types"].get(name)

    def get_fermentation_controller(self, name):
        return self.cache["fermentation_controller_types"].get(name)


    # Step action
    def action(self,label):
        def real_decorator(func):
            func.action = True
            func.label = label
            return func
        return real_decorator

    # step decorator
    def step(self, cls):

        key = "step_types"
        name = cls.__name__
        self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}

        tmpObj = cls()
        members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
        for m in members:
            if isinstance(tmpObj.__getattribute__(m), StepProperty.Number):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "default_value": t.default_value, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor",  "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
            elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle):
                t = tmpObj.__getattribute__(m)
                self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})

        for name, method in cls.__dict__.iteritems():
            if hasattr(method, "action"):
                label = method.__getattribute__("label")
                self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})

        return cls


    # Event Bus
    def event(self, name, async=False):

        def real_decorator(function):
            if self.eventbus.get(name) is None:
                self.eventbus[name] = []
            self.eventbus[name].append({"function": function, "async": async})
            def wrapper(*args, **kwargs):
                return function(*args, **kwargs)
            return wrapper
        return real_decorator

    def emit_message(self, message):
        self.emit_event(name="MESSAGE", message=message)

    def emit_event(self, name, **kwargs):
        for i in self.eventbus.get(name, []):
            if i["async"] is False:
                i["function"](**kwargs)
            else:
                t = self.socketio.start_background_task(target=i["function"], **kwargs)

    # initializer decorator
    def initalizer(self, order=0):
        def real_decorator(function):
            self.cache["init"].append({"function": function, "order": order})
            def wrapper(*args, **kwargs):
                return function(*args, **kwargs)
            return wrapper
        return real_decorator



    def try_catch(self, errorResult="ERROR"):
        def real_decorator(function):
            def wrapper(*args, **kwargs):
                try:
                    return function(*args, **kwargs)
                except:
                    self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult))
                    return errorResult
            return wrapper

        return real_decorator

    def nocache(self, view):
        @wraps(view)
        def no_cache(*args, **kwargs):
            response = make_response(view(*args, **kwargs))
            response.headers['Last-Modified'] = datetime.now()
            response.headers[
                'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
            response.headers['Pragma'] = 'no-cache'
            response.headers['Expires'] = '-1'
            return response

        return update_wrapper(no_cache, view)

    def init_kettle(self, id):
        try:
            value = self.cache.get("kettle").get(id)
            value["state"] = False
        except:
            self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None)
            self.app.logger.error("Initializing of Kettle %s failed" % id)


    def run_init(self):
        '''
        call all initialziers after startup
        :return: 
        '''
        self.app.logger.info("Invoke Init")
        self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order'])
        for i in self.cache.get("init"):
            self.app.logger.info("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
            i.get("function")(self)



    def backgroundtask(self, key, interval, config_parameter=None):

        '''
        Background Task Decorator
        :param key: 
        :param interval: 
        :param config_parameter: 
        :return: 
        '''
        def real_decorator(function):
            self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter})
            def wrapper(*args, **kwargs):
                return function(*args, **kwargs)
            return wrapper
        return real_decorator

    def run_background_processes(self):
        '''
        call all background task after startup
        :return: 
        '''
        self.app.logger.info("Start Background")

        def job(interval, method):
            while True:
                try:
                    method(self)
                except Exception as e:
                    self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
                self.socketio.sleep(interval)


        for  value in self.cache.get("background"):
            t = self.socketio.start_background_task(target=job,  interval=value.get("interval"),  method=value.get("function"))
