/*
 * Copyright 2011 Christoph Bumiller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef __NV50_IR_GRAPH_H__
#define __NV50_IR_GRAPH_H__

#include "nv50_ir_util.h"
#include <vector>

namespace nv50_ir {

#define ITER_NODE(x) reinterpret_cast<Graph::Node *>((x).get())
#define ITER_EDGE(x) reinterpret_cast<Graph::Edge *>((x).get())

// A connected graph.
class Graph
{
public:
   class Node;

   class Edge
   {
   public:
      enum Type
      {
         UNKNOWN,
         TREE,
         FORWARD,
         BACK,
         CROSS, // e.g. loop break
         DUMMY
      };

      Edge(Node *dst, Node *src, Type kind);
      ~Edge() { unlink(); }

      inline Node *getOrigin() const { return origin; }
      inline Node *getTarget() const { return target; }

      inline Type getType() const { return type; }
      const char *typeStr() const;

   private:
      Node *origin;
      Node *target;

      Type type;
      Edge *next[2]; // next edge outgoing/incident from/to origin/target
      Edge *prev[2];

      void unlink();

      friend class Graph;
   };

   class EdgeIterator : public Iterator
   {
   public:
      EdgeIterator() : e(0), t(0), d(0), rev(false) { }
      EdgeIterator(Graph::Edge *first, int dir, bool reverse)
         : d(dir), rev(reverse)
      {
         t = e = ((rev && first) ? first->prev[d] : first);
      }

      virtual void next()
      {
         Graph::Edge *n = (rev ? e->prev[d] : e->next[d]);
         e = (n == t ? NULL : n);
      }
      virtual bool end() const { return !e; }
      virtual void *get() const { return e; }

      inline Node *getNode() const { assert(e); return d ?
                                                   e->origin : e->target; }
      inline Edge *getEdge() const { return e; }
      inline Edge::Type getType() { return e ? e->getType() : Edge::UNKNOWN; }

   private:
      Graph::Edge *e;
      Graph::Edge *t;
      int d;
      bool rev;
   };

   class Node
   {
   public:
      Node(void *);
      ~Node() { cut(); }

      void attach(Node *, Edge::Type);
      bool detach(Node *);
      void cut();

      inline EdgeIterator outgoing(bool reverse = false) const;
      inline EdgeIterator incident(bool reverse = false) const;

      inline Node *parent() const; // returns NULL if count(incident edges) != 1

      bool reachableBy(const Node *node, const Node *term) const;

      inline bool visit(int);
      inline int  getSequence() const;

      inline int incidentCountFwd() const; // count of incident non-back edges
      inline int incidentCount() const { return inCount; }
      inline int outgoingCount() const { return outCount; }

      Graph *getGraph() const { return graph; }

      void *data;

   private:
      Edge *in;
      Edge *out;
      Graph *graph;

      int visited;

      int16_t inCount;
      int16_t outCount;
   public:
      int tag; // for temporary use

      friend class Graph;
   };

public:
   Graph();
   ~Graph(); // does *not* free the nodes (make it an option ?)

   inline Node *getRoot() const { return root; }

   inline unsigned int getSize() const { return size; }

   inline int nextSequence();

   void insert(Node *node); // attach to or set as root

   IteratorRef iteratorDFS(bool preorder = true);
   IteratorRef iteratorCFG();

   // safe iterators are unaffected by changes to the *edges* of the graph
   IteratorRef safeIteratorDFS(bool preorder = true);
   IteratorRef safeIteratorCFG();

   void classifyEdges();

   // @weights: indexed by Node::tag
   int findLightestPathWeight(Node *, Node *, const std::vector<int>& weights);

private:
   void classifyDFS(Node *, int&);

private:
   Node *root;
   unsigned int size;
   int sequence;
};

int Graph::nextSequence()
{
   return ++sequence;
}

Graph::Node *Graph::Node::parent() const
{
   if (inCount != 1)
      return NULL;
   assert(in);
   return in->origin;
}

bool Graph::Node::visit(int v)
{
   if (visited == v)
      return false;
   visited = v;
   return true;
}

int Graph::Node::getSequence() const
{
   return visited;
}

Graph::EdgeIterator Graph::Node::outgoing(bool reverse) const
{
   return EdgeIterator(out, 0, reverse);
}

Graph::EdgeIterator Graph::Node::incident(bool reverse) const
{
   return EdgeIterator(in, 1, reverse);
}

int Graph::Node::incidentCountFwd() const
{
   int n = 0;
   for (EdgeIterator ei = incident(); !ei.end(); ei.next())
      if (ei.getType() != Edge::BACK)
         ++n;
   return n;
}

} // namespace nv50_ir

#endif // __NV50_IR_GRAPH_H__