import hexaly.optimizer
import sys

def read_elem(filename) :
    with open(filename) as f :
        return [str(elem) for elem in f.read().split()]

def read_instance(filename) :
    file_iterator = iter(read_elem(filename))
    nb_orders = int(next(file_iterator)) + 1
    distances_data = [None] * nb_orders
    for i in range(nb_orders) :
        distances_data[i] = [None] * nb_orders
        for j in range(nb_orders) :
            distances_data[i][j] = int(next(file_iterator))
    return nb_orders, distances_data

def main(input_file, output_file, time_limit) :
    # Read the instance from input_file
    nb_orders, distances_data = read_instance(input_file)
    
    with hexaly.optimizer.HexalyOptimizer() as optimizer :
        # Declare the model
        model = optimizer.model

        # Declare the list containing the picking order
        picking_list = model.list(nb_orders)

        # All orders must be picked
        model.constraint(model.count(picking_list) == nb_orders)

        # Create an Hexaly array for the distance matrix in order to access it using the "at" operator
        distances_matrix = model.array(distances_data)

        # Lambda expression to compute the distance to the next order
        distance_to_next_order_lambda = model.lambda_function( 
            lambda i : model.at(distances_matrix, picking_list[i], picking_list[i + 1]))

        # The objective is to minimize the total distance required to pick 
        # all the orders and to go back to the initial position
        objective = model.sum(model.range(0, nb_orders - 1), distance_to_next_order_lambda) \
            + model.at(distances_matrix, picking_list[nb_orders - 1], picking_list[0])

        # Store the index of the initial position in the list.
        # It will be used at the end to write the solution starting from the initial point.
        index_initial_position = model.index(picking_list, 0)

        model.minimize(objective)

        # End of the model declaration
        model.close()

        optimizer.param.time_limit = time_limit

        optimizer.solve()

        if output_file != None :
            with open(output_file, 'w') as f:
                f.write("%i\n" % objective.value)
                for i in range(nb_orders):
                    index = (index_initial_position.get_value() + i) % nb_orders
                    f.write("%i " % picking_list.value[index])


if __name__ == '__main__' :
    if len(sys.argv) < 2:
        print("Usage: python order_picking.py input_file [output_file] [time_limit]")
        sys.exit(1)
    input_file = sys.argv[1]
    output_file = sys.argv[2] if len(sys.argv) >= 3 else None
    time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 10
    main(input_file, output_file, time_limit)
