/* delegate.c - handling the delegations
   Copyright 1988-2017 Free Software Foundation, Inc.

   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 3, 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, see
   <https://www.gnu.org/licenses/gpl-3.0.html>.  */

#include <config.h>

#include "a2ps.h"
#include "jobs.h"
#include "fjobs.h"
#include "output.h"
#include "delegate.h"
#include "message.h"
#include "routines.h"
#include "metaseq.h"
#include "dsc.h"
#include "buffer.h"
#include "lister.h"
#include "quotearg.h"

/* Priviledge access to job and delegations */
extern struct a2ps_job *job;
extern struct hash_table_s *delegation_table;

/************************************************************************/
/*      Handling of the hash table of the delegations                   */
/************************************************************************/
/*
 * The hashing is done on the contract, not the name (who
 * cares the name of the contract!)
 */
static unsigned long
delegate_hash_1 (void const *key)
{
  return_STRING_HASH_1 (((const struct delegation *) key)->contract);
}

static unsigned long
delegate_hash_2 (void const *key)
{
  return_STRING_HASH_2 (((const struct delegation *) key)->contract);
}

static int
delegate_hash_cmp (void const *x, void const *y)
{
  return_STRING_COMPARE (((const struct delegation *) x)->contract,
			 ((const struct delegation *) y)->contract);
}

static int
delegate_hash_qcmp (const struct delegation **x,
		    const struct delegation **y)
{
  return_STRING_COMPARE ((*x)->name, (*y)->name);
}

static size_t
delegate_name_len (struct delegation *delegation)
{
  return strlen (delegation->name);
}

static int
delegate_name_fputs (struct delegation *delegation, FILE * stream)
{
  return fputs (delegation->name, stream);
}

/*
 * Create the table handling the subcontracts
 */
struct hash_table_s *
delegation_table_new (void)
{
  struct hash_table_s * res = XMALLOC (struct hash_table_s);
  hash_init (res, 8,
	     delegate_hash_1, delegate_hash_2, delegate_hash_cmp);
  return res;
}

/************************************************************************/
/*              Use of the subcontracts                                 */
/************************************************************************/
/*
 * Read a (PS) file, and extract the NeededResources etc.
 * To include in the whole file Prolog.
 * Get the number of pages too.
 */

/*
 * Read a line "Delegation: <CONTRACT_LINE>" in a config file
 */
#define error_if_null(_str_)						\
  if (_str_ == NULL) 							\
    error_at_line (1, 0, filename, line,				\
		   _("missing argument for `%s'"), quotearg (contract_line));

void
add_delegation (const char *filename, unsigned line,
		char *contract_line)
{
  char *cp, *cp2;
  struct delegation *contract;

  contract = XMALLOC (struct delegation);

  /* Structure of the line:
     <name of contract> <source type>:<destination type> <command> */
  cp = strtok (contract_line, " \t\n");
  error_if_null (cp);
  contract->name = xstrdup (cp);

  cp = strtok (NULL, " \t\n:");
  error_if_null (cp);
  cp2 = strtok (NULL, " \t\n");
  error_if_null (cp2);
  contract->contract = XNMALLOC (strlen (cp) + strlen (cp2) + 2, char);
  sprintf (contract->contract, "%s:%s", cp, cp2);

  cp = strtok (NULL, "\n");
  error_if_null (cp);
  contract->command = xstrdup (cp + strspn (cp, "\t "));

  /* Put it in the table */
  hash_insert (delegation_table, contract);
}


/*
 * Return the subcontract if there is, otherwise NULL
 */
struct delegation *
get_subcontract (const char *src_type, const char *dest_type)
{
  struct delegation token;

  token.contract = ALLOCA (char, strlen (src_type) + strlen (dest_type) + 2);
  sprintf (token.contract, "%s:%s", src_type, dest_type);

  return (struct delegation *) hash_find_item (delegation_table, &token);
}

/*
 * Return the command associated a sub contract.
 * if EVALUATE, return the evaluated command.
 * The result is not malloc'ed, and must be used before
 * any other call to expand_user_string.
 */
char *
get_delegate_command (struct delegation *contract,
		      struct file_job *file,
		      int evaluate)
{
  if (evaluate)
    return (char *) expand_user_string (job, file,
					"delegating command",
					contract->command);
  else
    return contract->command;
}

/*
 * Subcontract FILE to CONTRACTOR
 * This should produce a tmp file, then insert in the OUTPUT struture
 * the routine to dump and remove it.
 * Return true open success, false otherwise
 */
enum continuation_e
{
  no_continuation, needed_resource
};

int
subcontract (struct file_job *fjob, buffer_t * buffer,
	     struct delegation *contractor)
{
  char *command, *stdin_content_filename = NULL;
  FILE *in_stream, *out_stream;
  int lines_read = 0;
  char buf[512];

  /* Here we store the type of the last %%??Resource: tag we saw,
   * to be ready to handle continuation (%%+ ) */
  enum continuation_e continuation = no_continuation;

  /* This is an awful kludge.  I dunno how to do it nicely...  The
     problem is that a2ps can be fed by stdin, but delegations cannot.
     So we first dump stdin into a temporary file.  */
  if (fjob->name == job->stdin_filename)	/* not strcmp */
    {
      /* Dump the content of the buffer */
      tempname_ensure (fjob->stdin_tmpname);
      stdin_content_filename = fjob->stdin_tmpname;
      buffer_save (buffer, stdin_content_filename);

      /* We change the name of the file so that the correct file name
	 is used in the command (that of the temporary file containing
	 stdin). */
      fjob->name = stdin_content_filename;
      command = get_delegate_command (contractor, fjob, 1);
      fjob->name = job->stdin_filename;
    }
  else
    {
      command = get_delegate_command (contractor, fjob, 1);
    }

  /* First, before it break :), say what you do */
  message (msg_file,
	   (stderr, "Delegating `%s' to `%s' (%s)\n",
	    fjob->name, contractor->name, command));

  /* Open a pipe from the delegation, and the temp file in which
   * the result is stored */
  tempname_ensure (fjob->delegation_tmpname);
  out_stream = fopen (fjob->delegation_tmpname, "w");
  if (!out_stream)
    {
      error (0, errno, _("cannot create file `%s'"),
	     quotearg (fjob->delegation_tmpname));
      return false;
    }

  in_stream = popen (command, "r");
  if (!in_stream)
    {
      fclose (out_stream);
      error (0, errno, _("cannot open a pipe on `%s'"),
	     quotearg (command));
      return false;
    }

  /* Make the file know its first page/sheet */
  file_job_synchronize_sheets (job);
  file_job_synchronize_pages (job);

  /* Now, read the file, update the PS info, and store the result in
   * out_stream */
  while (fgets (buf, sizeof (buf), in_stream))
    {
      /* This is not exactely the number of lines,
       * but anyway it is only used to be sure something was read,
       * to track the failure of a delegation */
      lines_read++;
#define PAGE_TAG	"%%Page: "
      if (strprefix (PAGE_TAG, buf))
	{
	  /* We suppose that it has respected the number of virtual
	   * pages per sheet */
	  job->pages += job->rows * job->columns;
	  job->sheets++;
	  file_job_synchronize_sheets (job);
	  file_job_synchronize_pages (job);
	}
#define NEEDED_RES_TAG	"%%DocumentNeededResources: "
      else if (strprefix (NEEDED_RES_TAG, buf))
	{
	  /* The needed resources must be included too.
	   * Take care of the %%+ continuation tag */
	  char *value, *res, *buf_copy;

	  continuation = needed_resource;
	  astrcpy (buf_copy, buf + strlen (NEEDED_RES_TAG));
	  res = strtok (buf_copy, " \n\t");

	  /* This while saves us from a special case of
	   * %%DocumentNeededResources: (atend)
	   */
	  while ((value = strtok (NULL, " \n\t")))
	    add_needed_resource (job, res, value);
	}
#define CONTINUATION_TAG	"%%+ "
      else if (strprefix (CONTINUATION_TAG, buf))
	{
	  char *value, *res, *buf_copy;

	  astrcpy (buf_copy, buf + strlen (CONTINUATION_TAG));
	  res = strtok (buf_copy, " \n\t");
	  while ((value = strtok (NULL, " \n\t")))
	    switch (continuation)
	      {
	      case needed_resource:
		add_needed_resource (job, res, value);
		break;

	      default:
		break;
	      }
	}

      /* The content should be left untouched */
      fputs (buf, out_stream);
    }

  pclose (in_stream);
  fclose (out_stream);

  /* If a temporary file was created to deal with stdin, unlink it. */
  if (stdin_content_filename)
    unlink (stdin_content_filename);

  /* FIXME: This is a trial to see when there is an error */
  if (lines_read == 0)
    return false;

  /* This one must not be cut by the page selection */
  {
    int saved_redirection_of_output;
    saved_redirection_of_output = output_is_to_void (job->divertion);
    output_to_void (job->divertion, false);

    /* Protect the rest of the file and give sane environment */
    output (job->divertion, "BeginInclude\n");
    output (job->divertion, "%%%%BeginDocument: %s\n", fjob->name);
    output_delayed_routine (job->divertion,
                            (delayed_routine_t) stream_dump,
                            (void *) fjob->delegation_tmpname);
    /* remove the file after its use */
    output_delayed_routine (job->divertion,
                            (delayed_routine_t) unlink2,
                            (void *) fjob->delegation_tmpname);
    output (job->divertion, "%%%%EndDocument\n");
    output (job->divertion, "EndInclude\n");

    output_to_void (job->divertion, saved_redirection_of_output);
  }

  /* The pages are no longer ordered.  Respect DSC */
  job->status->page_are_ordered = false;
  return true;
}

/************************************************************************/
/*              "Visible" interface of the subcontracts                 */
/************************************************************************/
/*
 * Print a single contract on STREAM
 */
static void
dump_contract (FILE * stream, struct delegation *contract)
{
  char *cp, *cp2;
  cp = xstrdup (contract->contract);
  cp = strtok (cp, ":");
  cp2 = strtok (NULL, ":");

  /* E.g.: Delegation `PsNup', from ps to ps */
  fprintf (stream, _("Delegation `%s', from %s to %s\n"),
	   contract->name, cp, cp2);
  fprintf (stream, "\t%s\n", contract->command);
}

/*
 * List the subcontracts for --list-subcontracts
 */
void
delegations_list_long (struct hash_table_s *contracts,
		       FILE * stream)
{
  int i;
  struct delegation **ordered_contracts;
  ordered_contracts =
    ((struct delegation **)
     hash_dump (contracts, NULL, (qsort_cmp_t) delegate_hash_qcmp));

  fputs (_("Applications configured for delegation"), stream);
  putc ('\n', stream);

  for (i = 0; ordered_contracts[i]; i++)
    dump_contract (stream, ordered_contracts[i]);
  putc ('\n', stream);
}

/*
 * For --list-features
 */
void
delegations_list_short (struct hash_table_s *contracts,
			FILE * stream)
{
  struct delegation **ordered_contracts;
  ordered_contracts =
    ((struct delegation **)
     hash_dump (contracts, NULL, (qsort_cmp_t) delegate_hash_qcmp));

  fputs (_("Applications configured for delegation"), stream);
  putc ('\n', stream);

  lister_fprint_vertical (NULL, stream,
                          (void *) ordered_contracts, (size_t) -1,
                          (lister_width_t) delegate_name_len,
                          (lister_print_t) delegate_name_fputs);
}
