/* Copyright (C) 1992, 2000 artofcode LLC.  All rights reserved.
  
  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.

*/

/*$Id: zmisc2.c,v 1.3.6.1.2.1 2003/01/17 00:49:06 giles Exp $ */
/* Miscellaneous Level 2 operators */
#include "memory_.h"
#include "string_.h"
#include "ghost.h"
#include "oper.h"
#include "estack.h"
#include "iddict.h"
#include "idparam.h"
#include "iparam.h"
#include "dstack.h"
#include "ilevel.h"
#include "iname.h"
#include "iutil2.h"
#include "ivmspace.h"
#include "store.h"

/* Forward references */
private int set_language_level(P2(i_ctx_t *, int));

/* ------ Language level operators ------ */

/* - .languagelevel <int> */
private int
zlanguagelevel(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    push(1);
    make_int(op, LANGUAGE_LEVEL);
    return 0;
}

/* <int> .setlanguagelevel - */
private int
zsetlanguagelevel(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    int code = 0;

    check_type(*op, t_integer);
    if (op->value.intval != LANGUAGE_LEVEL) {
	code = set_language_level(i_ctx_p, (int)op->value.intval);
	if (code < 0)
	    return code;
    }
    LANGUAGE_LEVEL = op->value.intval;
    pop(1);
    return code;
}

/* ------ Initialization procedure ------ */

/* The level setting ops are recognized even in Level 1 mode. */
const op_def zmisc2_op_defs[] =
{
    {"0.languagelevel", zlanguagelevel},
    {"1.setlanguagelevel", zsetlanguagelevel},
    op_def_end(0)
};

/* ------ Internal procedures ------ */

/*
 * Adjust the interpreter for a change in language level.
 * This is used for the .setlanguagelevel operator,
 * and (perhaps someday) after a restore.
 */
private int swap_level_dict(P2(i_ctx_t *i_ctx_p, const char *dict_name));
private int swap_entry(P4(i_ctx_t *i_ctx_p, ref elt[2], ref * pdict,
			  ref * pdict2));
private int
set_language_level(i_ctx_t *i_ctx_p, int new_level)
{
    int old_level = LANGUAGE_LEVEL;
    ref *pgdict =		/* globaldict, if present */
	ref_stack_index(&d_stack, ref_stack_count(&d_stack) - 2);
    ref *level2dict;
    int code = 0;

    if (new_level < 1 ||
	new_level >
	(dict_find_string(systemdict, "ll3dict", &level2dict) > 0 ? 3 : 2)
	)
	return_error(e_rangecheck);
    if (dict_find_string(systemdict, "level2dict", &level2dict) <= 0)
	return_error(e_undefined);
    /*
     * As noted in dstack.h, we allocate the extra d-stack entry for
     * globaldict even in Level 1 mode; in Level 1 mode, this entry
     * holds an extra copy of systemdict, and [count]dictstack omit the
     * very bottommost entry.
     */
    while (new_level != old_level) {
	switch (old_level) {
	    case 1: {		/* 1 => 2 or 3 */
				/* Put globaldict in the dictionary stack. */
		ref *pdict;

		/*
		 * This might be called so early in initialization that
		 * globaldict hasn't been defined yet.  If so, just skip
		 * this step.
		 */
		code = dict_find_string(level2dict, "globaldict", &pdict);
		if (code > 0) {
		    if (!r_has_type(pdict, t_dictionary))
			return_error(e_typecheck);
		    *pgdict = *pdict;
		}
		/* Set other flags for Level 2 operation. */
		dict_auto_expand = true;
		}
		code = swap_level_dict(i_ctx_p, "level2dict");
		if (code < 0)
		    return code;
		++old_level;
		continue;
	    case 3:		/* 3 => 1 or 2 */
		code = swap_level_dict(i_ctx_p, "ll3dict");
		if (code < 0)
		    return code;
		--old_level;
		continue;
	    default:		/* 2 => 1 or 3 */
		break;
	}
	switch (new_level) {
	    case 1: {		/* 2 => 1 */
		/*
		 * Clear the cached definition pointers of all names defined
		 * in globaldict.  This will slow down future lookups, but
		 * we don't care.
		 */
		int index = dict_first(pgdict);
		ref elt[2];

		while ((index = dict_next(pgdict, index, &elt[0])) >= 0)
		    if (r_has_type(&elt[0], t_name))
			name_invalidate_value_cache(&elt[0]);
		/* Overwrite globaldict in the dictionary stack. */
		*pgdict = *systemdict;
		/* Set other flags for Level 1 operation. */
		dict_auto_expand = false;
		}
		code = swap_level_dict(i_ctx_p, "level2dict");
		break;
	    case 3:		/* 2 => 3 */
		code = swap_level_dict(i_ctx_p, "ll3dict");
		break;
	    default:		/* not possible */
		return_error(e_Fatal);
	}
	break;
    }
    dict_set_top();		/* reload dict stack cache */
    return code;
}

/*
 * Swap the contents of a level dictionary (level2dict or ll3dict) and
 * systemdict.  If a value in the level dictionary is itself a dictionary,
 * and it contains a key/value pair referring to itself, swap its contents
 * with the contents of the same dictionary in systemdict.  (This is a hack
 * to swap the contents of statusdict.)
 */
private int
swap_level_dict(i_ctx_t *i_ctx_p, const char *dict_name)
{
    ref *pleveldict;
    ref rleveldict;
    int index;
    ref elt[2];			/* key, value */
    ref *psubdict;

    /*
     * We have to copy the refs for leveldict and subdict, since they may
     * move if their containing dictionary is resized.
     */
    if (dict_find_string(systemdict, dict_name, &pleveldict) <= 0)
	return_error(e_undefined);
    rleveldict = *pleveldict;
    index = dict_first(&rleveldict);
    while ((index = dict_next(&rleveldict, index, &elt[0])) >= 0)
	if (r_has_type(&elt[1], t_dictionary) &&
	    dict_find(&elt[1], &elt[0], &psubdict) > 0 &&
	    obj_eq(&elt[1], psubdict)
	    ) {
	    /* elt[1] is the 2nd-level sub-dictionary */
	    int isub = dict_first(&elt[1]);
	    ref subelt[2];
	    int found = dict_find(systemdict, &elt[0], &psubdict);
	    ref rsubdict;

	    if (found <= 0)
		continue;
	    rsubdict = *psubdict;
	    while ((isub = dict_next(&elt[1], isub, &subelt[0])) >= 0)
		if (!obj_eq(&subelt[0], &elt[0])) {
		    /* don't swap dict itself */
		    int code = swap_entry(i_ctx_p, subelt, &rsubdict, &elt[1]);

		    if (code < 0)
			return code;
		}
	} else {
	    int code = swap_entry(i_ctx_p, elt, systemdict, &rleveldict);

	    if (code < 0)
		return code;
	}
    return 0;
}

/*
 * Swap an entry from a higher level dictionary into a base dictionary.
 * elt[0] is the key, elt[1] is the current value in the Level 2 dictionary
 * (*pdict2).
 */
private int
swap_entry(i_ctx_t *i_ctx_p, ref elt[2], ref * pdict, ref * pdict2)
{
    ref *pvalue;
    ref old_value;		/* current value in *pdict */
    int found = dict_find(pdict, &elt[0], &pvalue);

    switch (found) {
	default:		/* <0, error */
	    /*
	     * The only possible error here is a dictfull error, which is
	     * harmless.
	     */
	    /* fall through */
	case 0:		/* missing */
	    make_null(&old_value);
	    break;
	case 1:		/* present */
	    old_value = *pvalue;
    }
    /*
     * Temporarily flag the dictionaries as local, so that we don't
     * get invalidaccess errors.  (We know that they are both
     * referenced from systemdict, so they are allowed to reference
     * local objects even if they are global.)
     */
    {
	uint space2 = r_space(pdict2);
	int code;

	r_set_space(pdict2, avm_local);
	idict_put(pdict2, &elt[0], &old_value);
	if (r_has_type(&elt[1], t_null)) {
	    code = idict_undef(pdict, &elt[0]);
	    if (code == e_undefined &&
		r_has_type(&old_value, t_null)
		)
		code = 0;
	} else {
	    uint space = r_space(pdict);

	    r_set_space(pdict, avm_local);
	    code = idict_put(pdict, &elt[0], &elt[1]);
	    r_set_space(pdict, space);
	}
	r_set_space(pdict2, space2);
	return code;
    }
}
