<?php

class Manager extends IPSModule
{
    public function Create()
    {
        parent::Create();

        // Properies und Systemvariablen registrieren
        $this->RegisterPropertyInteger("Peakleistung", 0);
        $this->RegisterPropertyInteger("Ueberschussleistung", 0);
        $this->RegisterPropertyInteger("Netzbezug", 0); // Initialisierung mit 0
        $this->RegisterPropertyString("Verbraucher_Liste", "[]");
        $this->RegisterPropertyBoolean("UmschaltpunktStatisch", false); // Initialisierung mit 0
        $this->RegisterPropertyInteger("Umschalt_Solarladen", 100); // Initialisierung mit 0
        $this->RegisterPropertyInteger("Umschalt_Peakshaving", 10000); // Initialisierung mit 0
        $this->RegisterPropertyBoolean("HauptmanagerAktiv", false); // Initialisierung mit 0
        $this->RegisterPropertyInteger("ManagerID", 0); // Initialisierung mit 0
        $this->RegisterPropertyInteger("DatenHoch", 0); // Initialisierung mit 0
        $this->RegisterPropertyInteger("DatenZuruck", 0); // Initialisierung mit 0
        $this->RegisterPropertyInteger("Interval", 2); // Recheninterval

        $this->RegisterVariableBoolean("Is_Peak_Shaving", false);

        // Timer registrieren

        $this->RegisterTimer(
            "Timer_DistributeEnergy",
            $this->ReadPropertyInteger("Interval") * 1000,
            "IPS_RequestAction(" .
                $this->InstanceID .
                ', "DistributeEnergy", "");'
        );
    }

    public function ApplyChanges()
    {
        parent::ApplyChanges();

        $this->SetTimerInterval(
            "Timer_DistributeEnergy",
            $this->ReadPropertyInteger("Interval") * 1000
        );
    }

    public function RequestAction($Ident, $Value)
    {
        switch ($Ident) {
            case "DistributeEnergy":
                // Fallunterscheidung, ist der Manager stand-alone oder in einem verbund mit Hauptmanager?
                if ($this->ReadPropertyBoolean("HauptmanagerAktiv") == true) {
                    // Wenn im Verbund, parse die Verbraucherdaten und sende sie dem Hauptmanager, sofern dieser aktiv ist
                    $data = json_decode(
                        GetValue($this->ReadPropertyInteger("DatenZuruck")),
                        true
                    );

                    if (isset($data["Timestamp"])) {
                        $timestamp = $data["Timestamp"];
                        $currentTime = time();

                        if ($currentTime - $timestamp < 3600) {
                            $this->DistributeEnergy_Extern();
                        } else {
                            $sendarray = [
                                "Netzbezug" => GetValue(
                                    $this->ReadPropertyInteger("Netzbezug")
                                ),
                                "Timestamp" => time(),
                            ];

                            SetValue(
                                $this->ReadPropertyInteger("DatenHoch"),
                                json_encode($sendarray)
                            );

                            $this->DistributeEnergy();
                        }
                    } else {
                        $sendarray = [
                            "Netzbezug" => GetValue(
                                $this->ReadPropertyInteger("Netzbezug")
                            ),
                            "Timestamp" => time(),
                        ];

                        SetValue(
                            $this->ReadPropertyInteger("DatenHoch"),
                            json_encode($sendarray)
                        );

                        $this->DistributeEnergy();
                    }
                } else {
                    // wenn stand-alone, rufe einfach die Methode zum verteilen auf
                    $this->DistributeEnergy();
                }

                break;

            case "ApplyChanges":
                $this->ApplyChanges();
                break;

            default:
                throw new Exception("Invalid Ident");
        }
    }

    public function DistributeEnergy()
    {
        // Alle Energieverbraucher auslesen und dekodieren
        $Verbraucher_Liste = json_decode(
            $this->ReadPropertyString("Verbraucher_Liste"),
            true
        );

        if (empty($Verbraucher_Liste)) {
            // Wenn keine Verbruacher angemeldet sind abbrechen
            return;
        }

        // Hilfsvariabeln initialiseren

        $filteredVerbraucher = []; // Array das später mit allen Verbrauchsdaten der Energieverbraucher gefüllt wird
        $allIdle = true; // Variable zur Überprüfung, ob alle Benutzer Idle = true sind
        $totalAktuelle_Leistung = 0; // Variable zur Summierung der Aktuelle_Leistung Werte
        $helpvar_offset_peakermitteln = 0; // Variable zur Summierung der Aktuelle_Leistung Werte

        // Fülle das Array mit allen entsprechenden Werten der Verbraucher ab
        foreach ($Verbraucher_Liste as $user) {
            // Werte direkt von der Verbraucher-Instanz abrufen
            $Aktuelle_Leistung = GetValue(
                IPS_GetObjectIDByIdent("Power", $user["Verbraucher"])
            ); // Aktuelle Leistung durch Power ersetzt
            $Bezogene_Energie = GetValue(
                IPS_GetObjectIDByIdent("Bezogene_Energie", $user["Verbraucher"])
            );
            $PV_Prio = GetValue(
                IPS_GetObjectIDByIdent("PV_Prio", $user["Verbraucher"])
            );
            $Sperre_Prio = GetValue(
                IPS_GetObjectIDByIdent("Sperre_Prio", $user["Verbraucher"])
            );
            $idle = GetValue(
                IPS_GetObjectIDByIdent("Idle", $user["Verbraucher"])
            );
            $powerStepsJson = GetValue(
                IPS_GetObjectIDByIdent("PowerSteps", $user["Verbraucher"])
            );
            $powerSteps = json_decode($powerStepsJson, true);
            $delta = GetValue(
                IPS_GetObjectIDByIdent("Leistung_Delta", $user["Verbraucher"])
            );

            // Verbraucher-Daten zum gefilterten Array hinzufügen
            $filteredVerbraucher[] = [
                "Verbraucher" => $user["Verbraucher"],
                "InstanceID" => $user["Verbraucher"],
                "Aktuelle_Leistung" => $Aktuelle_Leistung,
                "Bezogene_Energie" => $Bezogene_Energie,
                "PV_Prio" => $PV_Prio,
                "Sperre_Prio" => $Sperre_Prio,
                "Idle" => $idle,
                "PowerSteps" => $powerSteps,
                "Leistung_Delta" => $delta,
            ];

            // Überprüfen, ob alle Benutzer Idle = true sind (bereit für eine Leistungsänderung), wenn einer nicht ist, wird später verworfen...
            if (!$idle) {
                $allIdle = false;
            }

            // Addiere die aktuell bereits verwendete Leistung auf, um sie bei der verteilung zu berücksichtigen
            $totalAktuelle_Leistung += $Aktuelle_Leistung - $delta;
        }

        // Variante der Leistungsregelung (Überschuss, Peak) ermitteln und aktuellen Netzbezug bereitstellen
        $Netzbezug = GetValue($this->ReadPropertyInteger("Netzbezug"));
        $Peakleistung = $this->ReadPropertyInteger("Peakleistung");
        $Ueberschussleistung = $this->ReadPropertyInteger(
            "Ueberschussleistung"
        );

        // Dynamische ermittlung der Betriebsart
        if ($this->ReadPropertyBoolean("UmschaltpunktStatisch") == false) {
            // Fallunterscheidung ob auf Solarladen oder Peakshaving gerregelt wird.
            if ($Netzbezug < ($Peakleistung + $Ueberschussleistung) / 2) {
                $remainingPower = -1 * (-1 * $Ueberschussleistung + $Netzbezug);
                $Is_Peak_Shaving = false;
            } else {
                $remainingPower = $Peakleistung - $Netzbezug;
                $Is_Peak_Shaving = true;
            }
        }
        // Statische ermittlung der Betriebsart
        else {
            if (
                $Netzbezug < $this->ReadPropertyInteger("Umschalt_Solarladen")
            ) {
                $remainingPower = -1 * (-1 * $Ueberschussleistung + $Netzbezug);
                $Is_Peak_Shaving = false;
            } elseif (
                $Netzbezug > $this->ReadPropertyInteger("Umschalt_Peakshaving")
            ) {
                $remainingPower = $Peakleistung - $Netzbezug;
                $Is_Peak_Shaving = true;
            } elseif ($this->GetValue("Is_Peak_Shaving") == false) {
                $remainingPower = -1 * (-1 * $Ueberschussleistung + $Netzbezug);
                $Is_Peak_Shaving = false;
            } elseif ($this->GetValue("Is_Peak_Shaving") == true) {
                $remainingPower = $Peakleistung - $Netzbezug;
                $Is_Peak_Shaving = true;
            }
        }

        // Aktuelle bereits verwendete Leistung auf die verbleibende Leistung als Offset summieren.
        $remainingPower += $totalAktuelle_Leistung;

        // Frage alle Energieverbraucher ab, was sie für Leistungen benötigen könnten
        foreach ($Verbraucher_Liste as $user) {
            if (IPS_InstanceExists($user["Verbraucher"])) {
                IPS_RequestAction(
                    $user["Verbraucher"],
                    "GetCurrentData",
                    $Is_Peak_Shaving
                );
            }
        }

        // Abbrechen wenn es keine Verbruacher gibt, die verfügbar sind
        if (empty($filteredVerbraucher)) {
            return;
        }

        // Wenn nicht alle Benutzer Idle = true sind, oder sich der zustand von Is_Peak_shaving gerade verändert hat rufe SetAktuelle_Leistung mit Aktuelle_Leistung Werten auf, (alle Verbraucher behalten die aktuelle Leistung)
        if (
            !$allIdle ||
            $Is_Peak_Shaving != $this->GetValue("Is_Peak_Shaving")
        ) {
            foreach ($filteredVerbraucher as $user) {
                IPS_RequestAction(
                    $user["InstanceID"],
                    "SetAktuelle_Leistung",
                    $user["Aktuelle_Leistung"]
                );
            }
            $this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);

            return;
        }

        $this->SetValue("Is_Peak_Shaving", $Is_Peak_Shaving);

        // Sortiere die Verbruacher nach Priorität entweder der PV_Prio oder der Peak Prio
        usort($filteredVerbraucher, function ($a, $b) use ($Is_Peak_Shaving) {
            $primaryKey = $Is_Peak_Shaving ? "Sperre_Prio" : "PV_Prio";
            // Wenn die Prio geleich ist, sortiere danach welcher verbraucher bisher am wenigsten Energie bekommen hat.
            if ($a[$primaryKey] == $b[$primaryKey]) {
                return round($a["Bezogene_Energie"] / 2000) <=>
                    round($b["Bezogene_Energie"] / 2000);
            }
            return $a[$primaryKey] <=> $b[$primaryKey];
        });

        // Primärschlüssel für die Priorität basierend auf dem Parameter auswählen (für sortierung in gruppen anschliessend)
        $priorityKey = $Is_Peak_Shaving ? "Sperre_Prio" : "PV_Prio";

        // Schleife durch alle Prioritäten
        $priorities = array_unique(
            array_column($filteredVerbraucher, $priorityKey)
        );
        $groupedUsers = [];

        foreach ($priorities as $priority) {
            $groupedUsers[$priority] = array_filter(
                $filteredVerbraucher,
                function ($user) use ($priority, $priorityKey) {
                    return $user[$priorityKey] == $priority;
                }
            );
        }

        // Jetzt werden die energien pro gruppe verteilt (Immer alle pro prio in einer gruppe miteinander)
        foreach ($groupedUsers as $priority => $users) {
            // Verbraucher mit gleicher Priorität sammeln
            $samePriorityUsers = isset($groupedUsers[$priority])
                ? $groupedUsers[$priority]
                : [];

            // Wenn keine Verbraucher mit gleicher Priorität vorhanden sind, überspringen
            if (empty($samePriorityUsers)) {
                continue;
            }
            $withZero = [];
            $withoutZeroHigh = [];
            $withoutZeroLow = [];

            // Sammle alle Verbraucher die nicht null als Verbruach annehmen können. (inkl. sortierung in positive und Negative)
            foreach ($samePriorityUsers as $entry) {
                $withZero[] = $entry;

                if (min($entry["PowerSteps"]) > 0) {
                    $withoutZeroHigh[] = $entry;
                }
                if (max($entry["PowerSteps"]) < 0) {
                    $withoutZeroLow[] = $entry;
                }
            }

            // positiven Verbrauchern die nicht 0 annhemen können bereits den minimalwert zuteilen
            if (!empty($withoutZeroHigh)) {
                foreach ($withoutZeroHigh as $entry) {
                    $instanceID = $entry["InstanceID"];
                    $minPowerStep = min($entry["PowerSteps"]);
                }
            }
            // negativen Verbrauchern die nicht 0 annhemen können bereits den minimalwert zuteilen
            if (!empty($withoutZeroLow)) {
                foreach ($withoutZeroLow as $entry) {
                    $instanceID = $entry["InstanceID"];
                    $minPowerStep = max($entry["PowerSteps"]);
                }
            }

            // Nun die Verbrucher verteilen, die auch 0 erhalten können.
            $samePriorityUsers = $withZero;

            $userEnergyProv = [];

            foreach ($samePriorityUsers as $entry) {
                $instanceID = $entry["InstanceID"];
                // Schutz: falls PowerSteps nicht gesetzt oder kein Array ist
                $steps =
                    isset($entry["PowerSteps"]) &&
                    is_array($entry["PowerSteps"])
                        ? $entry["PowerSteps"]
                        : [0];

                // Wenn 0 als möglicher Step vorhanden ist -> 0 setzen

                $minStep = min($steps);
                $maxStep = max($steps);

                if (in_array(0, $steps, true)) {
                    $userEnergyProv[$instanceID] = 0;
                    continue;
                } elseif ($minStep > 0) {
                    // nur positive Schritte -> minimalen positiven Wert einsetzen
                    $userEnergyProv[$instanceID] = $minStep;
                    // entspricht deinem vorherigen Verhalten: remainingPower reduzieren
                    $remainingPower -= $minStep;
                } elseif ($maxStep < 0) {
                    // nur negative Schritte -> maximalen (am wenigsten negativen) Wert einsetzen
                    $userEnergyProv[$instanceID] = $maxStep;
                    // entspricht deinem vorherigen Verhalten: remainingPower erhöhen (maxStep ist negativ)
                    $remainingPower += $maxStep;
                } else {
                    // Mixed (positive & negative) aber kein 0 -> Fallback 0 (ändere wenn nötig)
                    $userEnergyProv[$instanceID] = 0;
                }
            }

            // Beginne mit aktiver Verteilung
            if ($remainingPower >= 0) {
                // Alle Schritte der Benutzer in einem Array sammeln
                $allSteps = [];
                foreach ($samePriorityUsers as $user) {
                    foreach ($user["PowerSteps"] as $step) {
                        if ($step >= 0) {
                            $allSteps[] = [
                                "user" => $user["InstanceID"],
                                //"Leistung_Delta" => $user["Leistung_Delta"],
                                "step" => $step,
                            ];
                        }
                    }
                }

                // Sortiere die Schritte nach Größe
                usort($allSteps, function ($a, $b) {
                    return $a["step"] <=> $b["step"];
                });

                //if remaining power >0
                // Iteriere durch alle Schritte
                foreach ($allSteps as $entry) {
                    $user = $entry["user"];
                    $powerstep = $entry["step"];

                    // Überprüfe, ob noch genügend verbleibende Energie für den nächsten Schritt vorhanden ist
                    if (
                        $remainingPower >=
                        $powerstep - $userEnergyProv[$user]
                    ) {
                        // oder bedingung testen
                        // Aktualisiere die verbleibende Energie und die bereitgestellte Energie für den Benutzer
                        $remainingPower -= $powerstep - $userEnergyProv[$user];
                        $userEnergyProv[$user] = $powerstep;
                    }
                }

                // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt
                foreach ($userEnergyProv as $userInstanceID => $leistung) {
                    // Innerhalb der Schleife: alle nicht-negativen Leistungen sammeln
                    $positiveValues = array_filter(
                        array_column(
                            $samePriorityUsers,
                            "PowerSteps",
                            "InstanceID"
                        )[$userInstanceID],
                        function ($l) {
                            return $l >= 0;
                        }
                    );
                    // Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen
                    $fallbackMinimum = empty($positiveValues)
                        ? max(
                            array_column(
                                $samePriorityUsers,
                                "PowerSteps",
                                "InstanceID"
                            )[$userInstanceID]
                        )
                        : min($positiveValues);

                    // minimalleistung = dieser Fallback
                    $minimalleistung = $fallbackMinimum;

                    //den höheren Wert wählen und für IPS negativieren

                    if (abs($leistung) > abs($minimalleistung)) {
                        $schreibleistung = $leistung;
                    } else {
                        $schreibleistung = $minimalleistung;
                    }

                    // Die Veteilten Leistungen auf die Verbruacher schreiben
                    if (IPS_InstanceExists($userInstanceID)) {
                        IPS_RequestAction(
                            $userInstanceID,
                            "SetAktuelle_Leistung",
                            $schreibleistung
                        );
                    }
                }
            } else {
                // Alle Schritte der Benutzer in einem Array sammeln
                $allSteps = [];
                foreach ($samePriorityUsers as $user) {
                    foreach ($user["PowerSteps"] as $step) {
                        if ($step <= 0) {
                            $allSteps[] = [
                                "user" => $user["InstanceID"],
                                "step" => -1 * $step,
                            ];
                        }
                    }
                }

                // Sortiere die Schritte nach Größe
                usort($allSteps, function ($a, $b) {
                    return $a["step"] <=> $b["step"];
                });

                $remainingPower = $remainingPower * -1;
                // Iteriere durch alle Schritte
                foreach ($allSteps as $entry) {
                    $user = $entry["user"];
                    $powerstep = $entry["step"];

                    // Überprüfe, ob noch genügend verbleibende Energie für den nächsten Schritt vorhanden ist
                    if (
                        $remainingPower >=
                        $powerstep - $userEnergyProv[$user]
                    ) {
                        // oder bedingung testen
                        // Aktualisiere die verbleibende Energie und die bereitgestellte Energie für den Benutzer
                        $remainingPower -= $powerstep - $userEnergyProv[$user];
                        $userEnergyProv[$user] = $powerstep;
                    }
                }
                $remainingPower = $remainingPower * -1;

                // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt
                foreach ($userEnergyProv as $userInstanceID => $leistung) {
                    // Innerhalb der Schleife: alle nicht-negativen Leistungen sammeln
                    $positiveValues = array_filter(
                        array_column(
                            $samePriorityUsers,
                            "PowerSteps",
                            "InstanceID"
                        )[$userInstanceID],
                        function ($l) {
                            return $l <= 0;
                        }
                    );
                    // Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen
                    $fallbackMinimum = empty($positiveValues)
                        ? min(
                            array_column(
                                $samePriorityUsers,
                                "PowerSteps",
                                "InstanceID"
                            )[$userInstanceID]
                        )
                        : max($positiveValues);

                    // minimalleistung = dieser Fallback
                    $minimalleistung = $fallbackMinimum;

                    // den höheren Wert wählen und negieren

                    if (abs($leistung) > abs($minimalleistung)) {
                        $schreibleistung = $leistung * -1;
                    } else {
                        $schreibleistung = $minimalleistung;
                    }

                    // Die Veteilten Leistungen auf die Verbruacher schreiben
                    if (IPS_InstanceExists($userInstanceID)) {
                        IPS_RequestAction(
                            $userInstanceID,
                            "SetAktuelle_Leistung",
                            $schreibleistung
                        );
                    }
                }
            }
        }
    }

    public function DistributeEnergy_Extern()
    {
        // Systemvariablen abrufen
        $Netzbezug = GetValue($this->ReadPropertyInteger("Netzbezug"));

        $Verbraucher_Liste = json_decode(
            $this->ReadPropertyString("Verbraucher_Liste"),
            true
        );
        $filteredVerbraucher = [];
        // Fülle das Array mit allen entsprechenden Werten der Verbraucher ab
        foreach ($Verbraucher_Liste as $user) {
            // Werte direkt von der Verbraucher-Instanz abrufen
            $Aktuelle_Leistung = GetValue(
                IPS_GetObjectIDByIdent(
                    "Aktuelle_Leistung",
                    $user["Verbraucher"]
                )
            );
            $Bezogene_Energie = GetValue(
                IPS_GetObjectIDByIdent("Bezogene_Energie", $user["Verbraucher"])
            );
            $PV_Prio = GetValue(
                IPS_GetObjectIDByIdent("PV_Prio", $user["Verbraucher"])
            );
            $Sperre_Prio = GetValue(
                IPS_GetObjectIDByIdent("Sperre_Prio", $user["Verbraucher"])
            );
            $idle = GetValue(
                IPS_GetObjectIDByIdent("Idle", $user["Verbraucher"])
            );
            $powerStepsJson = GetValue(
                IPS_GetObjectIDByIdent("PowerSteps", $user["Verbraucher"])
            );
            $delta = GetValue(
                IPS_GetObjectIDByIdent("Leistung_Delta", $user["Verbraucher"])
            );
            $powerSteps = json_decode($powerStepsJson, true);

            // Verbraucher-Daten zum gefilterten Array hinzufügen
            $filteredVerbraucher[] = [
                "Verbraucher" => $user["Verbraucher"],
                "InstanceID" => $user["Verbraucher"],
                "Aktuelle_Leistung" => $Aktuelle_Leistung,
                "Bezogene_Energie" => $Bezogene_Energie,
                "PV_Prio" => $PV_Prio,
                "Sperre_Prio" => $Sperre_Prio,
                "Idle" => $idle,
                "PowerSteps" => $powerSteps,
                "Leistung_Delta" => $delta,
                "ParentManager" => $this->ReadPropertyInteger("ManagerID"),
            ];
        }

        $sendarray = [];

        $sendarray = [
            "User" => $filteredVerbraucher,
            "Netzbezug" => $Netzbezug,
            "Timestamp" => time(),
        ];

        SetValue(
            $this->ReadPropertyInteger("DatenHoch"),
            json_encode($sendarray)
        );

        $answerArray = json_decode(
            GetValue($this->ReadPropertyInteger("DatenZuruck")),
            true
        );

        foreach ($answerArray["User"] as $user) {
            IPS_RequestAction(
                $user["InstanceID"],
                "GetCurrentData",
                $answerArray["Is_Peak_Shaving"]
            );

            IPS_RequestAction(
                $user["InstanceID"],
                "SetAktuelle_Leistung",
                $user["Set_Leistung"]
            );
        }
    }
}
?>
