/*
 * Scyther : An automatic verifier for security protocols.
 * Copyright (C) 2007-2013 Cas Cremers
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/**
 * Handle bindings for Arache engine.
 */

#include "list.h"
#include "role.h"
#include "label.h"
#include "system.h"
#include "binding.h"
#include "warshall.h"
#include "debug.h"
#include "term.h"
#include "termmap.h"
#include "arachne.h"
#include "switches.h"
#include "depend.h"
#include "error.h"
#include "mymalloc.h"

static System sys;		//!< local storage of system pointer

extern Protocol INTRUDER;	//!< The intruder protocol
extern Role I_M;		//!< special role; precedes all other events always

/*
 *
 * Assist stuff
 *
 */

//! Create mem for binding
Binding
binding_create (Term term, int run_to, int ev_to)
{
  Binding b;

  b = malloc (sizeof (struct binding));
  b->done = false;
  b->blocked = false;
  b->run_from = -1;
  b->ev_from = -1;
  b->run_to = run_to;
  b->ev_to = ev_to;
  b->term = term;
  b->level = 0;
  return b;
}

//! Remove mem for binding
void
binding_destroy (Binding b)
{
  if (b->done)
    {
      goal_unbind (b);
    }
  free (b);
}

/*
 *
 * Main
 *
 */

//! Init module
void
bindingInit (const System mysys)
{
  sys = mysys;
  sys->bindings = NULL;

  dependInit (sys);
}

//! Close up
void
bindingDone ()
{
  int delete (Binding b)
  {
    binding_destroy (b);
    return true;
  }
  list_iterate (sys->bindings, delete);
  list_destroy (sys->bindings);

  dependDone (sys);
}


/**
 *
 * Externally available functions
 *
 */


//! Print a binding (given a binding list pointer)
int
binding_print (const Binding b)
{
  if (b->done)
    eprintf ("Binding (%i,%i) --( ", b->run_from, b->ev_from);
  else
    eprintf ("Unbound --( ");
  termPrint (b->term);
  eprintf (" )->> (%i,%i)", b->run_to, b->ev_to);
  if (b->blocked)
    eprintf ("[blocked]");
  return true;
}

//! Bind a goal (true if success, false if it must be pruned)
int
goal_bind (const Binding b, const int run, const int ev)
{
  if (b->blocked)
    {
      error ("Trying to bind a blocked goal.");
    }
  if (!b->done)
    {
#ifdef DEBUG
      if (run >= sys->maxruns || sys->runs[run].step <= ev)
	{
	  globalError++;
	  eprintf ("For term: ");
	  termPrint (b->term);
	  eprintf (" needed for r%ii%i.\n", b->run_to, b->ev_to);
	  eprintf ("Current limits: %i runs, %i events for this run.\n",
		   sys->maxruns, sys->runs[run].step);
	  globalError--;
	  error
	    ("trying to bind to something not yet in the semistate: r%ii%i.",
	     run, ev);
	}
#endif
      b->run_from = run;
      b->ev_from = ev;
      if (dependPushEvent (run, ev, b->run_to, b->ev_to))
	{
	  b->done = true;
	  if (switches.output == PROOF)
	    {
	      indentPrint ();
	      binding_print (b);
	      eprintf ("\n");
	    }
	  return true;
	}
    }
  else
    {
      globalError++;
      binding_print (b);
      error ("Trying to bind a bound goal again.");
    }
  return false;
}

//! Unbind a goal
void
goal_unbind (const Binding b)
{
  if (b->done)
    {
      dependPopEvent ();
      b->done = false;
    }
  else
    {
      error ("Trying to unbind an unbound goal again.");
    }
}

//! Check if term,run,ev already occurs in binding
int
is_new_binding (const Term term, const int run, const int ev)
{
  List bl = sys->bindings;

  while (bl != NULL)
    {
      Binding b;

      b = (Binding) bl->data;
      if (isTermEqual (b->term, term))
	{
	  if (run == b->run_to && ev == b->ev_to)
	    {
	      return false;
	    }
	}
      bl = bl->next;
    }
  return true;
}

//! Add a goal
/**
 * The int parameter 'level' is just to store additional info. Here, it stores priorities for a goal.
 * Higher level goals will be selected first. Typically, a normal goal is level 0, a key is 1.
 *
 * Returns the number of added goals (sometimes unfolding tuples)
 */
int
goal_add (Term term, const int run, const int ev, const int level)
{
  term = deVar (term);
#ifdef DEBUG
  if (term == NULL)
    {
      globalError++;
      roledefPrint (eventRoledef (sys, run, ev));
      eprintf ("\n");
      globalError--;
      error ("Trying to add an emtpy goal term to r%ii%i, with level %i.",
	     run, ev, level);
    }
  if (run >= sys->maxruns)
    error ("Trying to add a goal for a run that does not exist.");
  if (ev >= sys->runs[run].step)
    error
      ("Trying to add a goal for an event that is not in the semistate yet.");
#endif
  if (switches.intruder && realTermTuple (term))
    {
      // Only split if there is an intruder
      return goal_add (TermOp1 (term), run, ev, level) +
	goal_add (TermOp2 (term), run, ev, level);
    }
  else
    {
      // Determine whether we already had it
      if (is_new_binding (term, run, ev))
	{
	  // Add a new binding
	  Binding b;
	  b = binding_create (term, run, ev);
	  b->level = level;
	  sys->bindings = list_insert (sys->bindings, b);
#ifdef DEBUG
	  if (DEBUGL (3))
	    {
	      eprintf ("Adding new binding for ");
	      termPrint (term);
	      eprintf (" to run %i, ev %i.\n", run, ev);
	    }
#endif
	  return 1;
	}
    }
  return 0;
}

//! Remove a goal
void
goal_remove_last (int n)
{
  while (n > 0)
    {
      if (sys->bindings != NULL)
	{
	  Binding b;

	  b = (Binding) sys->bindings->data;
	  binding_destroy (b);
	  sys->bindings = list_delete (sys->bindings);
	  n--;
	}
      else
	{
	  error
	    ("goal_remove_last error: trying to remove %i too many bindings.",
	     n);
	}
    }
}

//! Determine whether some label set is ordered w.r.t. send/recv order.
/**
 * Assumes all these labels exist in the system, within length etc, and that the run mappings are valid.
 */
int
labels_ordered (Termmap runs, Termlist labels)
{
  while (labels != NULL)
    {
      // Given this label, and the mapping of runs, we want to know if the order is okay. Thus, we need to know sendrole and recvrole
      Labelinfo linfo;
      int send_run, send_ev, recv_run, recv_ev;

      int get_index (const int run)
      {
	Roledef rd;
	int i;

	i = 0;
	rd = sys->runs[run].start;
	while (rd != NULL && !isTermEqual (rd->label, labels->term))
	  {
	    rd = rd->next;
	    i++;
	  }
#ifdef DEBUG
	if (rd == NULL)
	  error
	    ("Could not locate send or recv for label, after niagree holds, to test for order.");
#endif
	return i;
      }

      linfo = label_find (sys->labellist, labels->term);
      if (!linfo->ignore)
	{
	  send_run = termmapGet (runs, linfo->sendrole);
	  recv_run = termmapGet (runs, linfo->recvrole);
	  send_ev = get_index (send_run);
	  recv_ev = get_index (recv_run);
	  if (!isDependEvent (send_run, send_ev, recv_run, recv_ev))
	    {
	      // Not ordered; false
	      return false;
	    }

	}
      // Proceed
      labels = labels->next;
    }
  return true;
}

//! Check whether the binding denotes a sensible thing such that we can use run_from and ev_from
int
valid_binding (Binding b)
{
  if (b->done && (!b->blocked))
    return true;
  else
    return false;
}

//! Iterate over all bindings
/**
 * Iterator should return true to proceed
 */
int
iterate_bindings (int (*func) (Binding b))
{
  List bl;

  for (bl = sys->bindings; bl != NULL; bl = bl->next)
    {
      Binding b;

      b = (Binding) bl->data;
      if (!func (b))
	{
	  return false;
	}
    }
  return true;
}

//! Determine whether two bindings have the same source and destination
int
same_binding (const Binding b1, const Binding b2)
{
  if (b1 == b2)
    {
      return true;
    }
  if ((b1 != NULL) && (b2 != NULL))
    {
      if ((b1->done) && (b2->done))
	{
	  if ((b1->run_to == b2->run_to) && (b1->ev_to == b2->ev_to))
	    {
	      if ((b1->run_from == b2->run_from)
		  && (b1->ev_from == b2->ev_from))
		{
		  return true;
		}
	    }
	}
    }
  return false;
}

//! Iterate over preceding bindings (this does not include stuff bound to the same destination)
/**
 * Iterator should return true to proceed
 */
int
iterate_preceding_bindings (const int run, const int ev,
			    int (*func) (Binding b))
{
  List bl;

  for (bl = sys->bindings; bl != NULL; bl = bl->next)
    {
      Binding b;

      b = (Binding) bl->data;
      if (isDependEvent (b->run_to, b->ev_to, run, ev))
	{
	  if (!func (b))
	    {
	      return false;
	    }
	}
    }
  return true;
}


//! Check for unique origination
/*
 * Contrary to a previous version, we simply check for unique origination.
 * This immediately takes care of any 'occurs before' things. Complexity is N
 * log N.
 *
 * Each term should originate only at one point (thus in one binding)
 *
 *@returns True, if it's okay. If false, it needs to be pruned.
 */
int
unique_origination ()
{
  List bl;

  bl = sys->bindings;

  while (bl != NULL)
    {
      Binding b;

      b = (Binding) bl->data;
      // Check for a valid binding; it has to be 'done' and sensibly bound (not as in tuple expanded stuff)
      if (valid_binding (b))
	{
	  Termlist terms;

	  terms = tuple_to_termlist (b->term);
	  if (terms != NULL)
	    {
	      /* Apparently this is a good term.
	       * Now we check whether it occurs in any previous bindings as well.
	       */

	      List bl2;

	      bl2 = sys->bindings;
	      while (bl2 != bl)
		{
		  Binding b2;

		  b2 = (Binding) bl2->data;
		  if (valid_binding (b2))
		    {
		      Termlist terms2, sharedterms;

		      if (switches.intruder)
			{
			  // For intruder we work with sets here
			  terms2 = tuple_to_termlist (b2->term);
			}
		      else
			{
			  // For regular agents we use terms
			  terms2 = termlistAdd (NULL, b2->term);
			}
		      sharedterms = termlistConjunct (terms, terms2);

		      // Compare terms
		      if (sharedterms != NULL)
			{
			  // Apparently, this binding shares a term.
			  // Equal terms should originate at the same point
			  if (b->run_from != b2->run_from ||
			      b->ev_from != b2->ev_from)
			    {
			      // Not equal: thus no unique origination.

			      // Cleanup of garbage
			      termlistDelete (terms2);
			      termlistDelete (sharedterms);
			      termlistDelete (terms);

			      return false;
			    }
			}
		      termlistDelete (terms2);
		      termlistDelete (sharedterms);
		    }
		  bl2 = bl2->next;
		}
	    }
	  termlistDelete (terms);
	}
      bl = bl->next;
    }
  return true;
}


//! Check for first-origination points
/**
 *@returns True, if it's okay. If false, it needs to be pruned.
 */
int
first_origination ()
{
  List bl;

  // For all goals
  bl = sys->bindings;
  while (bl != NULL)
    {
      Binding b;

      b = (Binding) bl->data;
      // Check for a valid binding; it has to be 'done' and sensibly bound (not as in tuple expanded stuff)
      if (valid_binding (b))
	{
	  int run;

	  // Find all preceding events
	  for (run = 0; run < sys->maxruns; run++)
	    {
	      int ev;
	      Roledef rd;

	      rd = sys->runs[run].start;

	      //!@todo hardcoded reference to step, should be length
	      for (ev = 0; ev < sys->runs[run].step; ev++)
		{
		  if (rd->type == SEND || rd->type == RECV)
		    {
		      if (isDependEvent (run, ev, b->run_from, b->ev_from))
			{
			  // this node is *before* the from node

			  int occursthere;

			  if (switches.intruder)
			    {
			      // intruder: interm bindings should cater for the first occurrence
			      occursthere = termInTerm (rd->message, b->term);
			    }
			  else
			    {
			      // no intruder, then simple test
			      occursthere =
				isTermEqual (rd->message, b->term);
			    }
			  if (occursthere)
			    {
			      // This term already occurs in a previous node!
#ifdef DEBUG
			      if (DEBUGL (4))
				{
				  // Report this
				  indentPrint ();
				  eprintf ("Binding for ");
				  termPrint (b->term);
				  eprintf
				    (" at r%i i%i is not redundant because it occurred before at r%i i%i in ",
				     b->run_from, b->ev_from, run, ev);
				  termPrint (rd->message);
				  eprintf ("\n");
				}
#endif
			      return false;
			    }
			}
		      else
			{
			  // If this event is not before the target, then the
			  // next in the run certainly is not either (because
			  // that would imply that this one is before it)
			  // Thus, we effectively exit the loop.
			  break;
			}
		    }
		}
	      rd = rd->next;
	    }
	}
      bl = bl->next;
    }
  return true;
}

//! Determine if pattern is redundant or not
/**
 * Intuition says this can be done a lot more efficient. Luckily this is the prototype.
 *
 *@returns True, if it's okay. If false, it needs to be pruned.
 */
int
non_redundant ()
{
  return (unique_origination () && first_origination ());
}

//! Count the number of bindings that are done.
int
countBindingsDone ()
{
  int count;
  List bl;


  count = 0;
  for (bl = sys->bindings; bl != NULL; bl = bl->next)
    {
      Binding b;

      b = (Binding) bl->data;
      if ((!b->blocked) && b->done)
	{
	  count++;
	}
    }
  return count;
}
