// graph.cc -- implementation of dependency graphs for lparse
// Copyright (C) 1999-2000 Tommi Syrjänen <Tommi.Syrjanen@hut.fi>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//  

#include "../config.h"
#include <string.h>

#ifndef PREDICATE_H
#include "predicate.h"
#endif
#ifndef GLOBAL_H
#include "global.h"
#endif
#ifndef GRAPH_H
#include "graph.h"
#endif
#ifndef DEBUG_H
#include "debug.h"
#endif
#ifndef ARRAY_H
#include "array.h"
#endif
#ifndef ERROR_H
#include "error.h"
#endif
static char *domain_string[] = { "DM_UNKNOWN", "DM_FALSE", "DM_TRUE",
				 "DM_DOMAIN" }; 

GraphNode::GraphNode()
  : children(NULL)
{
  size = 0;
  children = new ResizableArray();
  if (!children)
    error(SYS_ERR, "malloc error");
}

void GraphNode::AddChild(long child)
{
  (*(*children)[size++]) = child;
}


void GraphNode::ClearEdges()
{
  delete children;
  children = new ResizableArray(size ? size : 10);
  size = 0;
}

Graph::Graph()
{
  size = 0; visited = NULL; 
  domain = NULL;
  nodes = new GraphNodeArray();
  if (!nodes)
    error(SYS_ERR, "malloc error");
}

Graph::~Graph()
{
  delete nodes;
  if (domain) 
    delete [] domain; 
  if (visited)
    delete [] visited;
} 

void Graph::AddNode(long nd)
{
  (*nodes)[nd]->index = size++;
  
  /* delete old information */
  if (domain) {
    delete [] domain;
    domain = NULL;
  }
}

void Graph::AddEdge(long x, long y)
{
  /* sanity check */
  if ( (x > size) || (x < 0) || (y > size) || (y < 0))
    int_error("invalid edge in AddEdge", x);

  (*nodes)[x]->AddChild(y);
}

void Graph::CheckCycles()
{
  long i;
  debug(DBG_GRAPH, 2, "Finding cycles in graph");
  debug(DBG_GRAPH, 4, "Creating tables");

  visited = new long[size+1];

  if (!domain)
    domain = new DomainType[size+1];

  if (!domain || !visited)
    error(SYS_ERR, "malloc error");

  // initialize domain and visited information 
  for (i = 0; i <= size; i++) {
    domain[i] = DM_UNKNOWN;
    visited[i] = 0;
  }

  if (false_lit) {
    domain[false_lit->pred] = DM_FALSE;
  }

  // visit every node 
  for (i = 0; i < size; i++) 
    if (domain[i] == DM_UNKNOWN)
      Visit(i);
  return;
}


int Graph::Visit(long node)
{
  long i, val = 0, chld = 0;
  debug(DBG_GRAPH, 4, "Visiting node %d: %s", node, predicates[node]->Name());

  // mark this node as visited
  visited[node] = 1;
  
  // suppose by default that node is a domain predicate
  domain[node] = DM_TRUE;

  // now visit each children of the node
  for (i = 0; i < (*nodes)[node]->size; i++) {
    chld = *((*(*nodes)[node]->children)[i]); // ugly
    debug(DBG_GRAPH, 5, "\tChild %d", chld);
    // if we have visited the child previously a cycle is found 
    if ( visited[chld] == 1 )
      domain[node] = DM_FALSE;
    // if we haven't processed the child before process it, otherwise
    // use the calculated value.
    else if ( domain[chld] == DM_UNKNOWN)
      val = Visit(chld);
    else
      val = domain[chld];

    debug(DBG_GRAPH, 5, "\t\tDomain %d", domain[chld]);

    // predicates with special rules can't be domain predicates. 
    if (val == DM_FALSE || predicates[node]->HasSpecial())
      domain[node] = DM_FALSE;
  }

  // clear the visited mark of the node
  visited[node] = 0;

  // if the node is a domain predicate, calculate its domain.
  if (domain[node] == DM_TRUE) {
    if (predicates[node]->CheckRestrict(0) == RT_STRONG)
      predicates[node]->CalculateDomain();
  } else {
    predicates[node]->SetStatus(DM_FALSE);
  }
  // return the domain value of the node.
  return (int) domain[node];
}

// prints the domain values. For debugging only.
void Graph::PrintDomains()
{
  long i;
  if (!domain)
    return;

  for (i = 0; i < size; i++) {
    printf("Node %ld: %s\n", i, domain_string[domain[i]]);
  }

  return;
}

void Graph::ClearEdges()
{
  int i =0;

  for (i = 0; i < size; i++) {
    if (visited) 
      visited[i] = 0;
    if (domain)
      domain[i] = DM_UNKNOWN;
    if (toposort)
      toposort[i] = -1;
  }
  nodes->ClearEdges();
}

int Graph::TopologicalSort()
{
  int i = 0;

  if (!visited) {
    visited = new long[size];
    toposort = new long[size];
  }
  for (i = 0; i < size; i++) {
    visited[i] = 0;
    toposort[i] = -1;
  }

  counter = 0;
  for (i = 0; i < size; i++) {
    if (toposort[i] < 0) {
      if (!TopologicalVisit(i)) {
	return 0;
      }
    }
  }
  return 1;
}

int Graph::TopologicalVisit(long node)
{
  long i = 0, chld = 0;
  // mark this node as visited
  visited[node] = 1;
  
  // now visit each children of the node
  for (i = 0; i < (*nodes)[node]->size; i++) {
    chld = *((*(*nodes)[node]->children)[i]); // ugly
    if (visited[chld]) // a cycle
      return 0;

    TopologicalVisit(chld);
  }

  // mark the order of this node
  toposort[node] = counter++;
  visited[node] = 0;
  return 1;
}

long Graph::GetPosition(long x)
{
  return toposort[x];
}


void Graph::PropagateExternalDefinitions(long nd)
{
  long chld, i, j;
  int changed = 1;

  while (changed) {
    changed = 0;

    for (i = 0; i < size; i++) {
      for (j = 0; j < (*nodes)[i]->size; j++) {
	chld = *((*(*nodes)[i]->children)[j]); // ugly

	if ((chld == nd) || predicates[chld]->depends_on_external_atoms) {
	  if (!predicates[i]->depends_on_external_atoms)
	    changed = 1;
	  predicates[i]->depends_on_external_atoms = 1;
	}
      }
    }
  }
  
}
