#include "Reducibility.h"

#include "llvm/ADT/SetVector.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Scalar.h"

#include "LLVMUtils.h"

#include <fstream>
#include <vector>
#include <map>

#define DBGS errs
//#define DBGS dbgs

using namespace llvm;

struct Node
{
  SetVector<Node*> in;
  SetVector<Node*> out;
  SetVector<BasicBlock*> blocks; // block 0 dominates all others in this node
  size_t numInstructions = 0;

  Node() {}
  Node(BasicBlock* B) { insert(B); }

  void insert(BasicBlock* B)
  {
    numInstructions += B->size();
    blocks.insert(B);
  }
};


static void printDotGraph(const std::vector<Node*> nodes, const std::string& filename)
{
  DBGS() << "Writing " << filename << " ...";
  std::ofstream out(filename);
  if (!out)
  {
    DBGS() << "FAILED\n";
    return;
  }

  // Give nodes a numerical index to make the output cleaner
  std::map<Node*, int> idxMap;
  for (size_t i = 0; i < nodes.size(); ++i)
    idxMap[nodes[i]] = i;


  // Error check - make sure that all the out/in nodes are in the map
  for (Node* N : nodes)
  {
    for (Node* P : N->in)
    {
      if (idxMap.find(P) == idxMap.end())
        DBGS() << "MISSING INPUT NODE\n";
      if (P->out.count(N) == 0)
        DBGS() << "MISSING OUTGOING EDGE FROM PREDECESSOR.\n";
    }
    for (Node* S : N->out)
    {
      if (idxMap.find(S) == idxMap.end())
        DBGS() << "MISSING OUTPUT NODE\n";
      if (S->in.count(N) == 0)
        DBGS() << "MISSING INCOMING EDGE FROM SUCCESSOR.\n";
    }
  }


  // Print header
  out << "digraph g {\n";
  out << "node [\n";
  out << "  fontsize = \"12\"\n";
  out << "  labeljust = \"l\"\n";
  out << "]\n";

  for (unsigned i = 0; i < nodes.size(); ++i)
  {
    Node* N = nodes[i];

    // node
    out << "  N" << i << " [shape=record,label=\"";
    for (BasicBlock* B : N->blocks)
      out << B->getName().str() << "\\n";
    out << "\"];\n";

    // out edges
    for (Node* S : N->out)
      out << "  N" << i << " -> N" << idxMap[S] << ";\n";

    // in edges
    //for( Node* P : N->in )    
    //  out << "  N" << idxMap[P] << " -> N" << i << " [style=dotted];\n";
  }

  out << "}\n";

  DBGS() << "\n";
}

static void printDotGraph(const std::vector<Node*> nodes, Function* F, int step)
{
  printDotGraph(nodes, ("red." + F->getName() + "_" + std::to_string(step) + ".dot").str());
}


static Node* split(Node* N, std::map<BasicBlock*, Node*>& bbToNode, bool firstSplit)
{
  // Remove one predecessor P from N
  assert(N->in.size() > 1);
  Node* P = N->in.pop_back_val();
  P->out.remove(N);

  // Point P to the clone of N, Np
  Node* Np = new Node();
  P->out.insert(Np);
  Np->in.insert(P);

  // Copy successors of N to Np
  for (Node* S : N->out)
  {
    Np->out.insert(S);
    S->in.insert(Np);
  }

#if 1
  // Run reg2mem on the whole function so we don't have to deal with phis
  if (firstSplit)
  {
    runPasses(N->blocks[0]->getParent(), {
      createDemoteRegisterToMemoryPass()
    });
  }


  // Clone N into Np
  ValueToValueMapTy VMap;
  for (BasicBlock* B : N->blocks)
  {
    BasicBlock* Bp = CloneBasicBlock(B, VMap, ".c", B->getParent());
    Np->insert(Bp);
    VMap[B] = Bp;
  }
  for (BasicBlock* B : Np->blocks)
    for (Instruction& I : *B)
      RemapInstruction(&I, VMap, RF_NoModuleLevelChanges | RF_IgnoreMissingEntries);

  // Remap terminators of P from N to Np
  for (BasicBlock* B : P->blocks)
    RemapInstruction(B->getTerminator(), VMap, RF_NoModuleLevelChanges | RF_IgnoreMissingEntries);

#else
  // Clone N into Np
  ValueToValueMapTy VMap;
  for (BasicBlock* B : N->blocks)
  {
    BasicBlock* Bp = CloneBasicBlock(B, VMap, ".c", B->getParent());
    Np->insert(Bp);
    VMap[B] = Bp;
  }
  for (BasicBlock* B : Np->blocks)
    for (Instruction& I : *B)
      RemapInstruction(&I, VMap, RF_NoModuleLevelChanges | RF_IgnoreMissingEntries);


  // Remove incoming values from phis in Np that don't come from actual predecessors
  BasicBlock* NpEntry = Np->blocks[0];
  std::set<BasicBlock*> predSet(pred_begin(NpEntry), pred_end(NpEntry));
  auto I = NpEntry->begin();
  while (PHINode* phi = dyn_cast<PHINode>(I++))
  {
    if (phi->getNumIncomingValues() == predSet.size())
      continue;
    for (unsigned i = 0; i < phi->getNumIncomingValues(); )
    {
      BasicBlock* B = phi->getIncomingBlock(i);
      if (!predSet.count(B))
      {
        phi->removeIncomingValue(B);
        continue;
      }
      ++i;
    }
  }


  // Remove phi references to P in N. (Do this before remapping terminators.)
  BasicBlock* Nentry = N->blocks[0];
  for (BasicBlock* PB : predecessors(Nentry))
  {
    if (P->blocks.count(PB))
      Nentry->removePredecessor(PB);
  }

  // Remap terminators of P from N to Np
  for (BasicBlock* B : P->blocks)
    RemapInstruction(B->getTerminator(), VMap, RF_NoModuleLevelChanges | RF_IgnoreMissingEntries);


  // Update phis in successors of Np.
  // There are several cases for a value Vs reaching S. Vs may be defined in N and
  // a clone Vsp in Np or only passing through one or the other. Furthermore, Vs may 
  // either appear in a phi in the entry block of S or not.
  // 1) Vs defined in N (and clone Vsp in Np) and in phi:
  //    Add incoming value [Vsp, Bp] for cloned value Vsp from predecessor basic
  //    block Bp in Np wherever [Vs, B] appears
  // 2) Vs defined in N (and clone Vsp in Np) and not in phi:
  //    Add phi [Vs,B],[Vsp,Bp] if Vs reaches a use in or through S
  // 3) Vs passing through N or Np and in phi
  //    Change [Vs,B] to [Vs,Bp] in phis in S if Vs reached S through P
  // 4) Vs passing through N or Np and not in a phi
  //    Do nothing
  // 
  // TODO: Only 1) is implemented below and it isn't checking for definition in N
  for (Node* S : Np->out)
  {
    BasicBlock* Sentry = S->blocks[0];
    auto I = Sentry->begin();
    while (PHINode* phi = dyn_cast<PHINode>(I++))
    {
      for (unsigned i = 0; i < phi->getNumIncomingValues(); ++i)
      {
        BasicBlock* B = phi->getIncomingBlock(i);
        if (N->blocks.count(B))
        {
          Value* V = phi->getIncomingValue(i);
          Value* Vp = VMap[V];
          if (!Vp)
            Vp = V; // Def not in N
          BasicBlock* Bp = dyn_cast<BasicBlock>(VMap[B]);
          phi->addIncoming(Vp, Bp);
        }
      }
    }
  }
#endif

  return Np;
}

// Returns the number of splits
int makeReducible(Function* F)
{
  // Break critical edges now in case we need to do mem2reg in split(). mem2reg
  // will break critical edges and the CFG needs to remain unchanged.
  runPasses(F, {
    createBreakCriticalEdgesPass()
  });

  // initialize nodes
  std::vector<Node*> nodes;
  std::map<BasicBlock*, Node*> bbToNode;
  for (BasicBlock& B : *F)
  {
    nodes.push_back(new Node(&B));
    bbToNode[&B] = nodes.back();
  }

  // initialize edges
  for (Node* N : nodes)
  {
    for (BasicBlock* B : successors(N->blocks[0]))
    {
      Node* BN = bbToNode[B];
      N->out.insert(BN);
      BN->in.insert(N);
    }
  }

  int step = 0;
  bool print = false;
  if (print) printDotGraph(nodes, F, step++);

  int numSplits = 0;
  while (!nodes.empty())
  {
    bool changed;
    do
    {
  // It might more efficient to use a worklist based implementation instead
  // of iterating over the vector.
      changed = false;
      for (size_t i = 0; i < nodes.size(); )
      {
        Node* N = nodes[i];

        // Remove self references
        if (N->in.count(N))
        {
          N->in.remove(N);
          N->out.remove(N);
          changed = true;
        }

        // Remove singletons
        if (N->in.size() == 0 && N->out.size() == 0)
        {
          nodes.erase(nodes.begin() + i);
          changed = true;
          if (print) printDotGraph(nodes, F, step++);
          continue;
        }

        // Remove nodes with only one incoming edge
        if (N->in.size() == 1)
        {
          // fold into predecessor
          Node* P = N->in.back();
          P->blocks.insert(N->blocks.begin(), N->blocks.end());
          P->out.remove(N);
          for (Node* S : N->out)
          {
            S->in.remove(N);
            P->out.insert(S);
            S->in.insert(P);
          }
          P->numInstructions += N->numInstructions;
          nodes.erase(nodes.begin() + i);
          changed = true;
          if (print) printDotGraph(nodes, F, step++);
          continue;
        }

        i++;
      }
    } while (changed);

    if (!nodes.empty())
    {
      // Duplicate the smallest node with more than one incoming edge. Better 
      // methods exist for picking the node to split, e.g. "Making Graphs Reducible
      // with Controlled Node Splitting" by Janssen and Corporaal.
      size_t idxMin = ~0;
      for (size_t i = 0; i < nodes.size(); ++i)
      {
        if (nodes[i]->in.size() <= 1)
          continue;

        if (idxMin == ~0 || nodes[i]->numInstructions < nodes[idxMin]->numInstructions)
          idxMin = i;
      }
      nodes.push_back(split(nodes[idxMin], bbToNode, numSplits == 0));
      numSplits++;
      if (print) printDotGraph(nodes, F, step++);
    }
  }
  return numSplits;
}
