/*
     This file is part of GNUnet
     Copyright (C) 2012 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */
/**
 * @file src/regex/regex_test_graph.c
 * @brief functions for creating .dot graphs from regexes
 * @author Maximilian Szengel
 */
#include "platform.h"
#include "regex_internal_lib.h"
#include "regex_test_lib.h"
#include "regex_internal.h"

/**
 * Context for graph creation. Passed as the cls to
 * REGEX_TEST_automaton_save_graph_step.
 */
struct REGEX_TEST_Graph_Context
{
  /**
   * File pointer to the dot file used for output.
   */
  FILE *filep;

  /**
   * Verbose flag, if it's set to GNUNET_YES additional info will be printed in
   * the graph.
   */
  int verbose;

  /**
   * Coloring flag, if set to GNUNET_YES SCCs will be colored.
   */
  int coloring;
};


/**
 * Recursive function doing DFS with 'v' as a start, detecting all SCCs inside
 * the subgraph reachable from 'v'. Used with scc_tarjan function to detect all
 * SCCs inside an automaton.
 *
 * @param scc_counter counter for numbering the sccs
 * @param v start vertex
 * @param index current index
 * @param stack stack for saving all SCCs
 * @param stack_size current size of the stack
 */
static void
scc_tarjan_strongconnect (unsigned int *scc_counter,
                          struct REGEX_INTERNAL_State *v, unsigned int *index,
                          struct REGEX_INTERNAL_State **stack,
                          unsigned int *stack_size)
{
  struct REGEX_INTERNAL_State *w;
  struct REGEX_INTERNAL_Transition *t;

  v->index = *index;
  v->lowlink = *index;
  (*index)++;
  stack[(*stack_size)++] = v;
  v->contained = 1;

  for (t = v->transitions_head; NULL != t; t = t->next)
  {
    w = t->to_state;

    if (NULL == w)
      continue;

    if (w->index < 0)
    {
      scc_tarjan_strongconnect (scc_counter, w, index, stack, stack_size);
      v->lowlink = (v->lowlink > w->lowlink) ? w->lowlink : v->lowlink;
    }
    else if (1 == w->contained)
      v->lowlink = (v->lowlink > w->index) ? w->index : v->lowlink;
  }

  if (v->lowlink == v->index)
  {
    (*scc_counter)++;
    do
    {
      w = stack[--(*stack_size)];
      w->contained = 0;
      w->scc_id = *scc_counter;
    }
    while (w != v);
  }
}


/**
 * Detect all SCCs (Strongly Connected Components) inside the given automaton.
 * SCCs will be marked using the scc_id on each state.
 *
 * @param a the automaton for which SCCs should be computed and assigned.
 */
static void
scc_tarjan (struct REGEX_INTERNAL_Automaton *a)
{
  unsigned int index;
  unsigned int scc_counter;
  struct REGEX_INTERNAL_State *v;
  struct REGEX_INTERNAL_State *stack[a->state_count];
  unsigned int stack_size;

  for (v = a->states_head; NULL != v; v = v->next)
  {
    v->contained = 0;
    v->index = -1;
    v->lowlink = -1;
  }

  stack_size = 0;
  index = 0;
  scc_counter = 0;

  for (v = a->states_head; NULL != v; v = v->next)
  {
    if (v->index < 0)
      scc_tarjan_strongconnect (&scc_counter, v, &index, stack, &stack_size);
  }
}


/**
 * Save a state to an open file pointer. cls is expected to be a file pointer to
 * an open file. Used only in conjunction with
 * REGEX_TEST_automaton_save_graph.
 *
 * @param cls file pointer.
 * @param count current count of the state, not used.
 * @param s state.
 */
void
REGEX_TEST_automaton_save_graph_step (void *cls, unsigned int count,
                                      struct REGEX_INTERNAL_State *s)
{
  struct REGEX_TEST_Graph_Context *ctx = cls;
  struct REGEX_INTERNAL_Transition *ctran;
  char *s_acc = NULL;
  char *s_tran = NULL;
  char *name;
  char *to_name;

  if (GNUNET_YES == ctx->verbose)
    GNUNET_asprintf (&name, "%i (%s) (%s) (%s)", s->dfs_id, s->name, s->proof,
                     GNUNET_h2s (&s->hash));
  else
    GNUNET_asprintf (&name, "%i", s->dfs_id);

  if (s->accepting)
  {
    if (GNUNET_YES == ctx->coloring)
    {
      GNUNET_asprintf (&s_acc,
                       "\"%s\" [shape=doublecircle, color=\"0.%i 0.8 0.95\"];\n",
                       name, s->scc_id * s->scc_id);
    }
    else
    {
      GNUNET_asprintf (&s_acc, "\"%s\" [shape=doublecircle];\n", name,
                       s->scc_id);
    }
  }
  else if (GNUNET_YES == ctx->coloring)
  {
    GNUNET_asprintf (&s_acc,
                     "\"%s\" [shape=circle, color=\"0.%i 0.8 0.95\"];\n", name,
                     s->scc_id * s->scc_id);
  }
  else
  {
    GNUNET_asprintf (&s_acc, "\"%s\" [shape=circle];\n", name, s->scc_id);
  }

  GNUNET_assert (NULL != s_acc);

  fwrite (s_acc, strlen (s_acc), 1, ctx->filep);
  GNUNET_free (s_acc);
  s_acc = NULL;

  for (ctran = s->transitions_head; NULL != ctran; ctran = ctran->next)
  {
    if (NULL == ctran->to_state)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Transition from State %i has no state for transitioning\n",
                  s->id);
      continue;
    }

    if (GNUNET_YES == ctx->verbose)
    {
      GNUNET_asprintf (&to_name, "%i (%s) (%s) (%s)", ctran->to_state->dfs_id,
                       ctran->to_state->name, ctran->to_state->proof,
                       GNUNET_h2s (&ctran->to_state->hash));
    }
    else
      GNUNET_asprintf (&to_name, "%i", ctran->to_state->dfs_id);

    if (NULL == ctran->label)
    {
      if (GNUNET_YES == ctx->coloring)
      {
        GNUNET_asprintf (&s_tran,
                         "\"%s\" -> \"%s\" [label = \"ε\", color=\"0.%i 0.8 0.95\"];\n",
                         name, to_name, s->scc_id * s->scc_id);
      }
      else
      {
        GNUNET_asprintf (&s_tran, "\"%s\" -> \"%s\" [label = \"ε\"];\n", name,
                         to_name, s->scc_id);
      }
    }
    else
    {
      if (GNUNET_YES == ctx->coloring)
      {
        GNUNET_asprintf (&s_tran,
                         "\"%s\" -> \"%s\" [label = \"%s\", color=\"0.%i 0.8 0.95\"];\n",
                         name, to_name, ctran->label, s->scc_id * s->scc_id);
      }
      else
      {
        GNUNET_asprintf (&s_tran, "\"%s\" -> \"%s\" [label = \"%s\"];\n", name,
                         to_name, ctran->label, s->scc_id);
      }
    }

    GNUNET_free (to_name);

    GNUNET_assert (NULL != s_tran);

    fwrite (s_tran, strlen (s_tran), 1, ctx->filep);
    GNUNET_free (s_tran);
    s_tran = NULL;
  }

  GNUNET_free (name);
}


/**
 * Save the given automaton as a GraphViz dot file.
 *
 * @param a the automaton to be saved.
 * @param filename where to save the file.
 * @param options options for graph generation that include coloring or verbose
 *                mode
 */
void
REGEX_TEST_automaton_save_graph (struct REGEX_INTERNAL_Automaton *a,
                                 const char *filename,
                                 enum REGEX_TEST_GraphSavingOptions options)
{
  char *start;
  char *end;
  struct REGEX_TEST_Graph_Context ctx;

  if (NULL == a)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not print NFA, was NULL!");
    return;
  }

  if ((NULL == filename) || (strlen (filename) < 1))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No Filename given!");
    return;
  }

  ctx.filep = fopen (filename, "w");
  ctx.verbose =
    (0 == (options & REGEX_TEST_GRAPH_VERBOSE)) ? GNUNET_NO : GNUNET_YES;
  ctx.coloring =
    (0 == (options & REGEX_TEST_GRAPH_COLORING)) ? GNUNET_NO : GNUNET_YES;

  if (NULL == ctx.filep)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not open file for writing: %s",
                filename);
    return;
  }

  /* First add the SCCs to the automaton, so we can color them nicely */
  if (GNUNET_YES == ctx.coloring)
    scc_tarjan (a);

  start = "digraph G {\nrankdir=LR\n";
  fwrite (start, strlen (start), 1, ctx.filep);

  REGEX_INTERNAL_automaton_traverse (a, a->start, NULL, NULL,
                                     &REGEX_TEST_automaton_save_graph_step,
                                     &ctx);

  end = "\n}\n";
  fwrite (end, strlen (end), 1, ctx.filep);
  fclose (ctx.filep);
}
