#!/usr/bin/env python3
"""
DOCplex implementation for the construction company optimization problem.
"""

from docplex.mp.model import Model

def optimize_architecture():
    """Optimize the allocation of projects to architects to minimize total length of bridges and mills built."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="architecture_optimization")
    
    # Data from the database
    bridge_lengths = {1: 120, 2: 250, 3: 400}
    mill_lengths = {1: 350, 2: 450, 3: 550}
    architect_constraints = {1: (1, 3), 2: (1, 3), 3: (1, 3)}
    
    # Extract unique IDs
    bridge_ids = list(bridge_lengths.keys())
    mill_ids = list(mill_lengths.keys())
    architect_ids = list(architect_constraints.keys())
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(bridge_ids) > 0, "No bridges found"
    assert len(mill_ids) > 0, "No mills found"
    assert len(architect_ids) > 0, "No architects found"
    
    # 2. VARIABLES
    # Decision variables
    x = {b: mdl.binary_var(name=f"x_{b}") for b in bridge_ids}  # Whether bridge b is built
    y = {m: mdl.binary_var(name=f"y_{m}") for m in mill_ids}    # Whether mill m is built
    z = {(a, b): mdl.binary_var(name=f"z_{a}_{b}") for a in architect_ids for b in bridge_ids}  # Architect a assigned to bridge b
    w = {(a, m): mdl.binary_var(name=f"w_{a}_{m}") for a in architect_ids for m in mill_ids}    # Architect a assigned to mill m
    
    # 3. OBJECTIVE FUNCTION
    # Minimize total length of bridges and mills built
    total_length = mdl.sum(bridge_lengths[b] * x[b] for b in bridge_ids) + mdl.sum(mill_lengths[m] * y[m] for m in mill_ids)
    mdl.minimize(total_length)
    
    # 4. CONSTRAINTS
    
    # Minimum Projects per Architect
    for a in architect_ids:
        min_projects = architect_constraints[a][0]
        mdl.add_constraint(mdl.sum(z[a, b] for b in bridge_ids) + mdl.sum(w[a, m] for m in mill_ids) >= min_projects, ctname=f"min_projects_{a}")
    
    # Maximum Projects per Architect
    for a in architect_ids:
        max_projects = architect_constraints[a][1]
        mdl.add_constraint(mdl.sum(z[a, b] for b in bridge_ids) + mdl.sum(w[a, m] for m in mill_ids) <= max_projects, ctname=f"max_projects_{a}")
    
    # Bridge Assignment
    for a in architect_ids:
        for b in bridge_ids:
            mdl.add_constraint(z[a, b] <= x[b], ctname=f"bridge_assignment_{a}_{b}")
    
    # Mill Assignment
    for a in architect_ids:
        for m in mill_ids:
            mdl.add_constraint(w[a, m] <= y[m], ctname=f"mill_assignment_{a}_{m}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for b in bridge_ids:
            if solution.get_value(x[b]) > 0.5:
                print(f"Bridge {b} is built.")
        for m in mill_ids:
            if solution.get_value(y[m]) > 0.5:
                print(f"Mill {m} is built.")
        for a in architect_ids:
            for b in bridge_ids:
                if solution.get_value(z[a, b]) > 0.5:
                    print(f"Architect {a} is assigned to bridge {b}.")
            for m in mill_ids:
                if solution.get_value(w[a, m]) > 0.5:
                    print(f"Architect {a} is assigned to mill {m}.")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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