# Complete DOCPLEX implementation

from docplex.mp.model import Model

def optimize_speaker_allocation():
    """Optimize the allocation of speakers to debates to maximize audience reach."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="speaker_allocation")
    
    # Data from the database
    debates = [
        {"Debate_ID": 1, "Num_of_Audience": 150},
        {"Debate_ID": 2, "Num_of_Audience": 250},
        {"Debate_ID": 3, "Num_of_Audience": 100}
    ]
    
    decision_variables = [
        {"Debate_ID": 1, "People_ID": 101, "assignment": True},
        {"Debate_ID": 1, "People_ID": 102, "assignment": False},
        {"Debate_ID": 2, "People_ID": 101, "assignment": True},
        {"Debate_ID": 2, "People_ID": 103, "assignment": True},
        {"Debate_ID": 3, "People_ID": 102, "assignment": True}
    ]
    
    constraint_bounds = {
        "Max_Debates_Per_Speaker": 3,
        "Max_Speakers_Per_Debate": 5
    }
    
    # Extract unique speaker and debate IDs
    speaker_ids = set(dv["People_ID"] for dv in decision_variables)
    debate_ids = set(d["Debate_ID"] for d in debates)
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(debates) > 0, "No debates available"
    assert len(decision_variables) > 0, "No decision variables available"
    
    # 2. VARIABLES
    x = {(i, j): mdl.binary_var(name=f"x_{i}_{j}") for i in speaker_ids for j in debate_ids}
    
    # 3. OBJECTIVE FUNCTION
    objective = mdl.sum(debates[j-1]["Num_of_Audience"] * x[i, j] for i in speaker_ids for j in debate_ids)
    mdl.maximize(objective)
    
    # 4. CONSTRAINTS
    
    # Speaker Participation Limit
    for i in speaker_ids:
        mdl.add_constraint(mdl.sum(x[i, j] for j in debate_ids) <= constraint_bounds["Max_Debates_Per_Speaker"], ctname=f"max_debates_speaker_{i}")
    
    # Debate Capacity Limit
    for j in debate_ids:
        mdl.add_constraint(mdl.sum(x[i, j] for i in speaker_ids) <= constraint_bounds["Max_Speakers_Per_Debate"], ctname=f"max_speakers_debate_{j}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for i in speaker_ids:
            for j in debate_ids:
                value = solution.get_value(x[i, j])
                if value > 1e-6:
                    print(f"x[{i},{j}] = {value:.3f}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

# Run the optimization
optimize_speaker_allocation()