# Complete GUROBIPY implementation - Retry Attempt 1

import gurobipy as gp
from gurobipy import GRB

def ship_mission_optimization():
    # 1. MODEL & DATA SETUP
    model = gp.Model("ship_mission_optimization")
    
    # Data from the database
    missions = [
        {'mission_id': 1, 'min_tonnage': 5000, 'min_speed': 20, 'required_nationality': 'USA'},
        {'mission_id': 2, 'min_tonnage': 6000, 'min_speed': 25, 'required_nationality': 'UK'},
        {'mission_id': 3, 'min_tonnage': 7000, 'min_speed': 30, 'required_nationality': 'Canada'}
    ]
    
    ships = [
        {'ship_id': 1, 'tonnage': 5500, 'speed_knots': 22, 'nationality': 'USA'},
        {'ship_id': 2, 'tonnage': 6500, 'speed_knots': 27, 'nationality': 'UK'},
        {'ship_id': 3, 'tonnage': 7500, 'speed_knots': 32, 'nationality': 'Canada'}
    ]
    
    # CRITICAL: Validate array lengths before loops
    assert len(missions) > 0 and len(ships) > 0, "Empty missions or ships data"
    
    # 2. VARIABLES
    # Decision variable x_ij: whether ship i is assigned to mission j
    x = model.addVars(len(ships), len(missions), vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total operational cost
    c_tonnage = 1
    c_speed = 1
    model.setObjective(
        gp.quicksum(
            (c_tonnage * ships[i]['tonnage'] + c_speed * ships[i]['speed_knots']) * x[i, j]
            for i in range(len(ships))
            for j in range(len(missions))
        ),
        GRB.MINIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Mission Assignment Constraint: Each mission must be assigned to exactly one ship
    for j in range(len(missions)):
        model.addConstr(
            gp.quicksum(x[i, j] for i in range(len(ships))) == 1,
            name=f"mission_assignment_{j}"
        )
    
    # Ship Assignment Constraint: Each ship can be assigned to at most one mission
    for i in range(len(ships)):
        model.addConstr(
            gp.quicksum(x[i, j] for j in range(len(missions))) <= 1,
            name=f"ship_assignment_{i}"
        )
    
    # Tonnage Requirement Constraint: The tonnage of the ship must meet or exceed the mission's minimum tonnage
    for i in range(len(ships)):
        for j in range(len(missions)):
            model.addConstr(
                ships[i]['tonnage'] * x[i, j] >= missions[j]['min_tonnage'] * x[i, j],
                name=f"tonnage_requirement_{i}_{j}"
            )
    
    # Speed Requirement Constraint: The speed of the ship must meet or exceed the mission's minimum speed
    for i in range(len(ships)):
        for j in range(len(missions)):
            model.addConstr(
                ships[i]['speed_knots'] * x[i, j] >= missions[j]['min_speed'] * x[i, j],
                name=f"speed_requirement_{i}_{j}"
            )
    
    # Nationality Requirement Constraint: The nationality of the ship must match the mission's required nationality
    for i in range(len(ships)):
        for j in range(len(missions)):
            if ships[i]['nationality'] == missions[j]['required_nationality']:
                model.addConstr(
                    x[i, j] == x[i, j],
                    name=f"nationality_requirement_{i}_{j}"
                )
            else:
                model.addConstr(
                    x[i, j] == 0,
                    name=f"nationality_requirement_{i}_{j}"
                )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in range(len(ships)):
            for j in range(len(missions)):
                if x[i, j].x > 0.5:
                    print(f"Ship {ships[i]['ship_id']} assigned to Mission {missions[j]['mission_id']}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
ship_mission_optimization()