#!/usr/bin/env python3
"""
DOCPLEX implementation for the mountain photography lens selection problem.
"""

from docplex.mp.model import Model

def mountain_photos_optimization():
    """Optimize lens selection for mountain photography."""
    
    # 1. MODEL & DATA SETUP
    mdl = Model(name="mountain_photos")
    
    # Data from the database
    lenses = [
        {"lens_id": 1, "cost": 500.0, "focal_length_mm": 24, "max_aperture": 1.8},
        {"lens_id": 2, "cost": 750.0, "focal_length_mm": 50, "max_aperture": 2.8},
        {"lens_id": 3, "cost": 1000.0, "focal_length_mm": 85, "max_aperture": 4.0}
    ]
    
    mountains = [
        {"mountain_id": 1, "minimum_focal_length": 24, "minimum_aperture": 1.8},
        {"mountain_id": 2, "minimum_focal_length": 50, "minimum_aperture": 2.8},
        {"mountain_id": 3, "minimum_focal_length": 85, "minimum_aperture": 4.0}
    ]
    
    # CRITICAL: Validate array lengths to prevent IndexError
    assert len(lenses) > 0 and len(mountains) > 0, "Empty data arrays"
    
    # 2. VARIABLES
    # Binary decision variables: x[l][m] = 1 if lens l is assigned to mountain m
    x = {(l["lens_id"], m["mountain_id"]): mdl.binary_var(name=f"x_{l['lens_id']}_{m['mountain_id']}")
         for l in lenses for m in mountains}
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total cost of selected lenses
    objective = mdl.sum(l["cost"] * x[(l["lens_id"], m["mountain_id"])]
                        for l in lenses for m in mountains)
    mdl.minimize(objective)
    
    # 4. CONSTRAINTS
    
    # Constraint 1: Minimum Lenses per Mountain
    for m in mountains:
        mdl.add_constraint(mdl.sum(x[(l["lens_id"], m["mountain_id"])] for l in lenses) >= 1,
                           ctname=f"min_lenses_{m['mountain_id']}")
    
    # Constraint 2: Maximum Lenses per Mountain
    for m in mountains:
        mdl.add_constraint(mdl.sum(x[(l["lens_id"], m["mountain_id"])] for l in lenses) <= 3,
                           ctname=f"max_lenses_{m['mountain_id']}")
    
    # Constraint 3: Focal Length Requirement
    for l in lenses:
        for m in mountains:
            mdl.add_constraint(l["focal_length_mm"] * x[(l["lens_id"], m["mountain_id"])] >= m["minimum_focal_length"] * x[(l["lens_id"], m["mountain_id"])],
                               ctname=f"focal_length_{l['lens_id']}_{m['mountain_id']}")
    
    # Constraint 4: Aperture Requirement
    for l in lenses:
        for m in mountains:
            mdl.add_constraint(l["max_aperture"] * x[(l["lens_id"], m["mountain_id"])] >= m["minimum_aperture"] * x[(l["lens_id"], m["mountain_id"])],
                               ctname=f"aperture_{l['lens_id']}_{m['mountain_id']}")
    
    # 5. SOLVING & RESULTS
    solution = mdl.solve()
    
    if solution:
        print(f"Optimal value: {solution.objective_value}")
        for (lens_id, mountain_id), var in x.items():
            if solution.get_value(var) > 0:
                print(f"Lens {lens_id} assigned to Mountain {mountain_id}")
    else:
        print("No solution found")
        print(f"Status: {mdl.solve_details.status}")
    
    return mdl

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