#!/usr/bin/env python3
# vim: ts=2 sw=2 et

import grp
import json
import os
import stat
import subprocess
import threading
import time
from configparser import ConfigParser
from dataclasses import dataclass

import RPi.GPIO as gpio
import paho.mqtt.client as mqtt


class FoodoorMQTT:
    def __init__(self, area):
        self.area = area
        self.client = mqtt.Client()
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message

        self._last_state = None
        self._connect_lock = threading.Condition()

    def connect(self):
        try:
            self.client.connect("mqtt.chaospott.de")
            self.client.loop_start()
            with self._connect_lock:
                self._connect_lock.wait()
        except Exception as e:
            print(f"Verbindungsfehler zu MQTT-Server: {e}")

    def disconnect(self):
        self.client.loop_stop()

    def on_connect(self, client, userdata, flags, rc):
        if self._last_state is not None:
            self.send_state(self._last_state)
        with self._connect_lock:
            self._connect_lock.notify()

    def on_message(self, client, userdata, msg):
        print(f"MQTT-Server Message: {msg.topic} {msg.payload}")

    def send_state(self, locked: bool):
        self._last_state = locked
        self.client.publish(f"foobar/{self.area}/foodoor/status", {
            False: "open",
            True: "closed",
        }[locked], qos=0, retain=True)


# Definitions for output
PIN_OPEN = 24
PIN_CLOSE = 27

# Read config
parser = ConfigParser()
parser.read('/etc/foodoord.conf')


@dataclass
class API:
    location: str
    api_url: str
    consumer_key: str
    consumer_secret: str

    def update_state(self, locked):
        subprocess.check_call([
            "/usr/bin/curl", "-XPOST",
            "--header", "Content-Type: application/json",
            "--data",
            json.dumps({"consumer_key": self.consumer_key, "consumer_secret": self.consumer_secret, self.location: locked}),
            self.api_url
        ])


MQTT = FoodoorMQTT("unten")
APIv1 = API("cellar",
    parser.get('doorstatus', 'status_url'),
    parser.get('doorstatus', 'key'),
    parser.get('doorstatus', 'secret'),
)
APIv2 = API("cellar",
    parser.get('doorstatusv2', 'status_url'),
    parser.get('doorstatusv2', 'key'),
    parser.get('doorstatusv2', 'secret'),
)


def write_state(state):
    try:
        with open("/tmp/door_state", "w") as f:
            f.write(state)
    except:
        pass


def update_api(locked):
    try:
        MQTT.send_state(locked)
    except:
        pass
    try:
        APIv1.update_state(locked)
    except:
        pass
    try:
        APIv2.update_state(locked)
    except:
        pass


def main():
    # Start settings
    gpio.setmode(gpio.BCM)
    gpio.setup(PIN_OPEN, gpio.OUT)
    gpio.setup(PIN_CLOSE, gpio.OUT)

    # Setting up FiFo to get sshd-output
    try:
        os.mkfifo("/var/run/foodoord.pipe")
        os.chown("/var/run/foodoord.pipe", -1, grp.getgrnam('foodoor')[2])
        os.chmod("/var/run/foodoord.pipe", stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP)
    except OSError:
        pass

    MQTT.connect()

    ssh_input = open("/var/run/foodoord.pipe", "r")
    while True:
        # Read sshd output from pipe
        pipe_cmd = ssh_input.readline().strip()

        if pipe_cmd == "close":
            gpio.output(PIN_CLOSE, 1)
            time.sleep(1)
            gpio.output(PIN_CLOSE, 0)

            write_state("closed")
            update_api(True)

        elif pipe_cmd == "open":
            # Locking
            gpio.output(PIN_OPEN, 1)
            time.sleep(1)
            gpio.output(PIN_OPEN, 0)

            write_state("open")  # Save State
            update_api(False)  # Status Update

        time.sleep(0.2)


if __name__ == "__main__":
    main()
