/* --------------------------------------------------------------------------
 * Copyright 2003-2011 (inclusive) Nathan Angelacos 
 *                   (nangel@users.sourceforge.net)
 * 
 *   This file is part of haserl.
 *
 *   Haserl is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License version 2,
 *   as published by the Free Software Foundation.
 *
 *   Haserl 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 haserl.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ------------------------------------------------------------------------ */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "common.h"
#include "h_error.h"
#include "h_script.h"
#include "h_bash.h"
#include "haserl.h"

#ifdef BASHEXTENSIONS
/* HTML, RUN, INCLUDE, EVAL, COMMENT, IF, ELIF, ELSE, ENDIF, CASE, WHEN,
   OTHERWISE, ENDCASE, WHILE, ENDWHILE, UNTIL, ENDUNTIL, FOR, ENDFOR,
   UNLESS, ELUN, UNELSE, ENDUNLESS, NOOP }; */
#else
/* HTML, RUN, INCLUDE, EVAL, COMMENT, NOOP */
#endif

const char *g_tag[] = {
  "",
  "",
  "in",
  "=",
  "#",
#ifdef BASHEXTENSIONS
  "if",
  "elif",
  "else",
  "endif",
  "case",
  "when",
  "otherwise",
  "endcase",
  "while",
  "endwhile",
  "until",
  "enduntil",
  "for",
  "endfor",
  "unless",
  "elun",
  "unelse",
  "endunless",
#endif
  ""
};

/* the open and close tags */
char open_tag[3] = "<%";
char close_tag[3] = "%>";


/* Open a script and return a populated script_t structure
 */
script_t *
load_script (char *filename, script_t * scriptlist)
{
  script_t *scriptbuf;
  int scriptfp;
  struct stat filestat;

  scriptfp = open (filename, O_NONBLOCK + O_RDONLY);
  if (scriptfp == -1)
    {				/* open failed */
      die_with_message (NULL, NULL, g_err_msg[E_FILE_OPEN_FAIL], filename);
    }

  fstat (scriptfp, &filestat);
  scriptbuf = (script_t *) xmalloc (sizeof (script_t));
  scriptbuf->name = (char *) xmalloc (strlen (filename) + 1);
  scriptbuf->buf = (char *) xmalloc (filestat.st_size + 1);

  memset (scriptbuf->name, 0, strlen (filename) + 1);
  memcpy (scriptbuf->name, filename, strlen (filename));
  memset (scriptbuf->buf, 0, filestat.st_size + 1);
  read (scriptfp, scriptbuf->buf, filestat.st_size);

  scriptbuf->size = filestat.st_size;
  scriptbuf->uid = filestat.st_uid;
  scriptbuf->gid = filestat.st_gid;
  scriptbuf->curpos = 0;
  scriptbuf->next = NULL;

  /* if we already have scripts, add this one to the end */
  if (scriptlist != NULL)
    {
      while (scriptlist->next)
	scriptlist = scriptlist->next;
      scriptlist->next = scriptbuf;
    }

  /* skip over the first line, if the file starts with a #!
   * This means that offset will start at the beginning of
   * the second line in most cases.
   */
  if (memcmp (scriptbuf->buf, "#!", 2) == 0)
    {
      while ((scriptbuf->curpos < scriptbuf->size) &&
	     ((char) scriptbuf->buf[scriptbuf->curpos] != '\n'))
	{
	  (scriptbuf->curpos)++;
	}
      (scriptbuf->curpos)++;
    }

  /* If this is the first script, switch to <? ?> mode only
   * if <% is not used anywhere else in the script.  Otherwise
   * don't change the tagging method
   */
  if (scriptlist == NULL)
    {
      if (strstr (scriptbuf->buf + scriptbuf->curpos, open_tag) == NULL)
	{
	  open_tag[1] = '?';
	  close_tag[0] = '?';
	}
    }
  close (scriptfp);
  return (scriptbuf);
}

/* Free the script structures */
void
free_script_list (script_t * script)
{
  script_t *next;
  while (script)
    {
      next = script->next;
      if (script->name)
	free (script->name);
      if (script->buf)
	free (script->buf);
      free (script);
      script = next;
    }
}

/* Insert this token into the token chain.
 * If tokenlist is null, create a new one
 */
token_t *
push_token_on_list (token_t * tokenlist, script_t * scriptbuf,
		    char *start, size_t len)
{
  token_t *me, *next;
  if (len == 0)
    return (tokenlist);

  me = (token_t *) xmalloc (sizeof (token_t));

  if (tokenlist == NULL)
    {
      next = NULL;
    }
  else
    {
      next = tokenlist->next;
      tokenlist->next = me;
    }

  me->next = next;
  me->script = scriptbuf;
  me->buf = start;
  me->len = len;

  return (me);
}

/* Free a token chain */
void
free_token_list (token_t * tokenlist)
{
  token_t *next;

  while (tokenlist)
    {
      next = tokenlist->next;
      free (tokenlist);
      tokenlist = next;
    }
}

#ifndef JUST_LUACSHELL

/* return the point in a script where the "comment" ends */
char *
skip_comment (char *startbuf, char *endbuf)
{
  unsigned int c_lev = 1;
  char *s_tag, *e_tag;

  startbuf += 2;
  while (startbuf < endbuf)
    {
      s_tag = strstr (startbuf, open_tag);
      e_tag = strstr (startbuf, close_tag);

      if (!e_tag)
	{
	  break;
	}

      if ((s_tag) && (s_tag < e_tag))
	{
	  c_lev++;
	  startbuf = s_tag + 2;
	  continue;
	}
      /* otherwise, we have an end tag first */
      c_lev--;
      startbuf = e_tag;
      if (c_lev == 0)
	{
	  return (startbuf);
	}
      startbuf += 2;

    }
  return NULL;
}

/* build a tokenchain from a script.   This step just
 * splits a script buf into parts that are separated
 * by <% %>.  If the <% %> are out of order, then it
 * flags that, but doesn't try to do anything else.
 *
 * For nesting comments, this function IS aware of 
 * the comment tag, and will try accomodate commented
 * tags.  Commented script never enters the token list
 */
token_t *
build_token_list (script_t * scriptbuf, token_t * tokenlist)
{

  char *start, *end, *curpos, *endpos;
  token_t *curtoken, *firsttoken;

  curtoken = tokenlist;
  firsttoken = tokenlist;

  curpos = scriptbuf->buf + scriptbuf->curpos;
  endpos = scriptbuf->buf + scriptbuf->size;

  while (curpos < endpos)
    {
      start = strstr (curpos, open_tag);
      end = strstr (curpos, close_tag);

      /* if this is a comment tag, the end is at the end of the comment */
      if ((start) && (memcmp (start + 2, g_tag[COMMENT], 1) == 0))
	{
	  /* save any bare html before the comment */
	  curtoken = push_token_on_list (curtoken, scriptbuf, curpos,
					 start - curpos);
	  if (firsttoken == NULL)
	    firsttoken = curtoken;
	  /* push start of token to end of token  */
	  end = skip_comment (start, endpos);
	  if (end)
	    {
	      curpos = end + 2;
	      continue;
	    }
	}

      if (start && !end)
	die_with_message (scriptbuf, start, g_err_msg[E_NO_END_MARKER],
			  open_tag[1]);

      if ((start > end) || (!start && end))
	die_with_message (scriptbuf, end, g_err_msg[E_END_BEFORE_BEGIN],
			  open_tag[1], open_tag[1]);

      if (start && (strstr (start + 1, open_tag)
		    && (strstr (start + 1, open_tag) < end)))
	die_with_message (scriptbuf, start, g_err_msg[E_NO_END_MARKER],
			  open_tag[1]);
      if (end)
	{
	  /* push curpos to the start of the token */
	  curtoken = push_token_on_list (curtoken, scriptbuf, curpos,
					 start - curpos);
	  if (firsttoken == NULL)
	    firsttoken = curtoken;
	  /* push start of token to end of token  */
	  curtoken =
	    push_token_on_list (curtoken, scriptbuf, start, end - start);
	  if (firsttoken == NULL)
	    firsttoken = curtoken;
	  curpos = end + 2;
	}
      else
	{
	  /* push curpos to end of script */
	  curtoken =
	    push_token_on_list (curtoken, scriptbuf, curpos, endpos - curpos);
	  if (firsttoken == NULL)
	    firsttoken = curtoken;
	  curpos = endpos;
	}
    }

  return (firsttoken);
}

/* syntax check a script */
void
preprocess_token_list (token_t * tokenlist)
{
  script_t *newscript;
  token_t *me;
  char *cp;

  me = tokenlist;
  /* walk the chain to fill in the tags */
  while (me)
    {
      if (memcmp (me->buf, open_tag, 2))
	{
	  me->tag = HTML;
	}
      else
	{
	  me->tag = NOOP;
	  me->buf[me->len] = '\0';
	  cp = me->buf + 2;	/* skip <? */
	  if (memcmp (cp, g_tag[INCLUDE], 2) == 0)
	    {
	      me->tag = INCLUDE;
	      /* skip the first word - in, include, include-file, etc */
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      cp = find_whitespace (me->buf);
	      *cp = '\0';
	      me->len = strlen (me->buf) + 1;
	      newscript = load_script (me->buf, me->script);
	      build_token_list (newscript, me);
	    }
	  else if (memcmp (cp, g_tag[EVAL], 1) == 0)

	    {
	      me->tag = EVAL;
	      me->buf = find_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
#ifdef BASHEXTENSIONS
	  else if (memcmp (cp, g_tag[IF], 2) == 0)
	    {
	      me->tag = IF;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ELIF], 4) == 0)
	    {
	      me->tag = ELIF;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ELSE], 4) == 0)
	    {
	      me->tag = ELSE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDIF], 5) == 0)
	    {
	      me->tag = ENDIF;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[CASE], 4) == 0)
	    {
	      me->tag = CASE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[WHEN], 4) == 0)
	    {
	      me->tag = WHEN;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[OTHERWISE], 9) == 0)
	    {
	      me->tag = OTHERWISE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDCASE], 7) == 0)
	    {
	      me->tag = ENDCASE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[WHILE], 5) == 0)
	    {
	      me->tag = WHILE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDWHILE], 8) == 0)
	    {
	      me->tag = ENDWHILE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[UNTIL], 5) == 0)
	    {
	      me->tag = UNTIL;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDUNTIL], 8) == 0)
	    {
	      me->tag = ENDUNTIL;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[FOR], 3) == 0)
	    {
	      me->tag = FOR;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDFOR], 6) == 0)
	    {
	      me->tag = ENDFOR;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[UNLESS], 6) == 0)
	    {
	      me->tag = UNLESS;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ELUN], 4) == 0)
	    {
	      me->tag = ELUN;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[UNELSE], 6) == 0)
	    {
	      me->tag = UNELSE;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
	  else if (memcmp (cp, g_tag[ENDUNLESS], 9) == 0)
	    {
	      me->tag = ENDUNLESS;
	      me->buf = find_whitespace (me->buf);
	      me->buf = skip_whitespace (me->buf);
	      me->len = strlen (me->buf);
	    }
#endif /* BASHEXTENSIONS */
	  if (isspace (*cp))
	    {
	      me->tag = RUN;
	      me->buf = cp;
	    }
	  if (me->tag == NOOP)
	    {
	      die_with_message (me->script, cp, g_err_msg[E_NO_OP]);
	    }
	  me->len = strlen (me->buf);
	}
      me = me->next;
    }
}


token_t *
process_token_list (buffer_t * buf, token_t * token)
{
  char *c;

  haserl_buffer_init (buf);
  shell_exec (buf, "\n");	/* try to get the error reporting match the line number */

  while (token)
    {
      switch (token->tag)
	{
	case HTML:
	  /* Change from 0.8.0  - if the whole thing is just
	     whitespace, don't print it */
	  c = token->buf;
	  while ((c < (token->buf + token->len)) && (isspace (*c)))
	    c++;
	  if (c != token->buf + token->len)
	    shell_echo (buf, token->buf, token->len);
	  break;
	case RUN:
	  shell_exec (buf, token->buf);
	  shell_exec (buf, "\n");
	  break;
	case EVAL:
	  shell_eval (buf, token->buf, token->len);
	  break;
#ifdef BASHEXTENSIONS
	case IF:
	  shell_if (buf, token->buf, token->len);
	  break;
	case ELIF:
	  shell_elif (buf, token->buf, token->len);
	  break;
	case ELSE:
	  shell_else (buf, token->buf, token->len);
	  break;
	case ENDIF:
	  shell_endif (buf, token->buf, token->len);
	  break;
	case CASE:
	  shell_case (buf, token->buf, token->len);
	  break;
	case WHEN:
	  shell_when (buf, token->buf, token->len);
	  break;
	case OTHERWISE:
	  shell_otherwise (buf, token->buf, token->len);
	  break;
	case ENDCASE:
	  shell_endcase (buf, token->buf, token->len);
	  break;
	case WHILE:
	  shell_while (buf, token->buf, token->len);
	  break;
	case ENDWHILE:
	  shell_endwhile (buf, token->buf, token->len);
	  break;
	case UNTIL:
	  shell_until (buf, token->buf, token->len);
	  break;
	case ENDUNTIL:
	  shell_enduntil (buf, token->buf, token->len);
	  break;
	case FOR:
	  shell_for (buf, token->buf, token->len);
	  break;
	case ENDFOR:
	  shell_endfor (buf, token->buf, token->len);
	  break;
	case UNLESS:
	  shell_unless (buf, token->buf, token->len);
	  break;
	case ELUN:
	  shell_elun (buf, token->buf, token->len);
	  break;
	case UNELSE:
	  shell_unelse (buf, token->buf, token->len);
	  break;
	case ENDUNLESS:
	  shell_endunless (buf, token->buf, token->len);
	  break;
#endif
	default:
	  break;
	}
      token = token->next;
    }

  return (token);

}

#endif
