#!/usr/bin/env python3
"""
app.py – Hauptanwendung für Grow‑Control:
- Liest den DHT22-Sensor aus (mit Fehlerbehandlung)
- Schaltet Relais basierend auf Temperatur, Luftfeuchtigkeit und Hysterese
- Speichert Messwerte in einer SQLite-Datenbank
- Bietet eine Web-API und Weboberfläche (Flask) mit Diagrammen (Chart.js)
"""

import os
import sys
import time
import signal
import threading
import datetime
import logging
from flask import Flask, jsonify, send_from_directory
import RPi.GPIO as GPIO
from dht_reader import read_dht22
import database

# --- Konfiguration und GPIO-Pin-Zuweisungen ---
# BCM-Pin-Nummern:
PIN_EXHAUST      = 26   # Abluft
PIN_LIGHT        = 20   # Beleuchtung
PIN_HUMIDIFIER   = 21   # Vernebler
PIN_CIRCULATION  = 19   # Umluft
PIN_DHT          = 17   # DHT22

MEASUREMENT_INTERVAL = 60  # Sekunden

# Hysterese-Werte (anpassbar)
TEMP_HYST = 0.5  # °C
HUM_HYST  = 2    # Prozent

# Initiale Moduseinstellungen (Beispielwerte)
modeSettings = {
    'name': 'Manuell',
    'humidityMin': 50,
    'humidityMax': 70,
    'tempMax': 26,  # Maximale Temperatur, ab der Abluft schalten soll
    'lightSchedule': {'start': '06:00', 'end': '23:59'},
    'circulationMode': 'cycle',  # 'alwaysOn', 'alwaysOff', 'cycle'
    'circulationOn': 10,   # Minuten
    'circulationOff': 10   # Minuten
}

# Globale Zustände:
currentTemp = 0
currentHumidity = 0
exhaustOn = False
humidifierOn = False
currentCirculationState = False
isCirculationOverridden = False

# Logging einrichten:
logging.basicConfig(filename='log.txt', level=logging.INFO,
                    format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
def log_event(message):
    logging.info(message)
    print(message)

# --- GPIO initialisieren ---
def init_gpio():
    GPIO.setmode(GPIO.BCM)
    # Setup als Ausgang; Annahme: LOW = Relais AN, HIGH = AUS
    GPIO.setup(PIN_EXHAUST, GPIO.OUT)
    GPIO.setup(PIN_LIGHT, GPIO.OUT)
    GPIO.setup(PIN_HUMIDIFIER, GPIO.OUT)
    GPIO.setup(PIN_CIRCULATION, GPIO.OUT)
    # Alle Relais ausschalten:
    GPIO.output(PIN_EXHAUST, GPIO.HIGH)
    GPIO.output(PIN_LIGHT, GPIO.HIGH)
    GPIO.output(PIN_HUMIDIFIER, GPIO.HIGH)
    GPIO.output(PIN_CIRCULATION, GPIO.HIGH)

# --- Hilfsfunktionen für Zeitplanung (Beleuchtung) ---
def parse_time(t_str):
    h, m = map(int, t_str.split(':'))
    return h * 60 + m

def get_current_minutes():
    now = datetime.datetime.now()
    return now.hour * 60 + now.minute

def is_light_on(schedule):
    if not schedule:
        return False
    start = parse_time(schedule['start'])
    end = parse_time(schedule['end'])
    now = get_current_minutes()
    if start < end:
        return start <= now < end
    else:
        # Falls Zeit über Mitternacht (optional)
        return now >= start or now < end

# --- Relais-Steuerung ---
def set_relay(pin, on):
    # on == True => Relais AN (LOW), on == False => AUS (HIGH)
    GPIO.output(pin, GPIO.LOW if on else GPIO.HIGH)

# --- Steuerungsschleife (in einem separaten Thread) ---
def control_loop():
    global currentTemp, currentHumidity, exhaustOn, humidifierOn, isCirculationOverridden
    while True:
        result = read_dht22(PIN_DHT)
        if result is None:
            log_event("Fehler: DHT22-Sensor konnte nicht ausgelesen werden.")
        else:
            temperature = result['temperature']
            humidity = result['humidity']
            currentTemp = temperature
            currentHumidity = humidity

            # Messwerte in die DB schreiben
            try:
                database.insert_measurement(temperature, humidity)
            except Exception as e:
                log_event(f"DB-Fehler: {e}")

            # --- Steuerlogik mit Hysterese ---
            # Abluftsteuerung (Temperatur)
            if (not exhaustOn) and temperature > (modeSettings['tempMax'] + TEMP_HYST):
                set_relay(PIN_EXHAUST, True)
                exhaustOn = True
                isCirculationOverridden = True
                log_event(f"Abluft AN (Temp {temperature}°C > {modeSettings['tempMax']}+{TEMP_HYST})")
            elif exhaustOn and temperature < (modeSettings['tempMax'] - TEMP_HYST):
                set_relay(PIN_EXHAUST, False)
                exhaustOn = False
                log_event(f"Abluft AUS (Temp {temperature}°C < {modeSettings['tempMax']}-{TEMP_HYST})")
                if not humidifierOn:
                    isCirculationOverridden = False

            # Verneblersteuerung (Luftfeuchtigkeit)
            if (not humidifierOn) and humidity < (modeSettings['humidityMin'] - HUM_HYST):
                set_relay(PIN_HUMIDIFIER, True)
                humidifierOn = True
                isCirculationOverridden = True
                log_event(f"Vernebler AN (rF {humidity}% < {modeSettings['humidityMin']}-{HUM_HYST})")
            elif humidifierOn and humidity > (modeSettings['humidityMin'] + HUM_HYST):
                set_relay(PIN_HUMIDIFIER, False)
                humidifierOn = False
                log_event(f"Vernebler AUS (rF {humidity}% > {modeSettings['humidityMin']}+{HUM_HYST})")
                if not exhaustOn:
                    isCirculationOverridden = False

            # Beleuchtung
            if is_light_on(modeSettings.get('lightSchedule')):
                set_relay(PIN_LIGHT, True)
            else:
                set_relay(PIN_LIGHT, False)

        time.sleep(MEASUREMENT_INTERVAL)

# --- Umluft-Zyklus (separater Thread) ---
def circulation_cycle():
    global currentCirculationState
    while True:
        # Falls der gewählte Modus "alwaysOff" oder "Fermentation" ist, bleiben wir aus.
        if modeSettings['circulationMode'] in ['alwaysOff'] or modeSettings['name'] == 'Verarbeitung: Fermentation':
            set_relay(PIN_CIRCULATION, False)
            currentCirculationState = False
            time.sleep(10)
            continue

        # Wenn ein Override aktiv ist (z. B. Abluft oder Vernebler an), soll Umluft immer AN sein.
        if isCirculationOverridden:
            set_relay(PIN_CIRCULATION, True)
            currentCirculationState = True
            time.sleep(10)
            continue

        # Zyklischer Betrieb:
        if modeSettings['circulationMode'] == 'alwaysOn':
            set_relay(PIN_CIRCULATION, True)
            currentCirculationState = True
            time.sleep(10)
        elif modeSettings['circulationMode'] == 'cycle':
            if not currentCirculationState:
                # Einschalten
                set_relay(PIN_CIRCULATION, True)
                currentCirculationState = True
                time.sleep(modeSettings['circulationOn'] * 60)
            else:
                # Ausschalten
                set_relay(PIN_CIRCULATION, False)
                currentCirculationState = False
                time.sleep(modeSettings['circulationOff'] * 60)
        else:
            time.sleep(10)

# --- Flask Webserver ---
app = Flask(__name__, static_folder='public')

@app.route('/api/current')
def api_current():
    return jsonify({
        'temperature': currentTemp,
        'humidity': currentHumidity,
        'exhaustOn': exhaustOn,
        'lightOn': GPIO.input(PIN_LIGHT) == GPIO.LOW,
        'humidifierOn': humidifierOn,
        'circulationOn': GPIO.input(PIN_CIRCULATION) == GPIO.LOW,
        'mode': modeSettings['name']
    })

@app.route('/api/history')
def api_history():
    past_24h = (datetime.datetime.utcnow() - datetime.timedelta(hours=24)).isoformat()
    try:
        rows = database.get_measurements_since(past_24h)
        data = [{'timestamp': r[0], 'temperature': r[1], 'humidity': r[2]} for r in rows]
        return jsonify(data)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# Dient dazu, die index.html aus dem public-Verzeichnis zu liefern
@app.route('/')
def index():
    return app.send_static_file('index.html')

# --- Modus-Auswahl (Interaktive Abfrage im Terminal) ---
def ask_for_mode():
    global modeSettings
    print("Bitte wähle ein Programm aus:")
    print("1) Anzucht: Steckling")
    print("2) Wachstum: Jungpflanze")
    print("3) Wachstum: Vegetationsphase")
    print("4) Blüte: Frühblüte")
    print("5) Blüte: Spätblüte")
    print("6) Verarbeitung: Trocknung")
    print("7) Verarbeitung: Fermentation")
    print("8) Manuell")
    choice = input("Auswahl (1-8): ").strip()
    if choice == '1':
        modeSettings = {
            'name': 'Anzucht: Steckling',
            'humidityMin': 60,
            'humidityMax': 75,
            'tempMax': 26,
            'lightSchedule': {'start': '06:00', 'end': '23:59'},
            'circulationMode': 'cycle',
            'circulationOn': 5,
            'circulationOff': 25
        }
    elif choice == '2':
        modeSettings = {
            'name': 'Wachstum: Jungpflanze',
            'humidityMin': 50,
            'humidityMax': 70,
            'tempMax': 27,
            'lightSchedule': {'start': '06:00', 'end': '23:59'},
            'circulationMode': 'cycle',
            'circulationOn': 10,
            'circulationOff': 10
        }
    elif choice == '3':
        modeSettings = {
            'name': 'Wachstum: Vegetationsphase',
            'humidityMin': 40,
            'humidityMax': 60,
            'tempMax': 28,
            'lightSchedule': {'start': '06:00', 'end': '23:59'},
            'circulationMode': 'cycle',
            'circulationOn': 15,
            'circulationOff': 5
        }
    elif choice == '4':
        modeSettings = {
            'name': 'Blüte: Frühblüte',
            'humidityMin': 40,
            'humidityMax': 55,
            'tempMax': 27,
            'lightSchedule': {'start': '08:00', 'end': '20:00'},
            'circulationMode': 'alwaysOn'
        }
    elif choice == '5':
        modeSettings = {
            'name': 'Blüte: Spätblüte',
            'humidityMin': 30,
            'humidityMax': 50,
            'tempMax': 26,
            'lightSchedule': {'start': '08:00', 'end': '20:00'},
            'circulationMode': 'alwaysOn'
        }
    elif choice == '6':
        modeSettings = {
            'name': 'Verarbeitung: Trocknung',
            'humidityMin': 45,
            'humidityMax': 55,
            'tempMax': 22,
            'lightSchedule': None,
            'circulationMode': 'cycle',
            'circulationOn': 15,
            'circulationOff': 45
        }
    elif choice == '7':
        modeSettings = {
            'name': 'Verarbeitung: Fermentation',
            'humidityMin': 55,
            'humidityMax': 65,
            'tempMax': 22,
            'lightSchedule': None,
            'circulationMode': 'alwaysOff'
        }
    else:
        modeSettings['name'] = 'Manuell'
        try:
            modeSettings['humidityMin'] = float(input("Mindest-Luftfeuchte [%]: "))
            modeSettings['humidityMax'] = float(input("Maximale Luftfeuchte [%]: "))
            modeSettings['tempMax']     = float(input("Maximale Temperatur [°C]: "))
        except ValueError:
            print("Ungültige Eingabe. Standardwerte werden übernommen.")
        light_choice = input("Beleuchtung an? (y/n) oder Zeiten (z.B. '06:00-20:00'): ").strip()
        if light_choice.lower() == 'y':
            modeSettings['lightSchedule'] = {'start': '00:00', 'end': '00:00'}
        elif '-' in light_choice:
            start_time, end_time = light_choice.split('-')
            modeSettings['lightSchedule'] = {'start': start_time.strip(), 'end': end_time.strip()}
        else:
            modeSettings['lightSchedule'] = None
        print("Umluftoptionen:")
        print("1) Dauerhaft an")
        print("2) Dauerhaft aus")
        print("3) Zyklisch (X Minuten an, Y Minuten aus)")
        circ_choice = input("Auswahl (1-3): ").strip()
        if circ_choice == '1':
            modeSettings['circulationMode'] = 'alwaysOn'
        elif circ_choice == '2':
            modeSettings['circulationMode'] = 'alwaysOff'
        else:
            modeSettings['circulationMode'] = 'cycle'
            try:
                modeSettings['circulationOn'] = int(input("Wie viele Minuten an?: "))
                modeSettings['circulationOff'] = int(input("Wie viele Minuten aus?: "))
            except ValueError:
                modeSettings['circulationOn'] = 10
                modeSettings['circulationOff'] = 10

    log_event(f"Modus gewählt: {modeSettings['name']}")

# --- Signalbehandlung für sauberes Beenden ---
def shutdown(signal_received, frame):
    log_event("SIGINT empfangen – Beende Anwendung...")
    # Alle Relais ausschalten:
    set_relay(PIN_EXHAUST, False)
    set_relay(PIN_LIGHT, False)
    set_relay(PIN_HUMIDIFIER, False)
    set_relay(PIN_CIRCULATION, False)
    GPIO.cleanup()
    sys.exit(0)

signal.signal(signal.SIGINT, shutdown)

# --- Hauptfunktion ---
def main():
    # Datenbank initialisieren:
    database.init_db()
    init_gpio()
    ask_for_mode()

    # Starte Hintergrund-Threads für Steuerung und Umluftzyklus:
    threading.Thread(target=control_loop, daemon=True).start()
    threading.Thread(target=circulation_cycle, daemon=True).start()

    # Starte Flask-Webserver (Blockiert den Main-Thread)
    log_event("Starte Webserver auf Port 3000...")
    app.run(host='0.0.0.0', port=3000)

if __name__ == '__main__':
    main()
