/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * File:   temporalseqgraph.cpp
 * Author: pgess
 * 
 * Created on February 4, 2019, 4:51 PM
 */

#include "temporalseqgraph.h"

using namespace xreate::cfa;
using namespace std;

namespace std {
  bool operator<(const xreate::cfa::TemporalNode& x, const xreate::cfa::TemporalNode& y) {
    if(x.scope != y.scope) return x.scope < y.scope;
    return x.mark < y.mark;
  }
};

TemporalSeqGraph::TemporalSeqGraph(TranscendLayer* transcend)
: __transcend(transcend) { }

void
TemporalSeqGraph::addSubScopes(const CodeScope* scopeIn, const std::list<CodeScope*>& scopesChild) {
  auto socket = addSocket(scopeIn, TemporalOperator::AND);

  for(auto scopeChild : scopesChild) {
    TemporalNode nodeChild = TemporalNode{__transcend->pack(scopeChild), TemporalOperator::SCOPE};
    connect(socket.first, nodeChild);
    connect(nodeChild, socket.second);
  }
}

void
TemporalSeqGraph::addBranchScopes(const CodeScope* scopeIn, const std::list<CodeScope*>& scopesChild) {
  auto socket = addSocket(scopeIn, TemporalOperator::AND);
  TemporalNode toNode{__idNextVacant++, TemporalOperator::OR};
  connect(toNode, socket.second);

  for(auto scopeChild : scopesChild) {
    TemporalNode nodeChild = TemporalNode{__transcend->pack(scopeChild), TemporalOperator::SCOPE};
    connect(socket.first, nodeChild);
    connect(nodeChild, toNode);
  }
}

std::pair<TemporalNode, TemporalNode>
TemporalSeqGraph::addSocket(const CodeScope* scopeIn, TemporalOperator mark) {
  TemporalNode nodeFrom;
  TemporalNode nodeIn = TemporalNode{__transcend->pack(scopeIn), TemporalOperator::SCOPE};

  auto rangeInward = graph.right.equal_range(nodeIn);
  size_t sizeInward = std::distance(rangeInward.first, rangeInward.second);

  if        (sizeInward == 1) {
    //if scope has single inward connection
    nodeFrom = rangeInward.first->second;

  } else if (sizeInward > 1) {
    //if scope has multy inward con:
    nodeFrom = insertBefore(nodeIn, EMPTY);

  } else {
    //no inward connections:
    nodeFrom = insertBefore(nodeIn, EMPTY);
  }

  TemporalNode nodeTo = insertAfter(nodeIn, mark);
  return {nodeFrom, nodeTo};
}

Socket
TemporalSeqGraph::getFnSocket(ManagedFnPtr calleeFn) {
  if(__cacheFnSockets.count(calleeFn)) {
    return __cacheFnSockets.at(calleeFn);
  }

  TemporalNode fromNode{__idNextVacant++, TemporalOperator::EMPTY};
  TemporalNode toNode{__idNextVacant++, TemporalOperator::EMPTY};
  Socket fnSock = make_pair(fromNode, toNode);
  __cacheFnSockets.emplace(calleeFn, fnSock);

  return fnSock;
}

Socket
TemporalSeqGraph::getUncertainFnSocket(const std::string& calleeName, const std::list<ManagedFnPtr>& candidates) {
  if(__cacheUncertainFnSockets.count(calleeName)) {
    return __cacheUncertainFnSockets.at(calleeName);
  }

  Socket fnSock;
  if(candidates.size() == 0) {
    //External Function
    TemporalNode node{__idNextVacant++, TemporalOperator::EMPTY};
    fnSock = make_pair(node, node);

  } else {
    //Multiple specializations
    TemporalNode fromNode{__idNextVacant++, TemporalOperator::EMPTY};
    TemporalNode toNode{__idNextVacant++, TemporalOperator::OR};
    fnSock = make_pair(fromNode, toNode);

    for (const ManagedFnPtr& candidate : candidates) {
      Socket candidateSock = getFnSocket(candidate);
      connect(fromNode, candidateSock.first);
      connect(candidateSock.second, toNode);
    }
  }
  __cacheUncertainFnSockets.emplace(calleeName, fnSock);

  return fnSock;
}

TemporalNode
TemporalSeqGraph::insertBefore(TemporalNode node, TemporalOperator mark) {
  TemporalNode nodeNew{__idNextVacant++, mark};

  auto nodesInRange = graph.right.equal_range(node);
  list<TemporalNode> nodesIn;
  for(auto nodeInIt = nodesInRange.first; nodeInIt != nodesInRange.second; ++nodeInIt) {
    nodesIn.push_back(nodeInIt->second);
  }

  graph.right.erase(node);
  for(auto nodeIn : nodesIn) {
    connect(nodeIn, nodeNew);
  }

  connect(nodeNew, node);
  return nodeNew;
}

TemporalNode
TemporalSeqGraph::insertAfter(TemporalNode node, TemporalOperator mark) {
  TemporalNode nodeNew{__idNextVacant++, mark};

  auto nodesOutRange = graph.left.equal_range(node);
  list<TemporalNode> nodesOut;
  for(auto nodeOutIt = nodesOutRange.first; nodeOutIt != nodesOutRange.second; ++nodeOutIt) {
    nodesOut.push_back(nodeOutIt->second);
  }

  graph.left.erase(node);
  for(auto nodeOut : nodesOut) {
    connect(nodeNew, nodeOut);
  }

  connect(node, nodeNew);
  return nodeNew;
}

void
TemporalSeqGraph::connect(TemporalNode nodeFrom, TemporalNode nodeTo) {
  graph.insert(Graph::value_type(nodeFrom, nodeTo));
}

void
TemporalSeqGraph::connectGuarded(const Socket& from, const Socket& to) {
  unsigned int guard = __guardNextVacant++;

  graphGuarded.emplace(from.first, make_pair(to.first, TemporalGuard{TemporalGuard::IN, guard}));
  graphGuarded.emplace(to.second, make_pair(from.second, TemporalGuard{TemporalGuard::OUT, guard}));
}

void
TemporalSeqGraph::print(std::ostringstream &output) const {
  for (auto entry : graph) {
    output
      << "cfa_seq("
      << entry.left  << ", "
      << entry.right << ").\n";
  }

  for (auto entry : graphGuarded) {
    output
      << "cfa_seq_guarded("
      << entry.first << ", "
      << entry.second.first << ", "
      << (entry.second.second.mark == TemporalGuard::IN ? "in(" : "out(")
      << entry.second.second.id << ")).\n";
  }
}

bool TemporalSeqGraph::isOrdered(const ScopePacked& scopeAfter, const ScopePacked& scopeBefore) const {
  set<TemporalNode> visitedNodes;
  return isOrdered(TemporalNode{scopeAfter, TemporalOperator::SCOPE},
  TemporalNode{scopeBefore, TemporalOperator::SCOPE},
  visitedNodes);
}

bool
TemporalSeqGraph::isOrdered(const TemporalNode& nodeAfter, const TemporalNode& nodeBefore, set<TemporalNode>& visitedNodes) const {
  if (nodeAfter.scope == nodeBefore.scope && nodeAfter.mark == nodeBefore.mark) return true;
  if (visitedNodes.count(nodeAfter)) return false;
  visitedNodes.insert(nodeAfter);

  auto frontierList = graph.right.equal_range(nodeAfter);
  for(auto frontierIt = frontierList.first; frontierIt != frontierList.second; ++frontierIt) {
    if(isOrdered(frontierIt->second, nodeBefore, visitedNodes)) return true;
  }
  return false;
}

namespace xreate {
namespace cfa {
  static string dictNodes[] =  {"scope", "empty", "and", "or"};

  std::ostream&
  operator << (std::ostream& output, const TemporalNode& node) {
    string op = dictNodes[node.mark];
    output << op << "(" << node.scope << ")";

    return output;
  }
}}