using System;
using System.IO;
using System.Globalization;
using Hexaly.Optimizer;

public class OrderPicking : IDisposable
{
    int nbOrders;
    int[][] distancesData;

    HexalyOptimizer optimizer;

    HxExpression pickingList;

    HxExpression objective;

    HxExpression indexInitialPosition;

    public OrderPicking() 
    {
        optimizer = new HexalyOptimizer();
    }

    public void ReadInstance(string filename) 
    {
        using(StreamReader input = new StreamReader(filename)) 
        {
            string[] splittedLine = input.ReadLine().Split();
            nbOrders = int.Parse(splittedLine[0]) + 1;
            distancesData = new int[nbOrders][];
            for (int i = 0; i < nbOrders; ++i) 
            {
                splittedLine = input.ReadLine().Split();
                distancesData[i] = new int[nbOrders];
                for (int j = 0; j < nbOrders; ++j) 
                {
                    distancesData[i][j] = int.Parse(splittedLine[j]);
                }
            }
        }
    }

    public void Dispose() 
    {
        if (optimizer != null)
            optimizer.Dispose();
    } 

    public void Solve(int timeLimit) 
    {
        // Declare the model
        HxModel model = optimizer.GetModel();

        // Declare the list containing the picking order
        pickingList = model.List(nbOrders);

        // All orders must be picked
        model.Constraint(model.Count(pickingList) == nbOrders);

        // Create a HexalyOptimizer array for the distance matrix in order to access it using the "at" operator
        HxExpression distancesMatrix = model.Array(distancesData);

        // Lambda expression to compute the distance to the next order
        HxExpression distanceToNextOrderLambda = model.LambdaFunction( 
            i => distancesMatrix[pickingList[i]][pickingList[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, nbOrders - 1), distanceToNextOrderLambda) 
            + distancesMatrix[pickingList[nbOrders - 1]][pickingList[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.
        indexInitialPosition = model.IndexOf(pickingList, 0);

        model.Minimize(objective);

        // End of the model declaration
        model.Close();

        optimizer.GetParam().SetTimeLimit(timeLimit);

        optimizer.Solve();
    }

    public void WriteSolution(string filename) 
    {
        using (StreamWriter output = new StreamWriter(filename)) 
        {
            output.WriteLine(objective.GetIntValue());
            HxCollection solutionCollection = pickingList.GetCollectionValue();
            for (int i = 0; i < nbOrders; ++i) 
            {
                int index = ((int)indexInitialPosition.GetValue() + i) % nbOrders;
                output.Write(solutionCollection[index] + " ");
            }
            output.Close();
        } 
    }


    public static void Main(string[] args) 
    {
        if (args.Length < 1) 
        {
            Console.WriteLine("Usage: OrderPicking inputFile [outputFile] [timeLimit]");
            Environment.Exit(1);
        }
        string inputFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        int timeLimit = args.Length > 2 ? int.Parse(args[2]) : 10;
        using (OrderPicking model = new OrderPicking()) 
        {
            model.ReadInstance(inputFile);
            model.Solve(timeLimit);
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }

}
