#!/usr/bin/env python3
"""
Gurobipy implementation for the mountain_photos optimization problem.
"""

import gurobipy as gp
from gurobipy import GRB

def mountain_photos_optimization():
    """Optimize lens selection for photographing mountains."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("mountain_photos")
    
    # Data from the database schema
    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 before loops
    assert len(lenses) > 0, "No lenses provided"
    assert len(mountains) > 0, "No mountains provided"
    
    # 2. VARIABLES
    # Binary decision variables: x[l][m] = 1 if lens l is assigned to mountain m
    x = model.addVars(len(lenses), len(mountains), vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total cost of selected lenses
    model.setObjective(
        gp.quicksum(lenses[l]['cost'] * x[l, m] for l in range(len(lenses)) for m in range(len(mountains))),
        GRB.MINIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Constraint 1: Minimum Lenses per Mountain
    for m in range(len(mountains)):
        model.addConstr(
            gp.quicksum(x[l, m] for l in range(len(lenses))) >= 1,
            name=f"min_lenses_mountain_{m+1}"
        )
    
    # Constraint 2: Maximum Lenses per Mountain
    for m in range(len(mountains)):
        model.addConstr(
            gp.quicksum(x[l, m] for l in range(len(lenses))) <= 3,
            name=f"max_lenses_mountain_{m+1}"
        )
    
    # Constraint 3: Focal Length Requirement
    for l in range(len(lenses)):
        for m in range(len(mountains)):
            model.addConstr(
                lenses[l]['focal_length_mm'] * x[l, m] >= mountains[m]['minimum_focal_length'] * x[l, m],
                name=f"focal_length_lens_{l+1}_mountain_{m+1}"
            )
    
    # Constraint 4: Aperture Requirement
    for l in range(len(lenses)):
        for m in range(len(mountains)):
            model.addConstr(
                lenses[l]['max_aperture'] * x[l, m] >= mountains[m]['minimum_aperture'] * x[l, m],
                name=f"aperture_lens_{l+1}_mountain_{m+1}"
            )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for l in range(len(lenses)):
            for m in range(len(mountains)):
                if x[l, m].x > 0.5:
                    print(f"Lens {lenses[l]['lens_id']} assigned to Mountain {mountains[m]['mountain_id']}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

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