/* Copyright (C) 2002 MySQL AB

   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 2 of the License, 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, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include "mysql_priv.h"
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation
#endif

#if defined(WIN32) || defined(__WIN__)
#undef SAFEMALLOC				/* Problems with threads */
#endif

#include "sp_pcontext.h"
#include "sp_head.h"

/*
 * Sanity check for SQLSTATEs. Will not check if it's really an existing
 * state (there are just too many), but will check length and bad characters.
 * Returns TRUE if it's ok, FALSE if it's bad.
 */
bool
sp_cond_check(LEX_STRING *sqlstate)
{
  int i;
  const char *p;

  if (sqlstate->length != 5)
    return FALSE;
  for (p= sqlstate->str, i= 0 ; i < 5 ; i++)
  {
    char c = p[i];

    if ((c < '0' || '9' < c) &&
	(c < 'A' || 'Z' < c))
      return FALSE;
  }
  return TRUE;
}

sp_pcontext::sp_pcontext(sp_pcontext *prev)
  : Sql_alloc(), m_psubsize(0), m_csubsize(0), m_hsubsize(0),
    m_handlers(0), m_parent(prev)
{
  VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8));
  VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8));
  VOID(my_init_dynamic_array(&m_cursor, sizeof(LEX_STRING), 16, 8));
  VOID(my_init_dynamic_array(&m_handler, sizeof(sp_cond_type_t *), 16, 8));
  m_label.empty();
  m_children.empty();
  if (!prev)
    m_poffset= m_coffset= 0;
  else
  {
    m_poffset= prev->current_pvars();
    m_coffset= prev->current_cursors();
  }
}

void
sp_pcontext::destroy()
{
  List_iterator_fast<sp_pcontext> li(m_children);
  sp_pcontext *child;

  while ((child= li++))
    child->destroy();

  m_children.empty();
  m_label.empty();
  delete_dynamic(&m_pvar);
  delete_dynamic(&m_cond);
  delete_dynamic(&m_cursor);
  delete_dynamic(&m_handler);
}

sp_pcontext *
sp_pcontext::push_context()
{
  sp_pcontext *child= new sp_pcontext(this);

  if (child)
    m_children.push_back(child);
  return child;
}

sp_pcontext *
sp_pcontext::pop_context()
{
  uint submax= max_pvars();

  if (submax > m_parent->m_psubsize)
    m_parent->m_psubsize= submax;
  submax= max_handlers();
  if (submax > m_parent->m_hsubsize)
    m_parent->m_hsubsize= submax;
  submax= max_cursors();
  if (submax > m_parent->m_csubsize)
    m_parent->m_csubsize= submax;
  return m_parent;
}

uint
sp_pcontext::diff_handlers(sp_pcontext *ctx)
{
  uint n= 0;
  sp_pcontext *pctx= this;

  while (pctx && pctx != ctx)
  {
    n+= pctx->m_handlers;
    pctx= pctx->parent_context();
  }
  if (pctx)
    return n;
  return 0;			// Didn't find ctx
}

uint
sp_pcontext::diff_cursors(sp_pcontext *ctx)
{
  sp_pcontext *pctx= this;

  while (pctx && pctx != ctx)
    pctx= pctx->parent_context();
  if (pctx)
    return ctx->current_cursors() - pctx->current_cursors();
  return 0;			// Didn't find ctx
}

/* This does a linear search (from newer to older variables, in case
** we have shadowed names).
** It's possible to have a more efficient allocation and search method,
** but it might not be worth it. The typical number of parameters and
** variables will in most cases be low (a handfull).
** ...and, this is only called during parsing.
*/
sp_pvar_t *
sp_pcontext::find_pvar(LEX_STRING *name, my_bool scoped)
{
  uint i= m_pvar.elements;

  while (i--)
  {
    sp_pvar_t *p;

    get_dynamic(&m_pvar, (gptr)&p, i);
    if (my_strnncoll(system_charset_info,
		     (const uchar *)name->str, name->length,
		     (const uchar *)p->name.str, p->name.length) == 0)
    {
      return p;
    }
  }
  if (!scoped && m_parent)
    return m_parent->find_pvar(name, scoped);
  return NULL;
}

/*
  Find a variable by offset from the top.
  This used for two things:
  - When evaluating parameters at the beginning, and setting out parameters
    at the end, of invokation. (Top frame only, so no recursion then.)
  - For printing of sp_instr_set. (Debug mode only.)
 */
sp_pvar_t *
sp_pcontext::find_pvar(uint offset)
{
  if (m_poffset <= offset && offset < m_poffset + m_pvar.elements)
  {                           // This frame
    sp_pvar_t *p;

    get_dynamic(&m_pvar, (gptr)&p, offset - m_poffset);
    return p;
  }
  if (m_parent)
    return m_parent->find_pvar(offset); // Some previous frame
  return NULL;                  // index out of bounds
}

void
sp_pcontext::push_pvar(LEX_STRING *name, enum enum_field_types type,
		       sp_param_mode_t mode)
{
  sp_pvar_t *p= (sp_pvar_t *)sql_alloc(sizeof(sp_pvar_t));

  if (p)
  {
    if (m_pvar.elements == m_psubsize)
      m_psubsize+= 1;
    p->name.str= name->str;
    p->name.length= name->length;
    p->type= type;
    p->mode= mode;
    p->offset= current_pvars();
    p->dflt= NULL;
    insert_dynamic(&m_pvar, (gptr)&p);
  }
}

sp_label_t *
sp_pcontext::push_label(char *name, uint ip)
{
  sp_label_t *lab = (sp_label_t *)sql_alloc(sizeof(sp_label_t));

  if (lab)
  {
    lab->name= name;
    lab->ip= ip;
    lab->type= SP_LAB_GOTO;
    lab->ctx= this;
    m_label.push_front(lab);
  }
  return lab;
}

sp_label_t *
sp_pcontext::find_label(char *name)
{
  List_iterator_fast<sp_label_t> li(m_label);
  sp_label_t *lab;

  while ((lab= li++))
    if (my_strcasecmp(system_charset_info, name, lab->name) == 0)
      return lab;

  if (m_parent)
    return m_parent->find_label(name);
  return NULL;
}

void
sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val)
{
  sp_cond_t *p= (sp_cond_t *)sql_alloc(sizeof(sp_cond_t));

  if (p)
  {
    p->name.str= name->str;
    p->name.length= name->length;
    p->val= val;
    insert_dynamic(&m_cond, (gptr)&p);
  }
}

/*
 * See comment for find_pvar() above
 */
sp_cond_type_t *
sp_pcontext::find_cond(LEX_STRING *name, my_bool scoped)
{
  uint i= m_cond.elements;

  while (i--)
  {
    sp_cond_t *p;

    get_dynamic(&m_cond, (gptr)&p, i);
    if (my_strnncoll(system_charset_info,
		     (const uchar *)name->str, name->length,
		     (const uchar *)p->name.str, p->name.length) == 0)
    {
      return p->val;
    }
  }
  if (!scoped && m_parent)
    return m_parent->find_cond(name, scoped);
  return NULL;
}

/*
 * This only searches the current context, for error checking of
 * duplicates.
 * Returns TRUE if found.
 */
bool
sp_pcontext::find_handler(sp_cond_type_t *cond)
{
  uint i= m_handler.elements;

  while (i--)
  {
    sp_cond_type_t *p;

    get_dynamic(&m_handler, (gptr)&p, i);
    if (cond->type == p->type)
    {
      switch (p->type)
      {
      case sp_cond_type_t::number:
	if (cond->mysqlerr == p->mysqlerr)
	  return TRUE;
	break;
      case sp_cond_type_t::state:
	if (strcmp(cond->sqlstate, p->sqlstate) == 0)
	  return TRUE;
	break;
      default:
	return TRUE;
      }
    }
  }
  return FALSE;
}

void
sp_pcontext::push_cursor(LEX_STRING *name)
{
  LEX_STRING n;

  if (m_cursor.elements == m_csubsize)
    m_csubsize+= 1;
  n.str= name->str;
  n.length= name->length;
  insert_dynamic(&m_cursor, (gptr)&n);
}

/*
 * See comment for find_pvar() above
 */
my_bool
sp_pcontext::find_cursor(LEX_STRING *name, uint *poff, my_bool scoped)
{
  uint i= m_cursor.elements;

  while (i--)
  {
    LEX_STRING n;

    get_dynamic(&m_cursor, (gptr)&n, i);
    if (my_strnncoll(system_charset_info,
		     (const uchar *)name->str, name->length,
		     (const uchar *)n.str, n.length) == 0)
    {
      *poff= m_coffset + i;
      return TRUE;
    }
  }
  if (!scoped && m_parent)
    return m_parent->find_cursor(name, poff, scoped);
  return FALSE;
}

/*
  Find a cursor by offset from the top.
  This is only used for debugging.
 */
my_bool
sp_pcontext::find_cursor(uint offset, LEX_STRING *n)
{
  if (m_coffset <= offset && offset < m_coffset + m_cursor.elements)
  {                           // This frame
    get_dynamic(&m_cursor, (gptr)n, offset - m_coffset);
    return TRUE;
  }
  if (m_parent)
    return m_parent->find_cursor(offset, n); // Some previous frame
  return FALSE;                 // index out of bounds
}