
/*
 * Josh Holtrop
 * 2008-08-30
 * Grand Valley State University
 * CS677
 * Programming Assignment #1
 */

#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <map>
using namespace std;

/**************************************************************************
 * The Transition class holds a (cost, destination node) pair             *
 *************************************************************************/
class Transition
{
    public:
        int cost;
        string destination;
};

/**************************************************************************
 * Path objects represent a path through the the graph starting at the    *
 * start node. The Path object has a certain cost associated with it.     *
 * The path may or may not end on the goal node.                          *
 *************************************************************************/
class Path
{
    public:
        int cost;
        vector<string> node_list;
        map<string, bool> visited_nodes;

        /* Default constructor */
        Path() { cost = 0; }
        /* Construct a Path from another one, appending a new node from t */
        Path(const Path & orig, const Transition & t)
        {
            cost = orig.cost + t.cost;
            node_list = orig.node_list;
            node_list.push_back(t.destination);
            visited_nodes = orig.visited_nodes;
            visited_nodes[t.destination] = true;
        }

        // comparison operator for making a min priority_queue
        bool operator()(const Path & p1, const Path & p2)
        {
            return p1.cost > p2.cost;
        }
};
/* This function allows Path objects to be printed to ostream objects */
ostream & operator<<(ostream & out, const Path & p)
{
    int sz = p.node_list.size();
    out << '[';
    for (int i = 0; i < sz; i++)
    {
        out << p.node_list[i];
        if (i < sz-1)
            out << ", ";
    }
    out << ']';
    return out;
}

/**************************************************************************
 * Global type definitions                                                *
 *************************************************************************/
typedef vector<Transition> Transition_List_t;
typedef map<string, Transition_List_t> Transition_Table_t;

/**************************************************************************
 * Function Prototypes                                                    *
 *************************************************************************/
void createTransition(Transition_Table_t & transitions,
        string src, string dest, int cost);
void solveMinEnergy(string start, string goal,
        Transition_Table_t & transitions);

/**************************************************************************
 * Main Function                                                          *
 *************************************************************************/
int main(int argc, char * argv[])
{
    if (argc != 2)
    {
        cerr << "Usage: minimum-energy <input-file>" << endl;
        return 42;
    }

    ifstream i(argv[1]);
    if (!i.is_open())
    {
        cerr << "Could not open " << argv[1] << endl;
        return 1;
    }

    string startNode;
    string goalNode;
    Transition_Table_t node_transitions;

    /* Read the input file */
    i >> startNode;
    i >> goalNode;
    while (!i.eof())
    {
        string src, dest;
        int cost;
        i >> src >> dest >> cost;
        createTransition(node_transitions, src, dest, cost);
    }
    i.close();

    /* Once the input is read, try to find a path */
    solveMinEnergy(startNode, goalNode, node_transitions);

    return 0;
}

/**************************************************************************
 * Add a new transition into the node_transitions table                   *
 *************************************************************************/
void createTransition(Transition_Table_t & transitions,
        string src, string dest, int cost)
{
    if (transitions.find(src) == transitions.end())
    {
        /* src is not a key in transitions, so insert a new one */
        Transition_List_t tl;
        transitions[src] = tl;
    }
    Transition t;
    t.cost = cost;
    t.destination = dest;
    transitions[src].push_back(t);
}

/**************************************************************************
 * Solve the minimum energy problem for start node start, goal node goal, *
 * and transition table set transitions                                   *
 *************************************************************************/
void solveMinEnergy(string start, string goal,
        Transition_Table_t & transitions)
{
    priority_queue<Path, vector<Path>, Path> pathQueue;
    /* Create an initial path with just the starting node in it */
    Path init;
    init.cost = 0;
    init.node_list.push_back(start);
    init.visited_nodes[start] = true;
    pathQueue.push(init);

    /* LOOP
     *   IF there are no paths left in the pathQueue
     *     THEN there is no path to the goal; exit loop
     *   END IF
     *   IF the shortest path in the pathQueue ends at the goal node,
     *     THEN we are done; exit loop
     *   END IF
     *   Remove the shortest path in the pathQueue
     *   FOR EACH transition from the last node in the shortest path
     *     IF the destination of the transition has not been visited
     *         yet in this path
     *       THEN Add a new path to the pathQueue with the destination
     *         node of the transition appended to the end of the path
     *     END IF
     *   END FOR EACH
     * END LOOP
     */
    for (;;)
    {
        if (pathQueue.size() == 0)
        {
            cout << "There is no path from " << start << " to " << goal
                << "!" << endl;
            break;
        }
        if (pathQueue.top().visited_nodes.find(goal)
                != pathQueue.top().visited_nodes.end())
        {
            cout << "The minimum-energy set of reactions follows the path "
                << pathQueue.top() << " with a energy cost of "
                << pathQueue.top().cost
                << endl;
            break;
        }
        Path p = pathQueue.top();
        pathQueue.pop();
        string last = p.node_list[p.node_list.size() - 1];
        if (transitions.find(last) != transitions.end())
        {
            for (int i = 0, sz = transitions[last].size(); i < sz; i++)
            {
                if (p.visited_nodes.find(transitions[last][i].destination)
                        == p.visited_nodes.end())
                {
                    /* the destination has not yet been visited in this path */
                    Path newPath(p, transitions[last][i]);
                    pathQueue.push(newPath);
                }
            }
        }
    }
}
