#!/usr/bin/env python3
"""
Gurobipy 12.0.2 Implementation for Bike Redistribution Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def bike_redistribution_optimization():
    """Optimize bike redistribution to minimize unmet demands and movement costs."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("bike_redistribution")
    
    # Stations
    stations = [1, 2, 3]
    
    # Cost per bike movement
    cost_per_bike_movement = 3.0
    
    # Unmet demands
    unmet_demands = {1: 2, 2: 0, 3: 1}
    
    # Dock capacities
    dock_capacities = {1: 15, 2: 20, 3: 10}
    
    # Initial bike availability (assumed data)
    initial_bikes = {1: 10, 2: 15, 3: 8}
    
    # CRITICAL: Validate array lengths before loops
    assert len(stations) == len(unmet_demands) == len(dock_capacities) == len(initial_bikes), "Array length mismatch"
    
    # 2. VARIABLES
    # Decision variables for bike movements
    x = model.addVars(stations, stations, vtype=GRB.INTEGER, name="x", lb=0)
    
    # Decision variables for unmet demands
    u = model.addVars(stations, vtype=GRB.INTEGER, name="u", lb=0)
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total cost of bike movements and unmet demands
    model.setObjective(
        cost_per_bike_movement * gp.quicksum(x[i, j] for i in stations for j in stations if i != j) +
        gp.quicksum(u[i] for i in stations),
        GRB.MINIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Station Capacity Constraint
    for j in stations:
        model.addConstr(
            gp.quicksum(x[i, j] for i in stations if i != j) <= dock_capacities[j],
            name=f"capacity_{j}"
        )
    
    # Initial Bike Availability Constraint
    for i in stations:
        model.addConstr(
            gp.quicksum(x[i, j] for j in stations if i != j) <= initial_bikes[i],
            name=f"initial_bikes_{i}"
        )
    
    # Unmet Demand Constraint
    for i in stations:
        model.addConstr(
            u[i] >= unmet_demands[i],
            name=f"unmet_demand_{i}"
        )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in stations:
            for j in stations:
                if i != j and x[i, j].x > 1e-6:
                    print(f"Bikes moved from station {i} to station {j}: {x[i, j].x:.0f}")
        for i in stations:
            print(f"Unmet demands at station {i}: {u[i].x:.0f}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

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