/* flowfuncs.c: -*- C -*-  Flow control functions for Meta-HTML */

/*  Copyright (c) 1997 Brian J. Fox
    Author: Brian J. Fox (bfox@ai.mit.edu) Sat Jun 21 12:02:48 1997.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"

/************************************************************/
/*							    */
/*			Flow Control Functions		    */
/*							    */
/************************************************************/

/* <if test then else> If eval (test) produces non-zero length text, then... */
static void pf_if (PFunArgs);

/* <ifeq this that then else> If eval (this) = eval (that), then, else */
static void pf_ifeq (PFunArgs);

/* <when TEST> body </when> */
static void pf_when (PFunArgs);

/* <var-case [this=that do-this] ...> Do-this where <get-var THIS> == THAT. */
static void pf_var_case (PFunArgs);

/* <while test>  body-text ... </while> Do BODY-TEXT while TEST
   produces non-zero length text. */
#define PAGE_ITERATOR_MAX_COUNT 500 /* No more than this many times. */
static void pf_while (PFunArgs);

/* <break> Only valid inside of <while> or <foreach>. */
static void pf_break (PFunArgs);

/* <set-var x=hello>
   <with x=1 z=<get-var foo>> <get-var x> </with> --> 1
   <get-var x> --> hello */
static void pf_with (PFunArgs);

static PFunDesc func_table[] =
{
  { "IF",		0, 0, pf_if },
  { "IFEQ",		0, 0, pf_ifeq },
  { "WHEN",		1, 0, pf_when },
  { "VAR-CASE",		0, 0, pf_var_case },
  { "WHILE",		1, 0, pf_while },
  { "BREAK",		0, 0, pf_break },
  { "WITH",		1, 0, pf_with },

  { (char *)NULL,	0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_flowfunc_functions)

void
initialize_flowfunc_functions (Package *package)
{
  register int i;
  Symbol *sym;

  for (i = 0; func_table[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (package, func_table[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&func_table[i]);
    }
}

static void
pf_if (PFunArgs)
{
  char *test_clause = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *then_clause = get_positional_arg (vars, 1);
  char *else_clause = get_positional_arg (vars, 2);
  char *consequence;

  if (!empty_string_p (test_clause))
    consequence = then_clause;
  else
    consequence = else_clause;

  if (consequence != (char *)NULL)
    bprintf_insert (page, start, "%s", consequence);

  xfree (test_clause);
}

static void
pf_ifeq (PFunArgs)
{
  char *left_clause = mhtml_evaluate_string (get_positional_arg (vars, 0));
  char *right_clause = mhtml_evaluate_string (get_positional_arg (vars, 1));
  char *then_clause = get_positional_arg (vars, 2);
  char *else_clause = get_positional_arg (vars, 3);
  int caseless_p = var_present_p (vars, "CASELESS");
  char *consequence;

  if (((empty_string_p (left_clause)) && (empty_string_p (right_clause))) ||
      ((left_clause && right_clause) &&
       (((!caseless_p) && (strcmp (left_clause, right_clause) == 0)) ||
	((caseless_p) && (strcasecmp (left_clause, right_clause) == 0)))))
    consequence = then_clause;
  else
    consequence = else_clause;

  if (consequence != (char *)NULL)
    bprintf_insert (page, start, "%s", consequence);

  xfree (left_clause);
  xfree (right_clause);
}

static void
pf_when (PFunArgs)
{
  char *test = mhtml_evaluate_string (get_positional_arg (vars, 0));

  if (!empty_string_p (test))
    bprintf_insert (page, start, "%s", body->buffer);

  if (test) free (test);
}

static void
pf_break (PFunArgs)
{
  page->attachment = (void *)bprintf_create_buffer ();
  page->bindex = start;
  page->buffer[start] = '\0';
}

static void
pf_while (PFunArgs)
{
  char *test = get_positional_arg (vars, 0);
  int iteration_count = 0;
  char *limit_string = pagefunc_get_variable ("mhtml::iteration-limit");
  int limit = limit_string ? atoi (limit_string) : PAGE_ITERATOR_MAX_COUNT;

  while (1)
    {
      char *result = mhtml_evaluate_string (test);
      int empty = empty_string_p (result);
      PAGE *code;

      xfree (result);
      iteration_count++;

      if (empty || (iteration_count > limit))
	break;

      code = page_copy_page (body);
      page_process_page_internal (code);

      if (code != (PAGE *)NULL)
	{
	  int broken = (code->attachment != (void *)NULL);

	  if (code->bindex != 0)
	    {
	      bprintf_insert (page, start, "%s", code->buffer);
	      start += (code->bindex);

	      *newstart = start;
	    }

	  page_free_page (code);
	  if (broken)
	    break;
	}
    }
}

static void
pf_var_case (PFunArgs)
{
  register int i = 0;
  char **names = get_vars_names (vars);
  char **vals = get_vars_vals (vars);
  static char *nullval = "";
  char *default_action = (char *)NULL;
  int clause_found = 0;

  if (names != (char **)NULL)
    {
      while (1)
	{
	  char *name = (char *)NULL;
	  char *case_value = (char *)NULL;
	  char *page_value = (char *)NULL;
	  char *action = (char *)NULL;

	  if ((names[i] == (char *)NULL) || (names[i + 1] == (char *)NULL))
	    break;

	  name = mhtml_evaluate_string (names[i]);
	  case_value = mhtml_evaluate_string (vals[i]);
	  page_value = pagefunc_get_variable (name);
	  action = names[i + 1];
	  i += 2;

	  if (name != (char *)NULL)
	    {
	      /* Check for special "default" case. */
	      if (strcasecmp (name, "default") == 0)
		{
		  default_action = action;
		  if (case_value) free (case_value);
		  if (page_value) free (page_value);
		  free (name);
		  continue;
		}
	      free (name);
	    }

	  /* Check the value against the page value. */
	  if (empty_string_p (page_value))
	    {
	      page_value = nullval;
	    }

	  if (empty_string_p (case_value))
	    {
	      if (case_value) free (case_value);
	      case_value = nullval;
	    }

	  if ((page_value == case_value) ||
	      (strcasecmp (page_value, case_value) == 0))
	    {
	      clause_found = 1;
	      if (action != (char *)NULL)
		bprintf_insert (page, start, "%s", action);

	      if (case_value != nullval) free (case_value);
	      break;
	    }

	  if (case_value != nullval) free (case_value);
	}

      if (default_action && !clause_found)
	bprintf_insert (page, start, "%s", default_action);
    }
}

/* <set-var x=hello>
   <with x=1 z=<get-var foo>> <get-var x> </with> --> 1
   <get-var x> --> hello */
static void
pf_with (PFunArgs)
{
  register int i = 0;
  Package *saved_vars = symbol_get_package ((char *)NULL);
  Package *pack = CurrentPackage;
  char *packname = pack && pack->name ? strdup (pack->name) : (char *)NULL;
  char **names = get_vars_names (vars);
  char **vals = get_vars_vals (vars);
  int jump_again = 0;

  /* Save the current variables in our local package, and bind those
     variables to their new values. */
  for (i = 0; ((names != (char **)NULL) && (names[i] != (char *)NULL)); i++)
    {
      char *canonical_name = symbol_canonical_name (names[i]);
      Symbol *sym = symbol_intern (canonical_name);
      symbol_copy (sym, saved_vars);
  
      pagefunc_set_variable (names[i], vals[i]);
      free (canonical_name);
    }

  /* Execute the body. */
  {
    PageEnv *page_environ;
    PAGE *body_code = page_copy_page (body);

    page_environ = pagefunc_save_environment ();

    if ((jump_again = setjmp (page_jmp_buffer)) == 0)
      page_process_page_internal (body_code);
    pagefunc_restore_environment (page_environ);

    if (body_code != (PAGE *)NULL)
      {
	if (!jump_again && (body_code->buffer != (char *)NULL))
	  {
	    bprintf_insert (page, start, "%s", body_code->buffer);
	    *newstart = start + (body_code->bindex);
	  }

	page_free_page (body_code);
      }
  }

  /* If the package had a name before, get the named package again, because
     the body code might have deleted it!  If it had no name, there isn't
     any way that the body code could delete it, so trust that it exists. */
  if (packname)
    {
      pack = symbol_get_package (packname);
      free (packname);
    }

  /* Restore the values. */
  for (i = 0; ((names != (char **)NULL) && (names[i] != (char *)NULL)); i++)
    {
      char *canonical_name = symbol_canonical_name (names[i]);
      Symbol *sym = symbol_intern_in_package (saved_vars, canonical_name);

      symbol_copy (sym, pack);
      free (canonical_name);
    }

  if (jump_again) longjmp (page_jmp_buffer, 1);
}

