#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Course-Teacher Assignment Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_course_teacher_assignment():
    """Optimize the assignment of teachers to courses to maximize teaching quality."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("course_teacher_assignment")
    
    # Example data (replace with actual data loading from database)
    teachers = ['T1', 'T2', 'T3']
    courses = ['C1', 'C2', 'C3', 'C4', 'C5']
    
    # Teaching quality grades (g_ij)
    grades = {
        ('T1', 'C1'): 4.5,
        ('T1', 'C2'): 3.7,
        ('T1', 'C3'): 4.0,
        ('T1', 'C4'): 3.5,
        ('T1', 'C5'): 4.2,
        ('T2', 'C1'): 3.8,
        ('T2', 'C2'): 4.1,
        ('T2', 'C3'): 3.9,
        ('T2', 'C4'): 4.3,
        ('T2', 'C5'): 3.6,
        ('T3', 'C1'): 4.2,
        ('T3', 'C2'): 3.8,
        ('T3', 'C3'): 4.1,
        ('T3', 'C4'): 3.7,
        ('T3', 'C5'): 4.0,
    }
    
    # Maximum courses per teacher (c_i)
    max_courses = {
        'T1': 4,
        'T2': 3,
        'T3': 5,
    }
    
    # CRITICAL: Validate array lengths before loops
    assert len(teachers) == len(max_courses), "Teacher and max_courses length mismatch"
    assert len(grades) == len(teachers) * len(courses), "Grades length mismatch"
    
    # 2. VARIABLES
    # Binary decision variables x_ij
    x = model.addVars(teachers, courses, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total teaching quality
    model.setObjective(gp.quicksum(grades[(i, j)] * x[i, j] for i in teachers for j in courses), GRB.MAXIMIZE)
    
    # 4. CONSTRAINTS
    
    # Course Assignment Constraint: Each course must be assigned exactly one teacher
    for j in courses:
        model.addConstr(gp.quicksum(x[i, j] for i in teachers) == 1, name=f"course_assignment_{j}")
    
    # Teacher Capacity Constraint: Each teacher can be assigned no more than their maximum allowable number of courses
    for i in teachers:
        model.addConstr(gp.quicksum(x[i, j] for j in courses) <= max_courses[i], name=f"teacher_capacity_{i}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in teachers:
            for j in courses:
                if x[i, j].x > 0.5:
                    print(f"Teacher {i} assigned to course {j}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
if __name__ == "__main__":
    optimize_course_teacher_assignment()