#!/usr/bin/env python3

# Bausteine_Vorlage.py
"""
Kapitel Einfuehrung: Die Bausteine-Vorlage als ausfuellbares Python-Geruest.

Uebersetzt die Vier-Bausteine-Vorlage aus dem Abschnitt 'Die vier
universellen Bausteine jedes OR-Problems' direkt in Code: Wer sie
auf Papier ausgefuellt hat, kann sie fast unveraendert in ein loesbares
Modell umsetzen. Angewendet auf das Baeckerei-Beispiel der Handrechnung.
"""

from dataclasses import dataclass
from ortools.sat.python import cp_model


@dataclass
class Bausteine:
    """Die vier Bausteine jedes OR-Problems - wortwoertlich aus der Vorlage
    im Abschnitt 'Die vier universellen Bausteine' uebernommen."""
    variablen: str
    parameter: str
    zielfunktion: str
    harte_bedingungen: str
    weiche_bedingungen: str

    def zeige(self) -> None:
        print("=" * 78)
        print("  AUSGEFUELLTE BAUSTEINE-VORLAGE")
        print("=" * 78)
        for feld, wert in [
            ("Entscheidungsvariablen", self.variablen),
            ("Parameter", self.parameter),
            ("Zielfunktion", self.zielfunktion),
            ("Harte Bedingungen", self.harte_bedingungen),
            ("Weiche Bedingungen", self.weiche_bedingungen),
        ]:
            print(f"{feld:<24}: {wert}")
        print("=" * 78)


# --- Baeckerei-Beispiel aus der Handrechnung ---------------------------------
VORLAGE = Bausteine(
    variablen="x1 = Anzahl Brote, x2 = Anzahl Broetchen-Zehnerpackungen "
              "(beide ganzzahlig >= 0)",
    parameter="Deckungsbeitraege (2,50; 3,00) EUR; Mehlbedarf (0,5; 0,6) kg; "
              "Ofenzeit (4; 3) min; Kapazitaeten (90 kg, 600 min); Mindestmenge 40 Brote",
    zielfunktion="max 2,5*x1 + 3,0*x2  (Einheit: Euro Deckungsbeitrag)",
    harte_bedingungen="0,5*x1 + 0,6*x2 <= 90 (Mehl); 4*x1 + 3*x2 <= 600 (Ofen); "
                      "x1 >= 40 (Vertrag)",
    weiche_bedingungen="keine",
)


def loese_baeckerei():
    """Baut aus der ausgefuellten Vorlage mechanisch ein CP-SAT-Modell."""
    modell = cp_model.CpModel()
    x1 = modell.NewIntVar(40, 1000, "Brote")            # Vertrag: mindestens 40
    x2 = modell.NewIntVar(0, 1000, "Broetchen_Zehner")

    modell.Add(5 * x1 + 6 * x2 <= 900)      # Mehl, x10 fuer Ganzzahligkeit
    modell.Add(4 * x1 + 3 * x2 <= 600)      # Ofenzeit

    modell.Maximize(25 * x1 + 30 * x2)      # Deckungsbeitrag x10

    loeser = cp_model.CpSolver()
    status = loeser.Solve(modell)
    if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        raise SystemExit(f"Keine Loesung: {loeser.StatusName(status)}")
    return loeser.Value(x1), loeser.Value(x2), loeser.ObjectiveValue() / 10


if __name__ == "__main__":
    VORLAGE.zeige()

    print("\n--- Aus der Vorlage mechanisch abgeleitetes Modell ---")
    brote, broetchen, gewinn = loese_baeckerei()
    print(f"Optimale Brote:              {brote}")
    print(f"Optimale Broetchen-Zehner:   {broetchen}")
    print(f"Maximaler Deckungsbeitrag:   {gewinn:.2f} EUR")
    print()
    print("Genau diese drei Codezeilen (Variablen, Add, Maximize) entstehen")
    print("direkt aus den fuenf Zeilen der Vorlage - das ist der ganze Trick.")
