import time
from flask import request
from flask_classy import route

from modules import DBModel, cbpi, get_db
from modules.core.baseview import BaseView


class Fermenter(DBModel):
    __fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler",  "logic",  "config",  "target_temp"]
    __table_name__ = "fermenter"
    __json_fields__ = ["config"]

class FermenterStep(DBModel):
    __fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"]
    __table_name__ = "fermenter_step"

    @classmethod
    def get_by_fermenter_id(cls, id):
        cur = get_db().cursor()
        cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,))
        result = []
        for r in cur.fetchall():
            result.append(cls(r))
        return result

    @classmethod
    def get_max_order(cls,id):
        cur = get_db().cursor()
        cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,))
        r = cur.fetchone()
        return r.get("order")

    @classmethod
    def update_state(cls, id, state):
        cur = get_db().cursor()
        cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
        get_db().commit()

    @classmethod
    def update_timer(cls, id, timer):
        cur = get_db().cursor()
        cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id))
        get_db().commit()

    @classmethod
    def get_by_state(cls, state):
        cur = get_db().cursor()
        cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
        r = cur.fetchone()
        if r is not None:
            return cls(r)
        else:
            return None

    @classmethod
    def reset_all_steps(cls,id):
        cur = get_db().cursor()
        cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
        get_db().commit()

class FermenterView(BaseView):
    model = Fermenter
    cache_key = "fermenter"


    def _post_post_callback(self, m):
        m.state = False
        m.steps = []

    def _pre_put_callback(self, m):
        m.state = False
        try:
            m.instance.stop()
        except:
            pass

    def _post_put_callback(self, m):
        m.state = False

    @route('/<int:id>/targettemp/<temp>', methods=['POST'])
    def postTargetTemp(self, id, temp):
        if temp is None or not temp:
            return ('', 500)
        id = int(id)
        temp = float(temp)
        cbpi.cache.get(self.cache_key)[id].target_temp = float(temp)
        self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
        cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp})
        return ('', 204)

    @route('/<int:id>/brewname', methods=['POST'])
    def postBrewName(self, id):
        data = request.json
        brewname = data.get("brewname")
        cbpi.cache.get(self.cache_key)[id].brewname = brewname
        self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
        cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname})
        return ('', 204)

    @classmethod
    def post_init_callback(cls, obj):
        obj.steps = FermenterStep.get_by_fermenter_id(obj.id)
        obj.state = False

    @route('/<int:id>/step', methods=['POST'])
    def postStep(self, id):
        data = request.json
        order_max = FermenterStep.get_max_order(id)
        order = order_max + 1 if order_max is not None else 1
        data["order"] = order
        data["days"] = 0 if data["days"] == "" else data["days"]
        data["hours"] = 0 if data["hours"] == "" else data["hours"]
        data["minutes"] = 0 if data["minutes"] == "" else data["minutes"]
        data["temp"] = 0 if data["temp"] == "" else data["temp"]
        data["state"] = "I"
        data["name"] = "NO NAME" if data["name"] == "" else data["name"]
        f = FermenterStep.insert(**data)

        cbpi.cache.get(self.cache_key)[id].steps.append(f)

        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])

        return ('', 204)

    @route('/<int:id>/step/<int:stepid>', methods=["PUT"])
    def putStep(self, id, stepid):
        data = request.json
        # Select modal

        data["id"] = stepid
        data["fermenter_id"] = id

        data["days"] = 0 if data["days"] == "" else data["days"]
        data["hours"] = 0 if data["hours"] == "" else data["hours"]
        data["minutes"] = 0 if data["minutes"] == "" else data["minutes"]

        for s in cbpi.cache.get(self.cache_key)[id].steps:
            if s.id == stepid:
                s.__dict__.update(**data)

                FermenterStep.update(**s.__dict__)
                break
        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
        return ('', 204)

    @route('/<int:id>/step/<int:stepid>', methods=["DELETE"])
    def deleteStep(self, id, stepid):

        for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
            if s.id == stepid:
                del cbpi.cache.get(self.cache_key)[id].steps[idx]
                FermenterStep.delete(s.id)
                break
        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
        return ('', 204)

    @route('/<int:id>/start', methods=['POST'])
    def start_fermentation(self, id):
        active = None
        for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
            if s.state == 'A':
                active = s
                break

        inactive = None
        for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
            if s.state == 'I':
                inactive = s
                break

        if active is not None:

            active.state = 'D'
            active.end = time.time()
            FermenterStep.update(**active.__dict__)

            del cbpi.cache["fermenter_task"][id]

        if inactive is not None:
            fermenter = self.get_fermenter(inactive.fermenter_id)
            current_temp = cbpi.get_sensor_value(int(fermenter.sensor))

            inactive.state = 'A'
            inactive.start = time.time()
            inactive.direction = "C" if current_temp >= inactive.temp else "H"
            FermenterStep.update(**inactive.__dict__)

            self.postTargetTemp(id, inactive.temp)

            cbpi.cache["fermenter_task"][id] = inactive

        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
        return ('', 204)

    @route('/<int:id>/reset', methods=["POST"])
    def reset(self, id):
        FermenterStep.reset_all_steps(id)

        cbpi.cache[self.cache_key][id].steps = FermenterStep.get_by_fermenter_id(id)

        if id in cbpi.cache["fermenter_task"]:
            del cbpi.cache["fermenter_task"][id]

        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
        return ('', 204)

    @route('/<int:id>/automatic', methods=['POST'])
    def toggle(self, id):
        fermenter = cbpi.cache.get(self.cache_key)[id]
        try:
            print fermenter.state
            if fermenter.state is False:
                # Start controller
                if fermenter.logic is not None:
                    cfg = fermenter.config.copy()
                    cfg.update(
                        dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor))
                    instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg)
                    instance.init()
                    fermenter.instance = instance

                    def run(instance):
                        instance.run()

                    t = cbpi.socketio.start_background_task(target=run, instance=instance)
                fermenter.state = not fermenter.state
                cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
            else:
                # Stop controller
                fermenter.instance.stop()
                fermenter.state = not fermenter.state
                cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))

        except Exception as e:
            print e
            cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name,
                        type="danger", timeout=None)
            return ('', 500)

        return ('', 204)

    def get_fermenter(self, id):
        return cbpi.cache["fermenter"].get(id)

    def target_temp_reached(self,id, step):
        timestamp = time.time()

        days = step.days * 24 * 60 * 60
        hours = step.hours * 60 * 60
        minutes = step.minutes * 60
        target_time = days + hours + minutes + timestamp

        FermenterStep.update_timer(step.id, target_time)

        step.timer_start = target_time

        cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])

    def check_step(self):
        for key, value in cbpi.cache["fermenter_task"].iteritems():

            try:
                fermenter = self.get_fermenter(key)
                current_temp = current_temp = cbpi.get_sensor_value(int(fermenter.sensor))

                if value.timer_start is None:

                    if value.direction == "H" :

                        if current_temp >= value.temp:
                            self.target_temp_reached(key,value)
                    else:
                        if current_temp <= value.temp:
                            self.target_temp_reached(key, value)
                else:
                    if time.time() >= value.timer_start:
                        self.start_fermentation(key)
                    else:
                        pass
            except Exception as e:
                pass


@cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5)
def read_target_temps(api):
    """
    background process that reads all passive sensors in interval of 1 second
    :return: None
    """
    result = {}
    for key, value in cbpi.cache.get("fermenter").iteritems():
        cbpi.save_to_file(key, value.target_temp, prefix="fermenter")


instance = FermenterView()

@cbpi.backgroundtask(key="fermentation_task", interval=1)
def execute_fermentation_step(api):
    with cbpi.app.app_context():
        instance.check_step()


def init_active_steps():
    '''
    active_steps = FermenterStep.query.filter_by(state='A')
    for a in active_steps:
        db.session.expunge(a)
        cbpi.cache["fermenter_task"][a.fermenter_id] = a
    '''

@cbpi.initalizer(order=1)
def init(cbpi):

    FermenterView.register(cbpi.app, route_base='/api/fermenter')
    FermenterView.init_cache()
