/*
* ===========================================================================
*
*                            PUBLIC DOMAIN NOTICE
*            National Center for Biotechnology Information (NCBI)
*
*  This software/database is a "United States Government Work" under the
*  terms of the United States Copyright Act.  It was written as part of
*  the author's official duties as a United States Government employee and
*  thus cannot be copyrighted.  This software/database is freely available
*  to the public for use. The National Library of Medicine and the U.S.
*  Government do not place any restriction on its use or reproduction.
*  We would, however, appreciate having the NCBI and the author cited in
*  any work or product based on this material
*
*  Although all reasonable efforts have been taken to ensure the accuracy
*  and reliability of the software and data, the NLM and the U.S.
*  Government do not and cannot warrant the performance or results that
*  may be obtained by using this software or data. The NLM and the U.S.
*  Government disclaim all warranties, express or implied, including
*  warranties of performance, merchantability or fitness for any particular
*  purpose.
*
* ===========================================================================
*
* File Name:  testcgi.c
*
* Author:  Jonathan Kans
*
* Version Creation Date:   4/24/98
*
* $Revision: 6.37 $
*
* File Description: 
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
*
* ==========================================================================
*/

/*
*  To compile on Linux:
*
*   gcc -o testcgi.cgi testcgi.c -lm
*
*  To compile on Solaris:
*
*   cc -xildoff -o testcgi.cgi testcgi.c -lgen -lm
*
*  To compile on SGI:
*
*   cc -mips1 -o testcgi.cgi testcgi.c -lm -lPW -lsun
*
*/

#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* for development on Mac, need to define popen and pclose */

#if defined(__POWERPC__) || defined(powerc) || defined(__powerc) || defined(__POWERPC)
#define popen fopen
#define pclose fclose
#endif

/* convenient defines and typedefs from NCBI toolkit */

#ifndef NULL
#define NULL ((void *)0)
#endif

#ifndef Pointer
typedef void * Pointer;
#endif

#ifndef Char
typedef char Char, * CharPtr;
#endif

#ifndef Bool
typedef unsigned char Bool, * BoolPtr;
#endif

#ifndef TRUE
#define TRUE ((Bool)1)
#endif

#ifndef FALSE
#define FALSE ((Bool)0)
#endif

#ifndef Int2
typedef short Int2, * Int2Ptr;
#endif

#ifndef Int4
typedef long Int4, * Int4Ptr;
#endif

#ifndef MIN
#define MIN(a,b) ((a)>(b)?(b):(a))
#endif

/* useful portable character macros from NCBI toolkit (assumes ASCII) */

#define IS_DIGIT(c) ('0'<=(c) && (c)<='9')
#define IS_UPPER(c) ('A'<=(c) && (c)<='Z')
#define IS_LOWER(c) ('a'<=(c) && (c)<='z')
#define IS_ALPHA(c) (IS_UPPER(c) || IS_LOWER(c))
#define TO_LOWER(c) ((Char)(IS_UPPER(c) ? (c)+' ' : (c)))
#define TO_UPPER(c) ((Char)(IS_LOWER(c) ? (c)-' ' : (c)))
#define IS_WHITESP(c) (((c) == ' ') || ((c) == '\n') || ((c) == '\r') || ((c) == '\t'))
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_DIGIT(c))
#define IS_PRINT(c) (' '<=(c) && (c)<='~')

/* url decode/encode functions modified from NCBI toolkit */

/* Return integer (0..15) corresponding to the "ch" as a hex digit
 * Return -1 on error
 */
static int s_HexChar(char ch)
{
  if ('0' <= ch  &&  ch <= '9')
    return ch - '0';
  if ('a' <= ch  &&  ch <= 'f')
    return 10 + (ch - 'a');
  if ('A' <= ch  &&  ch <= 'F')
    return 10 + (ch - 'A');
  return -1;
}

/* The URL-encoding table
 */
static const char s_Encode[256][4] = {
  "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
  "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
  "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
  "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
  "+",   "!",   "%22", "%23", "$",   "%25", "%26", "'",
  "(",   ")",   "*",   "%2B", ",",   "-",   ".",   "%2F",
  "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
  "8",   "9",   "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
  "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",
  "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",
  "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",
  "X",   "Y",   "Z",   "%5B", "%5C", "%5D", "%5E", "_",
  "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",
  "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",
  "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",
  "x",   "y",   "z",   "%7B", "%7C", "%7D", "%7E", "%7F",
  "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
  "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
  "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
  "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
  "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
  "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
  "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
  "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
  "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
  "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
  "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
  "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
  "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
  "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
  "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
  "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
};
#define VALID_URL_SYMBOL(ch)  (s_Encode[(unsigned char)ch][0] != '%')

static Bool Url_Decode (
  const void* src_buf,
  size_t      src_size,
  size_t*     src_read,
  void*       dst_buf,
  size_t      dst_size,
  size_t*     dst_written
)

{
  unsigned char *src = (unsigned char*)src_buf;
  unsigned char *dst = (unsigned char*)dst_buf;

  *src_read    = 0;
  *dst_written = 0;
  if (!src_size  ||  !dst_size)
    return TRUE;

  for ( ;  *src_read != src_size  &&  *dst_written != dst_size;
        (*src_read)++, (*dst_written)++, src++, dst++) {
    switch ( *src ) {
    case '%': {
      int i1, i2;
      if (*src_read + 2 > src_size)
        return TRUE;
      if ((i1 = s_HexChar(*(++src))) == -1)
        return (Bool)(*dst_written ? TRUE : FALSE);
      if (*src_read + 3 > src_size)
        return TRUE;
      if ((i2 = s_HexChar(*(++src))) == -1)
        return (Bool)(*dst_written ? TRUE : FALSE);

      *dst = (unsigned char)((i1 << 4) + i2);
      *src_read += 2;
      break;
    }

    case '+': {
      *dst = ' ';
      break;
    }

    default:
      if ( VALID_URL_SYMBOL(*src) )
        *dst = *src;
      else
        return (Bool)(*dst_written ? TRUE : FALSE);
    }
  }

  /*
  ASSERT( src == (unsigned char*)src_buf + *src_read    );
  ASSERT( dst == (unsigned char*)dst_buf + *dst_written );
  */
  return TRUE;
}

static void Url_Encode (
  const void* src_buf,
  size_t      src_size,
  size_t*     src_read,
  void*       dst_buf,
  size_t      dst_size,
  size_t*     dst_written
)

{
  unsigned char *src = (unsigned char*)src_buf;
  unsigned char *dst = (unsigned char*)dst_buf;

  *src_read    = 0;
  *dst_written = 0;
  if (!src_size  ||  !dst_size)
    return;

  for ( ;  *src_read != src_size  &&  *dst_written != dst_size;
        (*src_read)++, (*dst_written)++, src++, dst++) {
    const char* subst = s_Encode[*src];
    if (*subst != '%') {
      *dst = *subst;
    } else if (*dst_written < dst_size - 2) {
      *dst = '%';
      *(++dst) = *(++subst);
      *(++dst) = *(++subst);
      *dst_written += 2;
    }
    else {
      return;
    }
  }
  /*
  ASSERT( src == (unsigned char*)src_buf + *src_read    );
  ASSERT( dst == (unsigned char*)dst_buf + *dst_written );
  */
}

/* combined read and decode, encode and write functions */

#define URLBUF 256

static size_t ReadAndDecode (CharPtr buf, size_t size, FILE *file)

{
  size_t  cnt;
  size_t  ct;
  size_t  len;
  size_t  more;
  size_t  srcrd;
  Char    tmp [URLBUF + 2];

  if (buf == NULL || size < 3 || file == NULL) return 0;
  len = MIN ((size_t) URLBUF, (size_t) (size - 2));
  cnt = fread (tmp, 1, len, file);
  if (cnt == 0) return 0;
  more = 0;
  if (tmp [cnt - 1] == '%') {
    more = 2;
  } else if (cnt > 1 && tmp [cnt - 2] == '%') {
    more = 1;
  }
  if (more > 0) {
    ct = fread (tmp + cnt, 1, more, file);
    if (ct == more) {
      cnt += more;
    }
  }
  if (Url_Decode (tmp, cnt, &srcrd, buf, size, &ct)) {
    return ct;
  }
  return 0;
}

static size_t EncodeAndWrite (CharPtr buf, size_t size, FILE *file)

{
  size_t  cnt;
  size_t  ct = 0;
  size_t  len;
  size_t  srcrd;
  Char    tmp [URLBUF * 3];

  if (buf == NULL || size < 1 || file == NULL) return 0;
  while (size > 0) {
    len = MIN ((size_t) URLBUF, (size_t) size);
    Url_Encode (buf, len, &srcrd, tmp, URLBUF, &cnt);
    buf += srcrd;
    size -= srcrd;
    if (cnt > 0) {
      ct += fwrite (tmp, 1, cnt, file);
    }
    if (srcrd == 0) return ct;
  }
  return ct;
}

/* copy of query string is parsed with strtok into tag and val paired arrays */

#define MAX_ENTRIES  32

static CharPtr  query = NULL;
static Int2     num_tags = 0;
static CharPtr  tag [MAX_ENTRIES];
static CharPtr  val [MAX_ENTRIES];


static Bool ParseQuery (CharPtr qstr, Bool queryRequired)

{
  size_t   len;
  CharPtr  ptr;

  /*
  *  given a query string enzyme=EcoRI&pattern=GAATTC
  */

  /* The >Message prefix causes Sequin to display the message to the user */

  if (qstr == NULL) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - Query not detected\n");
    fflush (stdout);
    return FALSE;
  }

  /* allocates a copy of query string that can be modified during parsing */

  len = strlen (qstr);
  query = malloc (len + 3);

  if (query == NULL) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - query allocation failed\n");
    fflush (stdout);
    return FALSE;
  }

  memset (query, 0, len + 2);
  strcpy (query, qstr);

  /* parse tag=value&tag=value query into arrays for easier interpretation */

  memset (tag, 0, sizeof (tag));
  memset (val, 0, sizeof (val));

  ptr = strtok (query, "=");
  for (num_tags = 0; num_tags < MAX_ENTRIES && ptr != NULL; num_tags++) {
    tag [num_tags] = ptr;
    ptr = strtok (NULL, "&");
    if (ptr != NULL) {
      val [num_tags] = ptr;
      ptr = strtok (NULL, "=");
    }
  }

  /*
  *  given the above query example, the tag and val arrays are now:
  *
  *   tag [0] = "enzyme"    val [0] = "EcoRI"
  *   tag [1] = "pattern"   val [1] = "GAATTC"
  *   tag [2] = NULL        val [2] = NULL
  *
  *  and num_tags is 2
  */

  /* verify that any required query string is present in the URL */

  if (queryRequired && num_tags == 0) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - Unable to parse tokens from query:\n'%s'\n", query);
    fflush (stdout);
    return FALSE;
  }

  return TRUE;
}


static CharPtr FindByName (CharPtr find)

{
  Int2  i;

  /* search the tag array for the desired name, returning the associated value */

  if (find == NULL) return NULL;
  for (i = 0; i < num_tags; i++) {
    if (tag [i] != NULL && strcmp (tag [i], find) == 0) {
      return val [i];
    }
  }
  return NULL;
}


static Int2 ListHasString (CharPtr list [], CharPtr str)

{
  Int2  i;

  /* search a null-terminated array of strings, returning an integer index */

  if (str == NULL || list == NULL) return -1;
  for (i = 0; list [i] != NULL; i++) {
    if (strcmp (str, list [i]) == 0) return i;
  }
  return -1;
}


#define SEND_FILENAME_ARGS_BEFORE  1
#define SEND_FILENAME_ARGS_AFTER   2
#define SEND_STDIN                 3

static void RunCustom (CharPtr tempfile)

{
  CharPtr   arguments;
  Char      buf [256];
  Char      cmmd [256];
  size_t    ct;
  FILE*     fp;
  Int2      meth = SEND_STDIN;
  CharPtr   method;
  CharPtr   port;
  CharPtr   program;
  long int  val;

  /* protect against custom requests on public server */

  port = getenv ("SERVER_PORT");
  if (port != NULL) {
    if (sscanf (port, "%ld", &val) == 1 && val == 80) {
      return;
    }
  }

  /* program argument would be actual path on cgi server machine */

  program = FindByName ("program");
  if (program == NULL) {
    printf (">Message\nFAILURE - no program path specified\n");
    fflush (stdout);
    return;
  }

  /* method defaults to sending data to program via stdin */

  method = FindByName ("method");
  if (method == NULL || strstr (method, "stdin") != NULL) {
    meth = SEND_STDIN;
  } else if (strstr (method, "filename") != NULL) {
    meth = SEND_FILENAME_ARGS_AFTER;
  } else if (strstr (method, "arguments") != NULL) {
    meth = SEND_FILENAME_ARGS_BEFORE;
  } else {
    meth = SEND_STDIN;
  }

  /* note that for arguments, %20 in a query string is converted to a space */

  arguments = FindByName ("arguments");
  if (arguments == NULL) {
    arguments = "";
  }

  /* launch program sending arguments and filename or data in appropriate order */

  switch (meth) {
    case SEND_FILENAME_ARGS_BEFORE :
      sprintf (cmmd, "%s %s %s", program, arguments, tempfile);
      break;
    case SEND_FILENAME_ARGS_AFTER :
      sprintf (cmmd, "%s %s %s", program, tempfile, arguments);
      break;
    case SEND_STDIN :
    default :
      sprintf (cmmd, "%s %s < %s", program, arguments, tempfile);
      break;
  }
  fp = popen (cmmd, "r");
  if (fp == NULL) return;

  /* assumes program sends output to stdout */

  while ((ct = fread (buf, 1, sizeof (buf), fp)) > 0) {
    EncodeAndWrite (buf, ct, stdout);
    fflush (stdout);
  }
  pclose (fp);
}


static void RunEcho (CharPtr tempfile)

{
  size_t  ct;
  Char    buf [256];
  FILE*   fp;
  Bool    headerSent = FALSE;

  /* reconstruct and print the query string */

  /*
  for (i = 0; i < num_tags; i++) {
    if (i > 0) {
      sprintf (buf, "%");
      EncodeAndWrite (buf, strlen (buf), stdout);
      fflush (stdout);
    }
    sprintf (buf, "%s=%s", tag [i], val [i]);
    EncodeAndWrite (buf, strlen (buf), stdout);
    fflush (stdout);
  }
  sprintf (buf, "\n");
  EncodeAndWrite (buf, strlen (buf), stdout);
  fflush (stdout);
  */

  /* now echo the data file */

  fp = fopen (tempfile, "r");
  if (fp == NULL) return;

  while ((ct = fread (buf, 1, sizeof (buf), fp)) > 0) {

    if (! headerSent) {

      /* send required first header information to stdout */

      printf ("Content-type: text/html\r\n\r\n");
      fflush (stdout);
      headerSent = TRUE;
    }

    EncodeAndWrite (buf, ct, stdout);
    fflush (stdout);
  }

  fclose (fp);

  if (! headerSent) {
    printf ("Content-type: text/html\r\n\r\n");
    fflush (stdout);
  }
}


static void RunSeg (CharPtr tempfile)

{
  Char     buf [256];
  Char     cmmd [256];
  size_t   ct;
  FILE*    fp;
  Bool     headerSent = FALSE;
  float    hi;
  CharPtr  hicut;
  CharPtr  lowcut;
  Char     tmp [16];
  CharPtr  window;

  /* launch seg with -x parameter, arguments, and name of data file */

  window = FindByName ("window");
  if (window == NULL) {
    window = "12";
  }
  lowcut = FindByName ("lowcut");
  if (lowcut == NULL) {
    lowcut = "2.2";
  }
  hicut = FindByName ("hicut");
  if (hicut == NULL || hicut [0] == '\0') {
    if (sscanf (lowcut, "%f", &hi) == 1) {
      hi += 0.3;
      sprintf (tmp, "%4.2f", hi);
      hicut = tmp;
    } else {
      hicut = "2.5";
    }
  }

  sprintf (cmmd, "./seg %s %s %s %s -x", tempfile, window, lowcut, hicut);
  fp = popen (cmmd, "r");
  if (fp == NULL) return;

  /* send processed FASTA data from seg directly to stdout and calling program */

  while ((ct = fread (buf, 1, sizeof (buf), fp)) > 0) {

    if (! headerSent) {

    /* send required first header information to stdout */

      printf ("Content-type: text/html\r\n\r\n");
      fflush (stdout);
      headerSent = TRUE;
    }

    EncodeAndWrite (buf, ct, stdout);
    fflush (stdout);
  }
  pclose (fp);

  if (! headerSent) {
    printf ("Content-type: text/html\r\n\r\n");
    fflush (stdout);
  }
}


#if 0
#define MAX_FIELDS  9

static void RunTrnaScan (CharPtr tempfile)

{
  CharPtr   aa;
  CharPtr   anticodonSeq;
  CharPtr   arg1 = "";
  CharPtr   arg2 = "";
  CharPtr   beg;
  Char      buf [256];
  Char      cmmd [256];
  CharPtr   domain;
  CharPtr   end;
  CharPtr   extras;
  CharPtr   field [MAX_FIELDS];
  FILE*     fp;
  Bool      headerSent = FALSE;
  CharPtr   id;
  Int2      idNotSent = TRUE;
  Int2      inBody = FALSE;
  CharPtr   intronBeg;
  CharPtr   intronEnd;
  long int  intronStart;
  long int  intronStop;
  Int2      numFields = 0;
  CharPtr   ptr;
  CharPtr   speed;
  long int  start;
  long int  stop;
  Char      str [80];

  /* launch tRNAscan-SE with -q parameter and name of data file */

  speed = FindByName ("speed");
  domain = FindByName ("domain");
  extras = FindByName ("extras");

  if (speed != NULL) {
    if (strcmp (speed, "slow") == 0) {
      arg1 = "-C ";
    }
  }

  if (domain != NULL) {
    if (strcmp (domain, "eukaryote") == 0) {
      arg2 = "";
    } else if (strcmp (domain, "prokaryote") == 0) {
      arg2 = "-P ";
    } else if (strcmp (domain, "archaea") == 0) {
      arg2 = "-A ";
    } else if (strcmp (domain, "organelle") == 0) {
      arg2 = "-O ";
    } else if (strcmp (domain, "general") == 0) {
      arg2 = "-G ";
    }
  }

  sprintf (cmmd, "./tRNAscan-SE -q %s%s%s", arg1, arg2, tempfile);

  fp = popen (cmmd, "r");
  if (fp == NULL) return;

  /* line by line processing of tRNAscan-SE output table */

  while (fgets (buf, sizeof (buf), fp) != NULL) {

    if (! headerSent) {

      /* send required first header information to stdout */

      printf ("Content-type: text/html\r\n\r\n");
      fflush (stdout);
      headerSent = TRUE;
    }

    if (inBody) {
      memset (field, 0, sizeof (field));

      /*
      *  parse tab-delimited output line into array of fields, avoiding use of
      *  strtok so that empty columns (adjacent tabs) are properly assigned to
      *  field array
      */

      ptr = buf;
      for (numFields = 0; numFields < MAX_FIELDS && ptr != NULL; numFields++) {
        field [numFields] = ptr;
        ptr = strchr (ptr, '\t');
        if (ptr != NULL) {
          *ptr = '\0';
          ptr++;
        }
      }

      /* interested in ID, start, stop, amino acid, and intron start and stop */

      id = field [0];
      beg = field [2];
      end = field [3];
      aa = field [4];
      anticodonSeq = field [5];
      intronBeg = field [6];
      intronEnd = field [7];

      if (numFields > 7 &&
          sscanf (beg, "%ld", &start) == 1 &&
          sscanf (end, "%ld", &stop) == 1 &&
          sscanf (intronBeg, "%ld", &intronStart) == 1 &&
          sscanf (intronEnd, "%ld", &intronStop) == 1) {

        /* first line of output gives SeqId from FASTA definition line */

        if (idNotSent) {
          sprintf (str, ">Features %s tRNAscan-SE\n", id);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
          idNotSent = FALSE;
        }

        /* first line of feature has start (tab) stop (tab) feature key */
        /* multiple intervals would have lines of start (tab) stop */

        if (intronStart == 0 && intronStop == 0) {
          sprintf (str, "%ld\t%ld\ttRNA\n", (long) start, (long) stop);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        } else {
          sprintf (str, "%ld\t%ld\ttRNA\n", (long) start, (long) (intronStart - 1));
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
          sprintf (str, "%ld\t%ld\t\n", (long) (intronStop + 1), (long) stop);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        }

        /* qualifier lines are (tab) (tab) (tab) qualifier key (tab) value */

        if (strstr (aa, "Pseudo") != NULL) {
          sprintf (str, "\t\t\tnote\ttRNA-Pseudo\n");
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        } else {
          sprintf (str, "\t\t\tproduct\t%s\n", aa);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        }

        /* introducing special qualifier to report anticodon sequence if requested */

        if (extras != NULL && strcmp (extras, "anticodon") == 0) {
          sprintf (str, "\t\t\tanti_codon_seq\t%s\n", anticodonSeq);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        }

        /* dash (formerly empty) gene qualifier to suppress /gene (e.g., if tRNA is in an intron) */

        sprintf (str, "\t\t\tgene\t-\n");
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
      }
    }

    /* detect last line of table header, ignoring everything before data section */

    if (strstr (buf, "-----") != NULL) {
      inBody = TRUE;
    }
  }

  pclose (fp);

  if (! headerSent) {
    printf ("Content-type: text/html\r\n\r\n");
    fflush (stdout);
  }

  if (idNotSent) {
    sprintf (str, ">Message\ntRNAscan-SE found no tRNA genes in this sequence\n");
    EncodeAndWrite (str, strlen (str), stdout);
    fflush (stdout);
  }
}
#endif

static CharPtr IsolateString (CharPtr str, Char ch)

{
  CharPtr  ptr;

  if (str == NULL || ch == '\0') return NULL;
  ptr = strchr (str, ch);
  if (ptr != NULL) {
    *ptr = '\0';
    ptr++;
  }
  return ptr;
}

static Bool ParseRange (CharPtr str, Int4Ptr startP, Int4Ptr stopP)

{
  CharPtr   nxt;
  CharPtr   ptr;
  long int  start = 0;
  long int  stop = 0;

  if (str == NULL || startP == NULL || stopP == NULL) return FALSE;
  *startP = 0;
  *stopP = 0;

  nxt = IsolateString (str, '(');
  if (nxt == NULL) return FALSE;

  ptr = nxt;
  nxt = IsolateString (ptr, '-');
  if (nxt == NULL) return FALSE;

  if (sscanf (ptr, "%ld", &start) != 1) return FALSE;

  ptr = nxt;
  nxt = IsolateString (ptr, ')');
  if (nxt == NULL) return FALSE;

  if (sscanf (ptr, "%ld", &stop) != 1) return FALSE;

  *startP = start;
  *stopP = stop;

  return TRUE;
}

static void RunTrnaScan (CharPtr tempfile)

{
  Char      aa [32];
  long int  anticodonStart;
  long int  anticodonStop;
  CharPtr   arg1 = "";
  CharPtr   arg2 = "";
  CharPtr   arg3 = "";
  CharPtr   beg;
  Char      buf [512];
  Char      cmmd [256];
  CharPtr   domain;
  CharPtr   end;
  FILE*     fp;
  CharPtr   gencode;
  Char      id [64];
  Int2      idNotSent = TRUE;
  CharPtr   intronBeg;
  CharPtr   intronEnd;
  long int  intronStart;
  long int  intronStop;
  CharPtr   nxt;
  Bool      pseudo;
  CharPtr   ptr;
  CharPtr   speed;
  long int  start;
  long int  stop;
  Char      str [256];

  /* launch tRNAscan-SE with -q parameter and name of data file */

  speed = FindByName ("speed");
  domain = FindByName ("domain");
  gencode = FindByName ("gencode");

  if (speed != NULL) {
    if (strcmp (speed, "slow") == 0) {
      arg1 = "-C ";
    }
  }

  if (domain != NULL) {
    if (strcmp (domain, "eukaryote") == 0) {
      arg2 = "";
    } else if (strcmp (domain, "prokaryote") == 0) {
      arg2 = "-P ";
    } else if (strcmp (domain, "archaea") == 0) {
      arg2 = "-A ";
    } else if (strcmp (domain, "organelle") == 0) {
      arg2 = "-O ";
    } else if (strcmp (domain, "general") == 0) {
      arg2 = "-G ";
    }
  }

  if (gencode != NULL) {
    if (strcmp (gencode, "standard") == 0) {
      arg3 = "";
    } else if (strcmp (gencode, "vertebrate") == 0) {
      arg3 = "-g gcode.vertmito ";
    } else if (strcmp (gencode, "yeast") == 0) {
      arg3 = "-g gcode.ystmito ";
    } else if (strcmp (gencode, "mold") == 0) {
      arg3 = "-g gcode.othmito ";
    } else if (strcmp (gencode, "invertebrate") == 0) {
      arg3 = "-g gcode.invmito ";
    } else if (strcmp (gencode, "echinoderm") == 0) {
      arg3 = "-g gcode.echdmito ";
    } else if (strcmp (gencode, "ciliate") == 0) {
      arg3 = "-g gcode.cilnuc ";
    }
  }

  sprintf (cmmd, "./tRNAscan-SE -q -f $ %s%s%s%s", arg1, arg2, arg3, tempfile);

  fp = popen (cmmd, "r");
  if (fp == NULL) return;

  /* send required first header information to stdout */

  printf ("Content-type: text/html\r\n\r\n");
  fflush (stdout);

  /* initialize variables */

  start = 0;
  stop = 0;
  intronStart = 0;
  intronStop = 0;
  anticodonStart = 0;
  anticodonStop = 0;
  id [0] = '\0';
  aa [0] = '\0';
  pseudo = FALSE;

  /* line by line processing of tRNAscan-SE output table */

  while (fgets (buf, sizeof (buf), fp) != NULL) {

    ptr = buf;
    if (*ptr == '\0') {

    } else if (strncmp (buf, "Str:", 4) == 0) {

    } else if (strncmp (buf, "Seq:", 4) == 0) {

      /* first line of output gives SeqId from FASTA definition line */

      if (idNotSent) {
        sprintf (str, ">Features %s tRNAscan-SE\n", id);
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
        idNotSent = FALSE;
      }

      /* first line of feature has start (tab) stop (tab) feature key */
      /* multiple intervals would have lines of start (tab) stop */

      if (intronStart == 0 && intronStop == 0) {
        sprintf (str, "%ld\t%ld\ttRNA\n", (long) start, (long) stop);
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
      } else {
        sprintf (str, "%ld\t%ld\ttRNA\n", (long) start, (long) (intronStart - 1));
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
        sprintf (str, "%ld\t%ld\n", (long) (intronStop + 1), (long) stop);
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
      }

      /* qualifier lines are (tab) (tab) (tab) qualifier key (tab) value */

      sprintf (str, "\t\t\tproduct\t%s\n", aa);
      EncodeAndWrite (str, strlen (str), stdout);
      fflush (stdout);

      if (pseudo) {
        sprintf (str, "\t\t\tpseudo\n");
        EncodeAndWrite (str, strlen (str), stdout);
        fflush (stdout);
      } else if (anticodonStart != 0 && anticodonStop != 0) {
        if (anticodonStart < anticodonStop) {
          sprintf (str, "\t\t\tanticodon\t(pos:%ld..%ld,aa:%s)\n",
                   (long) anticodonStart, (long) anticodonStop, aa);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        } else {
          sprintf (str, "\t\t\tanticodon\t(pos:complement(%ld..%ld),aa:%s)\n",
                   (long) anticodonStop, (long) anticodonStart, aa);
          EncodeAndWrite (str, strlen (str), stdout);
          fflush (stdout);
        }
      }

      /* dash (formerly empty) gene qualifier to suppress /gene (e.g., if tRNA is in an intron) */

      sprintf (str, "\t\t\tgene\t-\n");
      EncodeAndWrite (str, strlen (str), stdout);
      fflush (stdout);

      /* reset variables */

      start = 0;
      stop = 0;
      intronStart = 0;
      intronStop = 0;
      anticodonStart = 0;
      anticodonStop = 0;
      id [0] = '\0';
      aa [0] = '\0';
      pseudo = FALSE;

    } else if (strncmp (buf, "Type:", 5) == 0) {

      nxt = IsolateString (buf, ' ');
      if (nxt != NULL) {
        ptr = nxt;
        nxt = IsolateString (ptr, '\t');
        if (nxt != NULL) {
          strcpy (aa, ptr);
          if (strcmp (aa, "Undet") == 0 || strcmp (aa, "Sup") == 0) {
            strcpy (aa, "OTHER");
          }
          ptr = nxt;
          ParseRange (ptr, &anticodonStart, &anticodonStop);
        }
      }

    } else if (strncmp (buf, "Possible intron:", 16) == 0) {

      ParseRange (ptr, &intronStart, &intronStop);

    } else if (strncmp (buf, "Possible pseudogene:", 20) == 0) {

      pseudo = TRUE;

    } else if (strstr (buf, "Length:") != NULL) {

      ptr = strstr (buf, ".trna");
      if (ptr != NULL) {
        *ptr = '\0';
        ptr++;
        strcpy (id, buf);

        ParseRange (ptr, &start, &stop);
      }
    }
  }

  pclose (fp);

  if (idNotSent) {
    sprintf (str, ">Message\ntRNAscan-SE found no tRNA genes in this sequence\n");
    EncodeAndWrite (str, strlen (str), stdout);
    fflush (stdout);
  }
}


/* this program can provide several services, specified in URL query string */

static CharPtr services [] = {
  "", "custom", "echo", "seg", "trnascan", NULL
};

/* main function of cgi program, called by HTTPD server */

main (int argc, char *argv[])

{
  CharPtr  bf;
  Char     buf [256];
  size_t   ct;
  FILE*    fp;
  CharPtr  method;
  CharPtr  ptr;
  CharPtr  request;
  Int2     service;
  Char     tempfile [1024];

  /* at startup, first verify environment */

  method = getenv ("REQUEST_METHOD");
  if (method == NULL) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - Program launched from command line\n");
    fflush (stdout);
    return 1;
  }

  /* ensure that the POST method is being sent from the HTTPD server */

  if (strcmp (method, "POST") != 0) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - Method (%s) must be POST\n", method);
    fflush (stdout);
    return 1;
  }

  /* backward compatibility for query in URL after ? character */

  ptr = getenv ("QUERY_STRING");
  if (ptr != NULL && strlen (ptr) > 0) {
    if (! ParseQuery (ptr, TRUE)) {
      printf ("Content-type: text/html\r\n\r\n");
      printf (">Message\nFAILURE - QUERY_STRING parsing failed\n");
      fflush (stdout);
      if (query != NULL) {
        free (query);
      }
      return 1;
    }
  }

  /*
  *  copy all of stdin to temporary file before sending anything to stdout,
  *  to get around an apparent limitation in the HTTPD implementation that
  *  can cause socket deadlock
  */

  tmpnam (tempfile);
  fp = fopen (tempfile, "w");
  if (fp == NULL) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - file open failed\n");
    fflush (stdout);
    return 1;
  }

  while ((ct = ReadAndDecode (buf, sizeof (buf), stdin)) > 0) {

    bf = buf;

    /* write data part of buffer to temporary file */

    fwrite (bf, 1, ct, fp);
  }
  fflush (fp);
  fclose (fp);

  /* expect request=custom, request=echo, request=seg, or request=trnascan */

  request = FindByName ("request");
  if (request == NULL) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - No service request\n");
    fflush (stdout);
    return 1;
  }

  /* compare request value against list of available services */

  service = ListHasString (services, request);
  if (service < 1) {
    printf ("Content-type: text/html\r\n\r\n");
    printf (">Message\nFAILURE - Unable to match request '%s'\n", request);
    fflush (stdout);
    return 1;
  }

  /* call appropriate external analysis program */

  switch (service) {

#ifdef ALLOW_CUSTOM_PROGRAM

    /* for security, custom programs not allowed without symbol at compile time */

    case 1 :
      RunCustom (tempfile);
      break;

#endif /* ALLOW_CUSTOM_PROGRAM */

    case 2 :
      RunEcho (tempfile);
      break;
    case 3 :
      RunSeg (tempfile);
      break;
    case 4 :
      RunTrnaScan (tempfile);
      break;
    default :

      /* each function must send required first header information to stdout */

      printf ("Content-type: text/html\r\n\r\n");
      fflush (stdout);
      break;
  }

  /* flush buffer, cleanup temporary files and allocated memory, and exit */

  fflush (stdout);
  remove (tempfile);
  free (query);
  return 0;
}

