/******************************************************************************
 * Copyright (c) 2000-2021 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Baranyi, Botond
 *   Beres, Szabolcs
 *   Cserveni, Akos
 *   Delic, Adam
 *   Feher, Csaba
 *   Forstner, Matyas
 *   Kovacs, Ferenc
 *   Kremer, Peter
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Bence Janos
 *   Szabo, Janos Zoltan – initial implementation
 *   Szalai, Gabor
 *
 ******************************************************************************/
#include "../common/memory.h"
#include "datatypes.h"
#include "enum.h"
#include "encdec.h"

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

#include "main.hh"
#include "ttcn3/compiler.h"

void defEnumClass(const enum_def *edef, output_struct *output)
{
  size_t i;
  char *def = NULL, *src = NULL;
  const char *name = edef->name, *dispname = edef->dispname;
  boolean ber_needed = edef->isASN1 && enable_ber();
  boolean raw_needed = edef->hasRaw && enable_raw();
  boolean text_needed= edef->hasText && enable_text();
  boolean xer_needed = edef->hasXer && enable_xer();
  boolean json_needed = edef->hasJson && enable_json();
  boolean oer_needed = edef->hasOer && enable_oer();

  char *enum_type, *qualified_enum_type, *unknown_value, *unbound_value;
  enum_type = mcopystr("enum_type");
  qualified_enum_type = mprintf("%s::enum_type", name);
  unknown_value = mcopystr("UNKNOWN_VALUE");
  unbound_value = mcopystr("UNBOUND_VALUE");

  /* Class declaration */
  output->header.class_decls = mputprintf(output->header.class_decls,
    "class %s;\n", name);

  /* Class definition */
  def = mputprintf(def,
#ifndef NDEBUG
      "// written by %s in " __FILE__ " at %d\n"
#endif
    "class %s : public %s { // enum\n"
    "friend class %s_template;\n"
#ifndef NDEBUG
      , __FUNCTION__, __LINE__
#endif
      , name, (use_runtime_2) ? "Enum_Type" : "Base_Type", name);
  def = mputstr(def, "public:\n"
    "enum enum_type { ");
  for (i = 0; i < edef->nElements; i++) {
    def = mputprintf(def, "%s = %d, ", edef->elements[i].name,
    edef->elements[i].value);
  }
  def = mputprintf(def, "UNKNOWN_VALUE = %d, UNBOUND_VALUE = %d };\n"
    "private:\n", edef->firstUnused, edef->secondUnused);
  def = mputprintf(def, "%s enum_value;\n\n"
    "public:\n", enum_type);

  /* constructors */
  def = mputprintf(def, "%s();\n", name);
  src = mputprintf(src, "%s::%s()\n"
    "{\n"
    "enum_value = %s;\n"
    "}\n\n", name, name, unbound_value);

  def = mputprintf(def, "%s(int other_value);\n", name);
  src = mputprintf(src,
    "%s::%s(int other_value)\n"
    "{\n"
    "if (!is_valid_enum(other_value)) "
    "TTCN_error(\"Initializing a variable of enumerated type %s with "
    "invalid numeric value %%d.\", other_value);\n"
    "enum_value = (%s)other_value;\n"
    "}\n\n",
    name, name, dispname, enum_type);

  def = mputprintf(def, "%s(%s other_value);\n", name, enum_type);
  src = mputprintf(src,
    "%s::%s(%s other_value)\n"
    "{\n"
    "enum_value = other_value;\n"
    "}\n\n", name, name, enum_type);

  def = mputprintf(def, "%s(const %s& other_value);\n\n", name, name);
  src = mputprintf
    (src,
     "%s::%s(const %s& other_value)\n"
     ": %s()\n" /* Base class DEFAULT constructor*/
     "{\n"
     "if (other_value.enum_value == %s) "
     "TTCN_error(\"Copying an unbound value of enumerated type %s.\");\n"
     "enum_value = other_value.enum_value;\n"
     "}\n\n", name, name, name, (use_runtime_2) ? "Enum_Type" : "Base_Type",
     unbound_value, dispname);

  /* assignment operators */
  def = mputprintf(def, "%s& operator=(int other_value);\n", name);
  src = mputprintf(src,
    "%s& %s::operator=(int other_value)\n"
    "{\n"
    "if (!is_valid_enum(other_value)) "
    "TTCN_error(\"Assigning unknown numeric value %%d to a variable "
    "of enumerated type %s.\", other_value);\n"
    "enum_value = (%s)other_value;\n"
    "return *this;\n"
    "}\n\n", name, name, dispname, enum_type);

  def = mputprintf(def, "%s& operator=(%s other_value);\n", name, enum_type);
  src = mputprintf(src,
    "%s& %s::operator=(%s other_value)\n"
    "{\n"
    "enum_value = other_value;\n"
    "return *this;\n"
    "}\n\n", name, name, enum_type);

  def = mputprintf(def, "%s& operator=(const %s& other_value);\n\n", name,
                   name);
  src = mputprintf(src,
    "%s& %s::operator=(const %s& other_value)\n"
    "{\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"Assignment of an unbound value of enumerated type %s.\");\n"
    "enum_value = other_value.enum_value;\n"
    "return *this;\n"
    "}\n\n", name, name, name, unbound_value, dispname);

  /* Comparison operators */
  def = mputprintf(def, "boolean operator==(%s other_value) const;\n",
                   enum_type);
  src = mputprintf(src,
    "boolean %s::operator==(%s other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value == other_value;\n"
    "}\n\n", name, enum_type, unbound_value, dispname);

  def = mputprintf(def, "boolean operator==(const %s& other_value) const;\n",
                   name);
  src = mputprintf(src,
    "boolean %s::operator==(const %s& other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"The right operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value == other_value.enum_value;\n"
    "}\n\n", name, name, unbound_value, dispname, unbound_value, dispname);

  def = mputprintf(def, "inline boolean operator!=(%s other_value) "
                   "const { return !(*this == other_value); }\n", enum_type);

  def = mputprintf(def, "inline boolean operator!=(const %s& other_value) "
                   "const { return !(*this == other_value); }\n", name);

  def = mputprintf(def, "boolean operator<(%s other_value) const;\n",
                   enum_type);
  src = mputprintf(src,
    "boolean %s::operator<(%s other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value < other_value;\n"
    "}\n\n", name, enum_type, unbound_value, dispname);

  def = mputprintf(def, "boolean operator<(const %s& other_value) const;\n",
                   name);
  src = mputprintf(src,
    "boolean %s::operator<(const %s& other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"The right operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value < other_value.enum_value;\n"
    "}\n\n", name, name, unbound_value, dispname, unbound_value, dispname);

  def = mputprintf(def, "boolean operator>(%s other_value) const;\n",
                   enum_type);
  src = mputprintf(src,
    "boolean %s::operator>(%s other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value > other_value;\n"
    "}\n\n", name, enum_type, unbound_value, dispname);

  def = mputprintf(def, "boolean operator>(const %s& other_value) const;\n",
                   name);
  src = mputprintf(src,
    "boolean %s::operator>(const %s& other_value) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"The left operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"The right operand of comparison is an unbound value of "
    "enumerated type %s.\");\n"
    "return enum_value > other_value.enum_value;\n"
    "}\n\n", name, name, unbound_value, dispname, unbound_value, dispname);

  def = mputprintf(def, "inline boolean operator<=(%s other_value) "
                   "const { return !(*this > other_value); }\n", enum_type);

  def = mputprintf(def, "inline boolean operator<=(const %s& other_value) "
                   "const { return !(*this > other_value); }\n", name);

  def = mputprintf(def, "inline boolean operator>=(%s other_value) "
                   "const { return !(*this < other_value); }\n", enum_type);

  def = mputprintf(def, "inline boolean operator>=(const %s& other_value) "
                   "const { return !(*this < other_value); }\n\n", name);

  /* Conversion function: enum_to_str */
  def = mputprintf(def, "static const char *enum_to_str(%s enum_par%s);\n",
    enum_type,
    edef->xerText ? ", boolean txt = FALSE" : "");
  src = mputprintf(src, "const char *%s::enum_to_str(%s enum_par%s)\n"
    "{\n"
    "switch (enum_par) {\n", name, enum_type,
    edef->xerText ? ", boolean txt" : "");
  for (i = 0; i < edef->nElements; i++) {
    if (edef->elements[i].text) {
      src = mputprintf(src,
        "case %s: if (txt) return \"%s\"; else return \"%s\";\n",
        edef->elements[i].name, edef->elements[i].text, edef->elements[i].dispname);
    }
    else {
      src = mputprintf(src, "case %s: return \"%s\";\n",
        edef->elements[i].name, edef->elements[i].dispname);
    }
  }
  src = mputstr(src, "default: return \"<unknown>\";\n"
    "}\n"
    "}\n\n");

  /* Conversion function: str_to_enum */
  def = mputprintf(def, "static %s str_to_enum(const char *str_par);\n",
    enum_type);
  src = mputprintf(src, "%s %s::str_to_enum(const char *str_par)\n"
    "{\n", qualified_enum_type, name);
  for (i = 0; i < edef->nElements; i++) {
    if (!edef->elements[i].text) {
      src = mputprintf(src, "if (!strcmp(str_par, \"%s\")) return %s;\n"
        "else ", edef->elements[i].dispname, edef->elements[i].name);
    } else if (!strcmp(edef->elements[i].text, edef->elements[i].descaped_text)) {
      src = mputprintf(src, "if (!strcmp(str_par, \"%s\") || !strcmp(str_par, \"%s\")) return %s;\n"
        "else ", edef->elements[i].text, edef->elements[i].dispname, edef->elements[i].name);
    } else {
      src = mputprintf(src, "if (!strcmp(str_par, \"%s\") || !strcmp(str_par, \"%s\") || !strcmp(str_par, \"%s\")) return %s;\n"
        "else ", edef->elements[i].text, edef->elements[i].descaped_text, edef->elements[i].dispname, edef->elements[i].name);
    }
  }
  src = mputprintf(src, "return %s;\n"
    "}\n\n", unknown_value);

  /* Checking function: is_valid_enum */
  def = mputstr(def, "static boolean is_valid_enum(int int_par);\n\n");
  src = mputprintf(src, "boolean %s::is_valid_enum(int int_par)\n"
    "{\n"
    "switch (int_par) {\n", name);
  for (i = 0; i < edef->nElements; i++) {
    src = mputprintf(src, "case %d:\n", edef->elements[i].value);
  }
  src = mputstr(src, "return TRUE;\n"
    "default:\n"
    "return FALSE;\n"
    "}\n"
    "}\n\n");

  /* TTCN-3 predefined function enum2int() */
  def = mputprintf(def, "static int enum2int(%s enum_par);\n", enum_type);
  src = mputprintf(src, "int %s::enum2int(%s enum_par)\n"
    "{\n"
    "if (enum_par==%s || enum_par==%s) TTCN_error(\"The argument of function "
      "enum2int() is an %%s value of enumerated type %s.\", "
      "enum_par==%s?\"unbound\":\"invalid\");\n"
    "return enum_par;\n"
    "}\n\n",
    name, enum_type, unbound_value, unknown_value, dispname, unbound_value);
  def = mputprintf(def, "static int enum2int(const %s& enum_par);\n", name);
  src = mputprintf(src, "int %s::enum2int(const %s& enum_par)\n"
    "{\n"
    "if (enum_par.enum_value==%s || enum_par.enum_value==%s) "
      "TTCN_error(\"The argument of function "
      "enum2int() is an %%s value of enumerated type %s.\", "
      "enum_par==%s?\"unbound\":\"invalid\");\n"
    "return enum_par.enum_value;\n"
    "}\n\n",
    name, name, unbound_value, unknown_value, dispname, unbound_value);
  def = mputstr(def,
    "int as_int() const { return enum2int(enum_value); }\n"
    "void from_int(int p_val) { *this = p_val; }\n");
  
  /* TTCN-3 predefined function int2enum() */
  def = mputstr(def, "void int2enum(int int_val);\n");
  src = mputprintf(src, "void %s::int2enum(int int_val)\n"
    "{\n"
    "if (!is_valid_enum(int_val)) "
    "TTCN_error(\"Assigning invalid numeric value %%d to a variable of "
    "enumerated type %s.\", int_val);\n"
    "enum_value = (%s)int_val;\n"
    "}\n\n", name, dispname, enum_type);

  /* miscellaneous members */
  def = mputprintf(def, "operator %s() const;\n", enum_type);
  src = mputprintf(src,
    "%s::operator %s() const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"Using the value of an unbound variable of enumerated "
    "type %s.\");\n"
    "return enum_value;\n"
    "}\n\n", name, qualified_enum_type, unbound_value, dispname);

  def = mputprintf(def, "inline boolean is_bound() const "
    "{ return enum_value != %s; }\n", unbound_value);
  def = mputprintf(def, "inline boolean is_value() const "
    "{ return enum_value != %s; }\n", unbound_value);
  def = mputprintf(def, "inline void clean_up()"
    "{ enum_value = %s; }\n", unbound_value);

  if (use_runtime_2) {
    def = mputstr(def,
      "boolean is_equal(const Base_Type* other_value) const;\n"
      "void set_value(const Base_Type* other_value);\n"
      "Base_Type* clone() const;\n"
      "const TTCN_Typedescriptor_t* get_descriptor() const;\n");
    src = mputprintf(src,
      "boolean %s::is_equal(const Base_Type* other_value) const "
        "{ return *this == *(static_cast<const %s*>(other_value)); }\n"
      "void %s::set_value(const Base_Type* other_value) "
        "{ *this = *(static_cast<const %s*>(other_value)); }\n"
      "Base_Type* %s::clone() const { return new %s(*this); }\n"
      "const TTCN_Typedescriptor_t* %s::get_descriptor() const "
        "{ return &%s_descr_; }\n",
      name, name,
      name, name,
      name, name,
      name, name);
  } else {
    def = mputstr(def,
      "inline boolean is_present() const { return is_bound(); }\n");
  }

  def = mputstr(def, "void log() const;\n");
  src = mputprintf(src,
    "void %s::log() const\n"
    "{\n"
    "if (enum_value != %s) TTCN_Logger::log_event_enum(enum_to_str(enum_value), enum_value);\n"
    "else TTCN_Logger::log_event_unbound();\n"
    "}\n\n", name, unbound_value);

  def = mputstr(def, "void set_param(Module_Param& param);\n");
  src = mputprintf
    (src,
     "void %s::set_param(Module_Param& param)\n"
     "{\n"
     "  param.basic_check(Module_Param::BC_VALUE, \"enumerated value\");\n", name);
  if (use_runtime_2) {
    src = mputprintf(src,
     "  Module_Param_Ptr m_p = &param;\n"
     "  if (param.get_type() == Module_Param::MP_Reference) {\n"
     /* enumerated values are also treated as references (containing only 1 name) by the parser;
        first check if the reference name is a valid enumerated value */
     "    char* enum_name = param.get_enumerated();\n"
     /* get_enumerated() returns NULL if the reference contained more than one name */
     "    enum_value = (enum_name != NULL) ? str_to_enum(enum_name) : %s;\n"
     "    if (is_valid_enum(enum_value)) {\n"
     "      return;\n"
     "    }\n"
     /* it's not a valid enum value => dereference it! */
     "    m_p = param.get_referenced_param();\n"
     "  }\n", unknown_value);
  }
  src = mputprintf(src,
     "  if (%sget_type()!=Module_Param::MP_Enumerated) param.type_error(\"enumerated value\", \"%s\");\n"
     "  enum_value = str_to_enum(%sget_enumerated());\n"
     "  if (!is_valid_enum(enum_value)) {\n"
     "    param.error(\"Invalid enumerated value for type %s.\");\n"
     "  }\n"
     "}\n\n", use_runtime_2 ? "m_p->" : "param.", dispname,
     use_runtime_2 ? "m_p->" : "param.", dispname);
  
  if (use_runtime_2) {
    def = mputstr(def, "Module_Param* get_param(Module_Param_Name& param_name) const;\n");
    src = mputprintf
      (src,
      "Module_Param* %s::get_param(Module_Param_Name& /* param_name */) const\n"
      "{\n"
      "  if (!is_bound()) {\n"
      "    return new Module_Param_Unbound();\n"
      "  }\n"
      "  return new Module_Param_Enumerated(mcopystr(enum_to_str(enum_value)));\n"
      "}\n\n", name);
  }

  /* encoders/decoders */
  def = mputstr(def, "void encode_text(Text_Buf& text_buf) const;\n");
  src = mputprintf(src,
    "void %s::encode_text(Text_Buf& text_buf) const\n"
    "{\n"
    "if (enum_value == %s) "
    "TTCN_error(\"Text encoder: Encoding an unbound value of enumerated "
    "type %s.\");\n"
    "text_buf.push_int(enum_value);\n"
    "}\n\n", name, unbound_value, dispname);

  def = mputstr(def, "void decode_text(Text_Buf& text_buf);\n");
  src = mputprintf(src,
    "void %s::decode_text(Text_Buf& text_buf)\n"
    "{\n"
    "enum_value = (%s)text_buf.pull_int().get_val();\n"
    "if (!is_valid_enum(enum_value)) "
    "TTCN_error(\"Text decoder: Unknown numeric value %%d was "
    "received for enumerated type %s.\", enum_value);\n"
    "}\n\n", name, enum_type, dispname);

  /* BER functions */
  if(ber_needed || raw_needed || text_needed || xer_needed || json_needed
    || oer_needed) {
    def_encdec(name, &def, &src, ber_needed,
               raw_needed,text_needed, xer_needed, json_needed, oer_needed, TRUE);
  }
  if(ber_needed) {
    src=mputprintf(src,
       "ASN_BER_TLV_t* %s::BER_encode_TLV(const TTCN_Typedescriptor_t&"
       " p_td, unsigned p_coding) const\n"
       "{\n"
       "  BER_chk_descr(p_td);\n"
       "  ASN_BER_TLV_t *new_tlv=BER_encode_chk_bound(is_bound());\n"
       "  if(!new_tlv) {\n"
       "    BER_encode_chk_enum_valid(p_td, is_valid_enum(enum_value),"
       " enum_value);\n"
       "    new_tlv=BER_encode_TLV_INTEGER(p_coding, enum_value);\n"
       "  }\n"
       "  new_tlv=ASN_BER_V2TLV(new_tlv, p_td, p_coding);\n"
       "  return new_tlv;\n"
       "}\n"
       "\n"
       "boolean %s::BER_decode_TLV(const TTCN_Typedescriptor_t& p_td,"
       " const ASN_BER_TLV_t& p_tlv, unsigned L_form)\n"
       "{\n"
       "  enum_value = %s;\n"
       "  BER_chk_descr(p_td);\n"
       "  ASN_BER_TLV_t stripped_tlv;\n"
       "  BER_decode_strip_tags(*p_td.ber, p_tlv, L_form, stripped_tlv);\n"
       "  TTCN_EncDec_ErrorContext ec(\"While decoding ENUMERATED type %s: "
	"\");\n"
       "  int tmp_mfr;\n"
       "  if (BER_decode_TLV_INTEGER(stripped_tlv, L_form, tmp_mfr)) {\n"
       "    BER_decode_chk_enum_valid(p_td, is_valid_enum(tmp_mfr), tmp_mfr);\n"
       "    enum_value = (%s)tmp_mfr;\n"
       "    return TRUE;\n"
       "  } else return FALSE;\n"
       "}\n\n"
       , name, name, unbound_value, dispname, enum_type);
  } /* if ber_needed */
  /* new TEXT functions */
  if(text_needed){
    src = mputprintf(src,
      "int %s::TEXT_encode(const TTCN_Typedescriptor_t& p_td,"
      "TTCN_Buffer& p_buf) const{\n"
      "int encoded_length=0;\n"
      "if(p_td.text->begin_encode){\n"
      "  p_buf.put_cs(*p_td.text->begin_encode);\n"
      "  encoded_length+=p_td.text->begin_encode->lengthof();\n"
      "}\n"
      "  if (enum_value == %s) {\n"
      "    TTCN_EncDec_ErrorContext::error\n"
      "      (TTCN_EncDec::ET_UNBOUND, \"Encoding an unbound value of "
      "enumerated type %s.\");\n"
      "    if(p_td.text->end_encode){\n"
      "      p_buf.put_cs(*p_td.text->end_encode);\n"
      "      encoded_length+=p_td.text->end_encode->lengthof();\n"
      "    }\n"
      "    return encoded_length;\n"
      "  }\n"
      "if(p_td.text->val.enum_values==NULL){\n"
      "  int len=strlen(enum_to_str(enum_value));\n"
      "  p_buf.put_s(len,(const unsigned char*)enum_to_str(enum_value));\n"
      "  encoded_length+=len;\n"
      "} else {\n"
      "switch(enum_value){\n"
      , name, unbound_value, dispname
      );
    for(i=0;i<edef->nElements;i++){
      src = mputprintf(src,
        "case %s: \n"
        "if(p_td.text->val.enum_values[%lu].encode_token){\n"
        " p_buf.put_cs(*p_td.text->val.enum_values[%lu].encode_token);\n"
        " encoded_length+=p_td.text->val.enum_values[%lu]"
        ".encode_token->lengthof();\n"
        "} else { "
        "  int len=strlen(enum_to_str(enum_value));\n"
        "  p_buf.put_s(len,(const unsigned char*)enum_to_str(enum_value));\n"
        "  encoded_length+=len;\n"
        "}\n"
        "break;\n"
         ,edef->elements[i].name,
         (unsigned long) i,(unsigned long) i,(unsigned long) i
       );
    }

    src = mputstr(src,
      " default:\n"
      "break;\n"
      "}\n"
      "}\n"
      " if(p_td.text->end_encode){\n"
      "   p_buf.put_cs(*p_td.text->end_encode);\n"
      "   encoded_length+=p_td.text->end_encode->lengthof();\n"
      " }\n"
      " return encoded_length;\n"
      "}\n"
      );
    src = mputprintf(src,
      "int %s::TEXT_decode(const TTCN_Typedescriptor_t& p_td,"
      " TTCN_Buffer& p_buf, Limit_Token_List&, boolean no_err, boolean){\n"
      "  int decoded_length=0;\n"
      "  int str_len=0;\n"
      "  if(p_td.text->begin_decode){\n"
      "    int tl;\n"
      "    if((tl=p_td.text->begin_decode->match_begin(p_buf))<0){\n"
      "          if(no_err)return -1;\n"
      "          TTCN_EncDec_ErrorContext::error\n"
      "              (TTCN_EncDec::ET_TOKEN_ERR, \"The specified token '%%s'"
      " not found for '%%s': \",(const char*)*(p_td.text->begin_decode)"
      ", p_td.name);\n"
      "          return 0;\n"
      "        }\n"
      "    decoded_length+=tl;\n"
      "    p_buf.increase_pos(tl);\n"
      "  }\n"
      "  if(p_buf.get_read_len()<1 && no_err) return -1;\n"
      ,name
     );


    for(i=0;i<edef->nElements;i++){
      src = mputprintf(src,
       "if((str_len=p_td.text->val.enum_values[%lu].decode_token->"
       "match_begin(p_buf))!=-1){\n"
       "  enum_value=%s;\n"
       "} else "
       ,(unsigned long) i,edef->elements[i].name
      );
    }

    src = mputstr(src,
      " {\n"
      "    if(no_err)return -1;\n"
      "    TTCN_EncDec_ErrorContext::error"
      "(TTCN_EncDec::ET_TOKEN_ERR, \"No enum token found for '%s': \""
      ",p_td.name);\n"
      "    return decoded_length;\n"
      "}\n"
      "  decoded_length+=str_len;\n"
      "  p_buf.increase_pos(str_len);\n"
      "  if(p_td.text->end_decode){\n"
      "    int tl;\n"
      "    if((tl=p_td.text->end_decode->match_begin(p_buf))<0){\n"
      "          if(no_err)return -1;\n"
      "          TTCN_EncDec_ErrorContext::error"
      "(TTCN_EncDec::ET_TOKEN_ERR, \"The specified token '%s'"
      " not found for '%s': \",(const char*)*(p_td.text->end_decode)"
      ",p_td.name);\n"
      "          return decoded_length;\n"
      "        }\n"
      "    decoded_length+=tl;\n"
      "    p_buf.increase_pos(tl);\n"
      "  }\n"
      "  return decoded_length;\n"
      "}\n"
     );
  }
  /* new RAW functions */
  if (raw_needed) {
    int min_bits = 0;
    int max_val = edef->firstUnused;
    size_t a;
    for (a = 0; a < edef->nElements; a++) {
      int val = edef->elements[a].value;
      if (abs(max_val) < abs(val)) max_val = val;
    }
    if (max_val < 0) {
      min_bits = 1;
      max_val = -max_val;
    }
    while (max_val) {
      min_bits++;
      max_val /= 2;
    }
    src = mputprintf(src,
      "int %s::RAW_decode(const TTCN_Typedescriptor_t& p_td,TTCN_Buffer& p_buf,"
      "int limit, raw_order_t top_bit_ord, boolean no_err, int, boolean, "
      "const RAW_Force_Omit*)\n"
      "{\n"
      "  int decoded_value = 0;\n"
      "  int decoded_length = RAW_decode_enum_type(p_td, p_buf, limit, "
      "top_bit_ord, decoded_value, %d, no_err);\n"
      "  if (decoded_length < 0) return decoded_length;\n"
      "  if (is_valid_enum(decoded_value)) "
      "enum_value = (%s)decoded_value;\n"
      "  else {\n"
      "    if(no_err){\n"
      "     return -1;\n"
      "    } else {\n"
      "    TTCN_EncDec_ErrorContext::error\n"
      "      (TTCN_EncDec::ET_ENC_ENUM, \"Invalid enum value '%%d'"
      " for '%%s': \",decoded_value, p_td.name);\n"
      "    enum_value = %s;\n"
      "    }\n"
      "  }\n"
      "  return decoded_length;\n"
      "}\n\n", name, min_bits, enum_type, unknown_value);
    src = mputprintf(src,
      "int %s::RAW_encode(const TTCN_Typedescriptor_t& p_td, "
      "RAW_enc_tree& myleaf) const\n"
      "{\n"
      "  return RAW_encode_enum_type(p_td, myleaf, (int)enum_value, %d);\n"
      "}\n\n", name, min_bits);
  } /* if raw_needed */

  if (xer_needed) { /* XERSTUFF encoder codegen for enum */
    /* FIXME This is redundant,
     * because the code is identical to BaseType::can_start()
     * and enum types are derived from BaseType or EnumType (which, in turn,
     * is derived from BaseType). However, the declaration of can_start()
     * is written by def_encdec() in encdec.c, which doesn't know
     * that this is an enum type. Maybe we need to pass an is_enum to
     * def_encdec, and then we can omit generating can_start() for enums.
     */
    src = mputprintf(src,
      "boolean %s::can_start(const char *name, const char *uri, "
      "const XERdescriptor_t& xd, unsigned int flavor, unsigned int) {\n"
      "  boolean exer = is_exer(flavor);\n"
      "  return check_name(name, xd, exer) && (!exer || check_namespace(uri, xd));\n"
      "}\n\n"
      , name
      );

    src = mputprintf(src,
      "int %s::XER_encode(const XERdescriptor_t& p_td, TTCN_Buffer& p_buf,"
      " unsigned int p_flavor, unsigned int, int p_indent, embed_values_enc_struct_t*) const\n"
      "{\n"
      "  int encoded_length=(int)p_buf.get_len();\n"
      "  const boolean e_xer = is_exer(p_flavor);\n"
      "  p_flavor |= (SIMPLE_TYPE | BXER_EMPTY_ELEM);\n"
      "  if (begin_xml(p_td, p_buf, p_flavor, p_indent, FALSE) == -1) "
      "--encoded_length;\n"
      "  if (!e_xer) p_buf.put_c('<');\n"
      , name
    );
    if (edef->xerUseNumber) {
      src = mputstr(src,
        "  if (e_xer) {\n" /* compile-time instead of p_td.useNumber */
        "    char sval[24];\n" /* unsigned 64 bits fit in 20 characters */
        "    int slen = snprintf(sval, 24, \"%d\", enum_value);\n"
        "    if (slen > 0) p_buf.put_s((size_t)slen, (const unsigned char*)sval);\n"
        "  }\n"
        "  else" /* no newline, will take over following curly */
      );
    }
    src = mputprintf(src,
      "  {\n"
      "    const char * enumval = enum_to_str(enum_value%s);\n"
      "    p_buf.put_s(strlen(enumval), (const unsigned char*)enumval);\n"
      "  }\n"
      "  if (!e_xer) p_buf.put_s(2, (const unsigned char*)\"/>\");\n"
      "  end_xml(p_td, p_buf, p_flavor, p_indent, FALSE);\n"
      , edef->xerText ? ", e_xer" : ""
    );
    src = mputstr(src,
      "  return (int)p_buf.get_len() - encoded_length;\n"
      "}\n\n");

    src = mputprintf(src, /* XERSTUFF decoder codegen for enum */
#ifndef NDEBUG
      "// written by %s in " __FILE__ " at %d\n"
#endif
      "int %s::XER_decode(const XERdescriptor_t& p_td, XmlReaderWrap& p_reader,"
      " unsigned int p_flavor,  unsigned int /*p_flavor2*/, embed_values_dec_struct_t*)\n"
      "{\n"
      "  int rd_ok = 1, type;\n"

      "  const boolean e_xer = is_exer(p_flavor);\n"
      "  const boolean name_tag = !((!e_xer && is_record_of(p_flavor)) || (e_xer && ((p_td.xer_bits & UNTAGGED) ||(is_record_of(p_flavor) && is_exerlist(p_flavor)))));\n"
      "  if (e_xer && ((p_td.xer_bits & XER_ATTRIBUTE) || is_exerlist(p_flavor))) {\n"
      "    if ((p_td.xer_bits & XER_ATTRIBUTE)) verify_name(p_reader, p_td, e_xer);\n"
      "    const char * value = (const char *)p_reader.Value();\n"
      "    if (value) {\n"
#ifndef NDEBUG
      , __FUNCTION__, __LINE__
#endif
      , name);
    if (edef->xerUseNumber) {
      src = mputprintf(src,
        "        int tempvalue;\n"
        "        sscanf(value, \"%%d\", &tempvalue);\n"
        "        if (is_valid_enum(tempvalue)) enum_value = (%s)tempvalue;\n" /* static_cast would be nice */
        "        else enum_value = %s;\n", enum_type, unknown_value
      );
    }
    else {
      src = mputstr(src, "        enum_value = str_to_enum(value);\n");
    }
    src = mputstr(src,
      "    }\n"
      /* The caller should do AdvanceAttribute() */
      "  }\n"
      "  else {\n"
      "    if (name_tag)"
      /* Go past the opening tag with the type name */
      "      for (; rd_ok == 1; rd_ok = p_reader.Read()) {\n"
      "        type = p_reader.NodeType();\n"
      "        if (XML_READER_TYPE_ELEMENT == type) {\n"
      "          rd_ok = p_reader.Read();\n"
      "          break;\n"
      "        }\n"
      "      }\n"
      /* Go to the element with the actual data (EmptyElementEnumerated) */
      "    for (; rd_ok == 1; rd_ok = p_reader.Read()) {\n"
      "      type = p_reader.NodeType();\n"
      "      if (!e_xer && XML_READER_TYPE_ELEMENT == type) break;\n"
      "      if (XML_READER_TYPE_TEXT == type) break;\n"
      "    }\n"
      "    const char *local_name = e_xer ? (const char *)p_reader.Value() : (const char *)p_reader.Name();\n"
      /*                                       TextEnumerated                EmptyElementEnumerated */
      "    if (!local_name) ; else");
    if (edef->xerUseNumber) {
      src = mputprintf(src,
        "    if (e_xer) {\n"
        "      int tempvalue;\n"
        "      sscanf(local_name, \"%%d\", &tempvalue);\n"
        "      if (is_valid_enum(tempvalue)) enum_value = (%s)tempvalue;\n" /* static_cast would be nice */
        "      else enum_value = %s;\n"
        "    }\n"
        "    else" /* no newline */ , enum_type, unknown_value
        );
    }
    {
      src = mputstr(src,
        "    {\n"
        "      for (; '\\t'==*local_name || '\\n'==*local_name; ++local_name) ;\n" /* crutch while default-for-empty always puts in a newline */
        "      enum_value = str_to_enum(local_name);\n"
        "    }\n");
    }
    src = mputprintf(src,
      "    if (name_tag)\n"
      "      for (rd_ok = p_reader.Read(); rd_ok == 1; rd_ok = p_reader.Read()) {\n"
      "        type = p_reader.NodeType();\n"
      "        if (XML_READER_TYPE_END_ELEMENT == type) {\n"
      "          p_reader.Read();\n"
      "          break;\n"
      "        }\n"
      "      }\n"
      "    else p_reader.Read();\n"
      "  }\n"
      "  if (e_xer && (p_flavor & EXIT_ON_ERROR) && %s == enum_value) clean_up();\n" // set to unbound if decoding failed
      "  int decoded_length = 0;\n"
      "  return decoded_length;\n"
      "}\n\n"
      , unknown_value);
  }
  if (json_needed) {
    // JSON encode
    src = mputprintf(src,
      "int %s::JSON_encode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenizer& p_tok, boolean) const\n"
      "{\n"
      "  if (enum_value == %s) {\n"
      "    TTCN_EncDec_ErrorContext::error(TTCN_EncDec::ET_UNBOUND,\n"
      "      \"Encoding an unbound value of enumerated type %s.\");\n"
      "    return -1;\n"
      "  }\n\n"
      "  if (p_td.json->use_null) {\n"
      "    return p_tok.put_next_token(JSON_TOKEN_LITERAL_NULL);\n"
      "  }\n"
      "  char* tmp_str;\n"
      "  if (p_td.json->as_number) {\n"
      "    tmp_str = mprintf(\"%%d\", enum_value);\n"
      "  }\n"
      "  else {\n"
      "    boolean text_found = false;\n"
      "    for (size_t i = 0; i < p_td.json->nof_enum_texts; ++i) {\n"
      "      if (p_td.json->enum_texts[i].index == enum_value) {\n"
      "        tmp_str = mprintf(\"\\\"%%s\\\"\", p_td.json->enum_texts[i].text);\n"
      "        text_found = true;\n"
      "        break;\n"
      "      }\n"
      "    }\n"
      "    if (!text_found) {\n"
      "      tmp_str = mprintf(\"\\\"%%s\\\"\", enum_to_str(enum_value));\n"
      "    }\n"
      "  }\n"
      "  int enc_len = p_tok.put_next_token(p_td.json->as_number ? "
      "JSON_TOKEN_NUMBER : JSON_TOKEN_STRING, tmp_str);\n"
      "  Free(tmp_str);\n"
      "  return enc_len;\n"
      "}\n\n"
      , name, unbound_value, dispname);
    
    // JSON decode
    src = mputprintf(src,
      "int %s::JSON_decode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenizer& p_tok, boolean p_silent, boolean, int)\n"
      "{\n"
      "  json_token_t token = JSON_TOKEN_NONE;\n"
      "  char* value = 0;\n"
      "  size_t value_len = 0;\n"
      "  boolean error = FALSE;\n"
      "  size_t dec_len = 0;\n"
      "  boolean use_default = FALSE;\n"
      "  if (p_td.json->default_value.type == JD_STANDARD && 0 == p_tok.get_buffer_length()) {\n"
      "    *this = *static_cast<const %s*>(p_td.json->default_value.val);\n"
      "    return dec_len;\n"
      "  }\n"
      "  if (p_td.json->default_value.type == JD_LEGACY && 0 == p_tok.get_buffer_length()) {\n"
      // No JSON data in the buffer -> use default value
      "    value = const_cast<char*>(p_td.json->default_value.str);\n"
      "    value_len = strlen(value);\n"
      "    use_default = TRUE;\n"
      "  } else {\n"
      "    dec_len = p_tok.get_next_token(&token, &value, &value_len);\n"
      "  }\n"
      "  if (JSON_TOKEN_ERROR == token) {\n"
      "    JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, \"\");\n"
      "    return JSON_ERROR_FATAL;\n"
      "  }\n"
      "  else if (JSON_TOKEN_LITERAL_NULL == token && p_td.json->use_null) {\n"
      "    enum_value = %s;\n"
      "  }\n"
      "  else if (!p_td.json->use_null && ((JSON_TOKEN_STRING == token && !p_td.json->as_number) || use_default)) {\n"
      "    if (use_default || (value_len > 2 && value[0] == '\\\"' && value[value_len - 1] == '\\\"')) {\n"
      "      if (!use_default) value[value_len - 1] = 0;\n"
      "      boolean text_found = false;\n"
      "      for (size_t i = 0; i < p_td.json->nof_enum_texts; ++i) {\n"
      "        if (strcmp(p_td.json->enum_texts[i].text, value + (use_default ? 0 : 1)) == 0) {\n"
      "          enum_value = static_cast<%s>(p_td.json->enum_texts[i].index);\n"
      "          text_found = true;\n"
      "          break;\n"
      "        }\n"
      "      }\n"
      "      if (!text_found) {\n"
      "        enum_value = str_to_enum(value + (use_default ? 0 : 1));\n"
      "      }\n"
      "      if (!use_default) value[value_len - 1] = '\\\"';\n"
      "      if (%s == enum_value) {\n"
      "        error = TRUE;\n"
      "      }\n"
      "    } else {\n"
      "      error = TRUE;\n"
      "    }\n"
      "  }\n"
      "  else if (!p_td.json->use_null && JSON_TOKEN_NUMBER == token && p_td.json->as_number) {\n"
      "    char* value_str = mcopystrn(value, value_len);\n"
      "    int number = atoi(value_str);\n"
      "    if (strchr(value_str, '.') != NULL || strchr(value_str, 'e') != NULL "
      "|| strchr(value_str, 'E') != NULL) {\n"
      "      error = TRUE;\n"
      "    }\n"
      "    else if (is_valid_enum(number)) {\n"
      "      enum_value = static_cast<%s>(number);\n"
      "    }\n"
      "    else {\n"
      "      error = TRUE;\n"
      "    }\n"
      "    Free(value_str);\n"
      "  }\n"
      "  else {\n"
      "    enum_value = %s;\n"
      "    return JSON_ERROR_INVALID_TOKEN;\n"
      "  }\n\n"
      "  if (error) {\n"
      "    JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_FORMAT_ERROR, "
      "p_td.json->as_number ? \"number\" : \"string\", \"enumerated\");\n"
      "    enum_value = %s;\n"
      "    return JSON_ERROR_FATAL;\n"
      "  }\n"
      "  return (int)dec_len;\n"
      "}\n\n"
      , name, name, edef->elements[0].name, enum_type, unknown_value, enum_type
      , unbound_value, unbound_value);
  }
  
  if (oer_needed) {
    // OER encode
    src = mputprintf(src,
      "int %s::OER_encode(const TTCN_Typedescriptor_t&, TTCN_Buffer& p_buf) const\n"
      "{\n"
      "  if (enum_value == %s) {\n"
      "    TTCN_EncDec_ErrorContext::error(TTCN_EncDec::ET_UNBOUND,\n"
      "      \"Encoding an unbound value of enumerated type %s.\");\n"
      "    return -1;\n"
      "  }\n\n"
      "  if (enum_value >= 0 && enum_value < 128) {\n"
      "    char c = enum_value;\n"
      "    p_buf.put_c(c);\n"
      "  } else {\n"
      // This case is the same as encoding a not restricted integer except
      // the first bit is set to 1 before decoding
      "    INTEGER intval(enum_value);\n"
      "    TTCN_Buffer buf;\n"
      "    intval.OER_encode(INTEGER_descr_, buf);\n"
      "    unsigned char* uc = const_cast<unsigned char*>(buf.get_data());\n"
      "    *uc |= 1 << 7;\n"
      "    p_buf.put_buf(buf);\n"
      "  }\n"
      "  return 0;\n"
      "}\n\n"
      , name, unbound_value, dispname);
    
    // OER decode
    src = mputprintf(src,
      "int %s::OER_decode(const TTCN_Typedescriptor_t&, TTCN_Buffer& p_buf, OER_struct& p_oer)\n"
      "{\n"
      "  const unsigned char* uc = p_buf.get_read_data();\n"
      "  if (!(uc[0] & 0x80)) {\n"
      "    if (is_valid_enum(uc[0])) {\n"
      "      enum_value = static_cast<%s>(uc[0]);\n"
      "    } else {\n"
      "      enum_value = %s;\n"
      "    }\n"
      "    p_buf.increase_pos(1);\n"
      "  } else {\n"
      // This case is the same as decoding a not restricted integer except
      // the first bit is set to 0
      "    unsigned char* uc2 = const_cast<unsigned char*>(p_buf.get_read_data());\n"
      "    uc2[0] &= ~0x80;\n"
      "    INTEGER intval;\n"
      "    intval.OER_decode(INTEGER_descr_, p_buf, p_oer);\n"
      "    if (is_valid_enum(intval.get_val().get_val())) {\n"
      "      enum_value = static_cast<%s>(intval.get_val().get_val());\n"
      "    } else {\n"
      "      enum_value = %s;\n"
      "    }\n"
      "  }\n"
      "  return 0;\n"
      "}\n\n"
      , name, enum_type, unknown_value, enum_type, unknown_value);
  }
  /* end of class */
  def = mputstr(def, "};\n\n");

  output->header.class_defs = mputstr(output->header.class_defs, def);
  Free(def);
  output->source.methods = mputstr(output->source.methods, src);
  Free(src);


  Free(enum_type);
  Free(qualified_enum_type);
  Free(unknown_value);
  Free(unbound_value);
}

void defEnumTemplate(const enum_def *edef, output_struct *output)
{
  char *def = NULL, *src = NULL;
  const char *name = edef->name, *dispname = edef->dispname;

  char *enum_type, *unbound_value, *unknown_value;
  enum_type = mprintf("%s::enum_type", name);
  unbound_value = mprintf("%s::UNBOUND_VALUE", name);
  unknown_value = mprintf("%s::UNKNOWN_VALUE", name);

  /* Class declaration */
  output->header.class_decls = mputprintf(output->header.class_decls,
    "class %s_template;\n", name);

  /* Class definition */
  def = mputprintf(def, "class %s_template : public Base_Template {\n"
    "union {\n"
    "%s single_value;\n"
    "struct {\n"
    "unsigned int n_values;\n"
    "%s_template *list_value;\n"
    "} value_list;\n"
    "};\n\n", name, enum_type, name);

  /* private members */
  def = mputprintf(def, "void copy_template(const %s_template& "
                   "other_value);\n", name);
  src = mputprintf(src,
    "void %s_template::copy_template(const %s_template& "
    "other_value)\n"
    "{\n"
    "set_selection(other_value);\n"
    "switch (template_selection) {\n"
    "case SPECIFIC_VALUE:\n"
    "single_value = other_value.single_value;\n"
    "break;\n"
    "case OMIT_VALUE:\n"
    "case ANY_VALUE:\n"
    "case ANY_OR_OMIT:\n"
    "break;\n"
    "case VALUE_LIST:\n"
    "case COMPLEMENTED_LIST:\n"
    "value_list.n_values = other_value.value_list.n_values;\n"
    "value_list.list_value = new %s_template[value_list.n_values];\n"
    "for (unsigned int list_count = 0; list_count < value_list.n_values; "
    "list_count++)\n"
    "value_list.list_value[list_count].copy_template("
    "other_value.value_list.list_value[list_count]);\n"
    "break;\n"
    "default:\n"
    "TTCN_error(\"Copying an uninitialized/unsupported template of "
    "enumerated type %s.\");\n"
    "}\n"
    "}\n\n", name, name, name, dispname);

  def = mputstr(def, "\npublic:\n");

  /* constructors */
  def = mputprintf(def, "%s_template();\n", name);
  src = mputprintf(src,
    "%s_template::%s_template()\n"
    "{\n"
    "}\n\n", name, name);

  def = mputprintf(def, "%s_template(template_sel other_value);\n", name);
  src = mputprintf(src,
    "%s_template::%s_template(template_sel other_value)\n"
    " : Base_Template(other_value)\n"
    "{\n"
    "check_single_selection(other_value);\n"
    "}\n\n", name, name);

  def = mputprintf(def, "%s_template(int other_value);\n", name);
  src = mputprintf(src,
    "%s_template::%s_template(int other_value)\n"
    " : Base_Template(SPECIFIC_VALUE)\n"
    "{\n"
    "if (!%s::is_valid_enum(other_value)) "
    "TTCN_error(\"Initializing a template of enumerated type %s with "
    "unknown numeric value %%d.\", other_value);\n"
    "single_value = (%s)other_value;\n"
    "}\n\n", name, name, name, dispname, enum_type);

  def = mputprintf(def, "%s_template(%s other_value);\n", name, enum_type);
  src = mputprintf(src,
    "%s_template::%s_template(%s other_value)\n"
    " : Base_Template(SPECIFIC_VALUE)\n"
    "{\n"
    "single_value = other_value;\n"
    "}\n\n", name, name, enum_type);

  def = mputprintf(def, "%s_template(const %s& other_value);\n", name, name);
  src = mputprintf(src,
    "%s_template::%s_template(const %s& other_value)\n"
    " : Base_Template(SPECIFIC_VALUE)\n"
    "{\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"Creating a template from an unbound value of enumerated "
    "type %s.\");\n"
    "single_value = other_value.enum_value;\n"
    "}\n\n", name, name, name, unbound_value, dispname);

  def = mputprintf(def, "%s_template(const OPTIONAL<%s>& other_value);\n",
                   name, name);
  src = mputprintf(src,
    "%s_template::%s_template(const OPTIONAL<%s>& other_value)\n"
    "{\n"
    "switch (other_value.get_selection()) {\n"
    "case OPTIONAL_PRESENT:\n"
    "set_selection(SPECIFIC_VALUE);\n"
    "single_value = (%s)(const %s&)other_value;\n"
    "break;\n"
    "case OPTIONAL_OMIT:\n"
    "set_selection(OMIT_VALUE);\n"
    "break;\n"
    "default:\n"
    "TTCN_error(\"Creating a template of enumerated type %s from an unbound "
      "optional field.\");\n"
    "}\n"
    "}\n\n", name, name, name, enum_type, name, dispname);

  def = mputprintf(def, "%s_template(const %s_template& other_value);\n",
                   name, name);
  src = mputprintf(src,
    "%s_template::%s_template(const %s_template& other_value)\n"
    " : Base_Template()\n"
    "{\n"
    "copy_template(other_value);\n"
    "}\n\n", name, name, name);

  /* destructor */
  def = mputprintf(def, "~%s_template();\n\n", name);
  src = mputprintf(src,
    "%s_template::~%s_template()\n"
    "{\n"
    "clean_up();\n"
    "}\n\n", name, name);

      /* is_bound */
    def = mputstr(def, "boolean is_bound() const;\n");
    src = mputprintf(src, "boolean %s_template::is_bound() const\n"
        "{\n"
        "if (template_selection == UNINITIALIZED_TEMPLATE && !is_ifpresent) "
        "return FALSE;\n", name);
    src = mputstr(src, "return TRUE;\n"
        "}\n\n");

    /* is_value */
    def = mputstr(def, "boolean is_value() const;\n");
    src = mputprintf(src, "boolean %s_template::is_value() const\n"
        "{\n"
        "if (template_selection != SPECIFIC_VALUE || is_ifpresent) "
        "return FALSE;\n"
        "return single_value != %s;\n"
        "}\n\n", name, unbound_value);

  /* clean_up */
  def = mputstr(def, "void clean_up();\n");
  src = mputprintf(src,
    "void %s_template::clean_up()\n"
    "{\n"
    "if (template_selection == VALUE_LIST || "
    "template_selection == COMPLEMENTED_LIST) "
    "delete [] value_list.list_value;\n"
    "template_selection = UNINITIALIZED_TEMPLATE;\n"
    "}\n\n", name);

  /* assignment operators */
  def = mputprintf(def, "%s_template& operator=(template_sel other_value);\n",
                   name);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(template_sel other_value)\n"
    "{\n"
    "check_single_selection(other_value);\n"
    "clean_up();\n"
    "set_selection(other_value);\n"
    "return *this;\n"
    "}\n\n", name, name);

  def = mputprintf(def, "%s_template& operator=(int other_value);\n",
                   name);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(int other_value)\n"
    "{\n"
    "if (!%s::is_valid_enum(other_value)) "
    "TTCN_warning(\"Assigning unknown numeric value %%d to a template "
    "of enumerated type %s.\", other_value);\n"
    "clean_up();\n"
    "set_selection(SPECIFIC_VALUE);\n"
    "single_value = (%s)other_value;\n"
    "return *this;\n"
    "}\n\n", name, name, name, dispname, enum_type);

  def = mputprintf(def, "%s_template& operator=(%s other_value);\n",
                   name, enum_type);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(%s other_value)\n"
    "{\n"
    "clean_up();\n"
    "set_selection(SPECIFIC_VALUE);\n"
    "single_value = other_value;\n"
    "return *this;\n"
    "}\n\n", name, name, enum_type);

  def = mputprintf(def, "%s_template& operator=(const %s& other_value);\n",
                   name, name);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(const %s& other_value)\n"
    "{\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"Assignment of an unbound value of enumerated type %s to a "
    "template.\");\n"
    "clean_up();\n"
    "set_selection(SPECIFIC_VALUE);\n"
    "single_value = other_value.enum_value;\n"
    "return *this;\n"
    "}\n\n", name, name, name, unbound_value, dispname);

  def = mputprintf(def, "%s_template& operator=(const OPTIONAL<%s>& "
                   "other_value);\n", name, name);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(const OPTIONAL<%s>& other_value)\n"
    "{\n"
    "clean_up();\n"
    "switch (other_value.get_selection()) {\n"
    "case OPTIONAL_PRESENT:\n"
    "set_selection(SPECIFIC_VALUE);\n"
    "single_value = (%s)(const %s&)other_value;\n"
    "break;\n"
    "case OPTIONAL_OMIT:\n"
    "set_selection(OMIT_VALUE);\n"
    "break;\n"
    "default:\n"
    "TTCN_error(\"Assignment of an unbound optional field to a template of "
      "enumerated type %s.\");\n"
    "}\n"
    "return *this;\n"
    "}\n\n", name, name, name, enum_type, name, dispname);

  def = mputprintf(def, "%s_template& operator=(const %s_template& "
                   "other_value);\n\n", name, name);
  src = mputprintf(src,
    "%s_template& %s_template::operator=(const %s_template& other_value)\n"
    "{\n"
    "if (&other_value != this) {\n"
    "clean_up();\n"
    "copy_template(other_value);\n"
    "}\n"
    "return *this;\n"
    "}\n\n", name, name, name);

  /* match operators */
  def = mputprintf(def, "boolean match(%s other_value, boolean legacy = FALSE) "
    "const;\n", enum_type);
  src = mputprintf(src,
    "boolean %s_template::match(%s other_value, boolean) const\n"
    "{\n"
    "switch (template_selection) {\n"
    "case SPECIFIC_VALUE:\n"
    "return single_value == other_value;\n"
    "case OMIT_VALUE:\n"
    "return FALSE;\n"
    "case ANY_VALUE:\n"
    "case ANY_OR_OMIT:\n"
    "return TRUE;\n"
    "case VALUE_LIST:\n"
    "case COMPLEMENTED_LIST:\n"
    "for (unsigned int list_count = 0; list_count < value_list.n_values; "
    "list_count++)\n"
    "if (value_list.list_value[list_count].match(other_value)) "
    "return template_selection == VALUE_LIST;\n"
    "return template_selection == COMPLEMENTED_LIST;\n"
    "default:\n"
    "TTCN_error(\"Matching an uninitialized/unsupported template of "
    "enumerated type %s.\");\n"
    "}\n"
    "return FALSE;\n"
    "}\n\n", name, enum_type, dispname);

  def = mputprintf(def, "boolean match(const %s& other_value, boolean legacy "
    "= FALSE) const;\n", name);
  src = mputprintf(src,
    "boolean %s_template::match(const %s& other_value, boolean) const\n"
    "{\n"
    "if (other_value.enum_value == %s) "
    "TTCN_error(\"Matching a template of enumerated type %s with an unbound "
    "value.\");\n"
    "return match(other_value.enum_value);\n"
    "}\n\n", name, name, unbound_value, dispname);

  /* valueof operator */
  def = mputprintf(def, "%s valueof() const;\n", enum_type);
  src = mputprintf(src,
    "%s %s_template::valueof() const\n"
    "{\n"
    "if (template_selection != SPECIFIC_VALUE || is_ifpresent) "
    "TTCN_error(\"Performing a valueof or send operation on a "
    "non-specific template of enumerated type %s.\");\n"
    "return single_value;\n"
    "}\n\n", enum_type, name, dispname);

  /* value list handling operators */
  def = mputstr
    (def,
     "void set_type(template_sel template_type, unsigned int list_length);\n");
  src = mputprintf(src,
    "void %s_template::set_type(template_sel template_type, "
    "unsigned int list_length)\n"
    "{\n"
    "if (template_type != VALUE_LIST && template_type != COMPLEMENTED_LIST) "
    "TTCN_error(\"Setting an invalid list type for a template of enumerated "
    "type %s.\");\n"
    "clean_up();\n"
    "set_selection(template_type);\n"
    "value_list.n_values = list_length;\n"
    "value_list.list_value = new %s_template[list_length];\n"
    "}\n\n", name, dispname, name);

  def = mputprintf(def, "%s_template& list_item(unsigned int list_index);\n",
    name);
  src = mputprintf(src,
    "%s_template& %s_template::list_item(unsigned int list_index)\n"
    "{\n"
    "if (template_selection != VALUE_LIST && "
    "template_selection != COMPLEMENTED_LIST) "
    "TTCN_error(\"Accessing a list element in a non-list template of "
    "enumerated type %s.\");\n"
    "if (list_index >= value_list.n_values) "
    "TTCN_error(\"Index overflow in a value list template of enumerated type "
    "%s.\");\n"
    "return value_list.list_value[list_index];\n"
    "}\n\n", name, name, dispname, dispname);

  if (use_runtime_2) {
    /** virtual stuff */
    def = mputstr(def,
      "void valueofv(Base_Type* value) const;\n"
      "void set_value(template_sel other_value);\n"
      "void copy_value(const Base_Type* other_value);\n"
      "Base_Template* clone() const;\n"
      "const TTCN_Typedescriptor_t* get_descriptor() const;\n"
      "boolean matchv(const Base_Type* other_value, boolean legacy) const;\n"
      "void log_matchv(const Base_Type* match_value, boolean legacy) const;\n");
    src = mputprintf(src,
      "void %s_template::valueofv(Base_Type* value) const "
        "{ *(static_cast<%s*>(value)) = valueof(); }\n"
      "void %s_template::set_value(template_sel other_value) "
        "{ *this = other_value; }\n"
      "void %s_template::copy_value(const Base_Type* other_value) "
        "{ *this = *(static_cast<const %s*>(other_value)); }\n"
      "Base_Template* %s_template::clone() const "
        "{ return new %s_template(*this); }\n"
      "const TTCN_Typedescriptor_t* %s_template::get_descriptor() const "
        "{ return &%s_descr_; }\n"
      "boolean %s_template::matchv(const Base_Type* other_value, "
        "boolean legacy) const "
        "{ return match(*(static_cast<const %s*>(other_value)), legacy); }\n"
      "void %s_template::log_matchv(const Base_Type* match_value, "
        "boolean legacy) const "
        " { log_match(*(static_cast<const %s*>(match_value)), legacy); }\n",
      name, name,
      name,
      name, name,
      name, name,
      name, name,
      name, name,
      name, name);
  }

  /* logging functions */
  def = mputstr(def, "void log() const;\n");
  src = mputprintf
    (src,
     "void %s_template::log() const\n"
     "{\n"
     "switch (template_selection) {\n"
     "case SPECIFIC_VALUE:\n"
     "TTCN_Logger::log_event_enum(%s::enum_to_str(single_value), single_value);\n"
     "break;\n"
     "case COMPLEMENTED_LIST:\n"
     "TTCN_Logger::log_event_str(\"complement\");\n"
     "case VALUE_LIST:\n"
     "TTCN_Logger::log_char('(');\n"
     "for (unsigned int elem_count = 0; elem_count < value_list.n_values; "
     "elem_count++) {\n"
     "if (elem_count > 0) TTCN_Logger::log_event_str(\", \");\n"
     "value_list.list_value[elem_count].log();\n"
     "}\n"
     "TTCN_Logger::log_char(')');\n"
     "break;\n"
     "default:\n"
     "log_generic();\n"
     "}\n"
     "log_ifpresent();\n"
     "}\n\n", name, name);

  def = mputprintf(def, "void log_match(const %s& match_value, "
    "boolean legacy = FALSE) const;\n", name);
  src = mputprintf(src,
    "void %s_template::log_match(const %s& match_value, boolean) const\n"
    "{\n"
    "match_value.log();\n"
    "TTCN_Logger::log_event_str(\" with \");\n"
    "log();\n"
    "if (match(match_value)) TTCN_Logger::log_event_str(\" matched\");\n"
    "else TTCN_Logger::log_event_str(\" unmatched\");\n"
    "}\n\n", name, name);

  /* encoding/decoding functions */
  def = mputstr(def, "void encode_text(Text_Buf& text_buf) const;\n");
  src = mputprintf(src,
    "void %s_template::encode_text(Text_Buf& text_buf) "
    "const\n"
    "{\n"
    "encode_text_base(text_buf);\n"
    "switch (template_selection) {\n"
    "case SPECIFIC_VALUE:\n"
    "text_buf.push_int(single_value);\n"
    "case OMIT_VALUE:\n"
    "case ANY_VALUE:\n"
    "case ANY_OR_OMIT:\n"
    "break;\n"
    "case VALUE_LIST:\n"
    "case COMPLEMENTED_LIST:\n"
    "text_buf.push_int(value_list.n_values);\n"
    "for (unsigned int elem_count = 0; elem_count < value_list.n_values; "
    "elem_count++)\n"
    "value_list.list_value[elem_count].encode_text(text_buf);\n"
    "break;\n"
    "default:\n"
    "TTCN_error(\"Text encoder: Encoding an uninitialized/unsupported "
    "template of enumerated type %s.\");\n"
    "}\n"
    "}\n\n", name, dispname);

  def = mputstr(def, "void decode_text(Text_Buf& text_buf);\n");
  src = mputprintf(src,
    "void %s_template::decode_text(Text_Buf& text_buf)\n"
    "{\n"
    "clean_up();\n"
    "decode_text_base(text_buf);\n"
    "switch (template_selection) {\n"
    "case SPECIFIC_VALUE:\n"
    "single_value = (%s)text_buf.pull_int().get_val();\n"
    "if (!%s::is_valid_enum(single_value)) "
    "TTCN_error(\"Text decoder: Unknown numeric value %%d was "
    "received for a template of enumerated type %s.\", single_value);\n"
    "case OMIT_VALUE:\n"
    "case ANY_VALUE:\n"
    "case ANY_OR_OMIT:\n"
    "break;\n"
    "case VALUE_LIST:\n"
    "case COMPLEMENTED_LIST:\n"
    "value_list.n_values = text_buf.pull_int().get_val();\n"
    "value_list.list_value = new %s_template[value_list.n_values];\n"
    "for (unsigned int elem_count = 0; elem_count < value_list.n_values; "
    "elem_count++)\n"
    "value_list.list_value[elem_count].decode_text(text_buf);\n"
    "break;\n"
    "default:\n"
    "TTCN_error(\"Text decoder: An unknown/unsupported selection was "
    "received for a template of enumerated type %s.\");\n"
    "}\n"
    "}\n\n", name, enum_type, name, dispname, name, dispname);

  /* TTCN-3 ispresent() function */
  def = mputstr(def, "boolean is_present(boolean legacy = FALSE) const;\n");
  src = mputprintf(src,
    "boolean %s_template::is_present(boolean legacy) const\n"
    "{\n"
    "if (template_selection==UNINITIALIZED_TEMPLATE) return FALSE;\n"
    "return !match_omit(legacy);\n"
    "}\n\n", name);

  /* match_omit() */
  def = mputstr(def, "boolean match_omit(boolean legacy = FALSE) const;\n");
  src = mputprintf(src,
    "boolean %s_template::match_omit(boolean legacy) const\n"
    "{\n"
    "if (is_ifpresent) return TRUE;\n"
    "switch (template_selection) {\n"
    "case OMIT_VALUE:\n"
    "case ANY_OR_OMIT:\n"
    "return TRUE;\n"
    "case VALUE_LIST:\n"
    "case COMPLEMENTED_LIST:\n"
    "if (legacy) {\n"
    "for (unsigned int i=0; i<value_list.n_values; i++)\n"
    "if (value_list.list_value[i].match_omit())\n"
    "return template_selection==VALUE_LIST;\n"
    "return template_selection==COMPLEMENTED_LIST;\n"
    "} // else fall through\n"
    "default:\n"
    "return FALSE;\n"
    "}\n"
    "return FALSE;\n"
    "}\n\n", name);

  /* set_param() */
  def = mputstr(def, "void set_param(Module_Param& param);\n");
  src = mputprintf(src,
    "void %s_template::set_param(Module_Param& param)\n"
    "{\n"
    "  param.basic_check(Module_Param::BC_TEMPLATE, \"enumerated template\");\n"
    "  Module_Param_Ptr m_p = &param;\n", name);
  if (use_runtime_2) {
    src = mputprintf(src,
    "  if (param.get_type() == Module_Param::MP_Reference) {\n"
    /* enumerated values are also treated as references (containing only 1 name) by the parser;
       first check if the reference name is a valid enumerated value */
    "    char* enum_name = param.get_enumerated();\n"
    /* get_enumerated() returns NULL if the reference contained more than one name */
    "    %s enum_val = (enum_name != NULL) ? %s::str_to_enum(enum_name) : %s;\n"
    "    if (%s::is_valid_enum(enum_val)) {\n"
    "      *this = enum_val;\n"
    "      is_ifpresent = param.get_ifpresent() || m_p->get_ifpresent();\n"
    "      return;\n"
    "    }\n"
    /* it's not a valid enum value => dereference it! */
    "    m_p = param.get_referenced_param();\n"
    "  }\n", enum_type, name, unknown_value, name);
  }
  src = mputprintf(src,
    "  switch (m_p->get_type()) {\n"
    "  case Module_Param::MP_Omit:\n"
    "    *this = OMIT_VALUE;\n"
    "    break;\n"
    "  case Module_Param::MP_Any:\n"
    "    *this = ANY_VALUE;\n"
    "    break;\n"
    "  case Module_Param::MP_AnyOrNone:\n"
    "    *this = ANY_OR_OMIT;\n"
    "    break;\n"
    "  case Module_Param::MP_List_Template:\n"
    "  case Module_Param::MP_ComplementList_Template: {\n"
    "    %s_template new_temp;\n"
    "    new_temp.set_type(m_p->get_type()==Module_Param::MP_List_Template ? "
    "VALUE_LIST : COMPLEMENTED_LIST, m_p->get_size());\n"
    "    for (size_t p_i=0; p_i<m_p->get_size(); p_i++) {\n"
    "      new_temp.list_item(p_i).set_param(*m_p->get_elem(p_i));\n"
    "    }\n"
    "    *this = new_temp;\n"
    "    break; }\n"
    "  case Module_Param::MP_Enumerated: {\n"
    "    %s enum_val = %s::str_to_enum(m_p->get_enumerated());\n"
    "    if (!%s::is_valid_enum(enum_val)) {\n"
    "      param.error(\"Invalid enumerated value for type %s.\");\n"
    "    }\n"
    "    *this = enum_val;\n"
    "  } break;\n"
    "  default:\n"
    "    param.type_error(\"enumerated template\", \"%s\");\n"
    "  }\n"
    "  is_ifpresent = param.get_ifpresent()%s;\n"
    "}\n\n", name, enum_type, name, name, dispname, dispname,
    use_runtime_2 ? " || m_p->get_ifpresent()" : "");

  /* get_param(), RT2 only */
  if (use_runtime_2) {
    def = mputstr(def, "Module_Param* get_param(Module_Param_Name& param_name) const;\n");
    src = mputprintf
      (src,
      "Module_Param* %s_template::get_param(Module_Param_Name& param_name) const\n"
      "{\n"
      "  Module_Param* m_p = NULL;\n"
      "  switch (template_selection) {\n"
      "  case UNINITIALIZED_TEMPLATE:\n"
      "    m_p = new Module_Param_Unbound();\n"
      "    break;\n"
      "  case OMIT_VALUE:\n"
      "    m_p = new Module_Param_Omit();\n"
      "    break;\n"
      "  case ANY_VALUE:\n"
      "    m_p = new Module_Param_Any();\n"
      "    break;\n"
      "  case ANY_OR_OMIT:\n"
      "    m_p = new Module_Param_AnyOrNone();\n"
      "    break;\n"
      "  case SPECIFIC_VALUE:\n"
      "    m_p = new Module_Param_Enumerated(mcopystr(%s::enum_to_str(single_value)));\n"
      "    break;\n"
      "  case VALUE_LIST:\n"
      "  case COMPLEMENTED_LIST: {\n"
      "    if (template_selection == VALUE_LIST) {\n"
      "      m_p = new Module_Param_List_Template();\n"
      "    }\n"
      "    else {\n"
      "      m_p = new Module_Param_ComplementList_Template();\n"
      "    }\n"
      "    for (size_t i_i = 0; i_i < value_list.n_values; ++i_i) {\n"
      "      m_p->add_elem(value_list.list_value[i_i].get_param(param_name));\n"
      "    }\n"
      "    break; }\n"
      "  default:\n"
      "    break;\n"
      "  }\n"
      "  if (is_ifpresent) {\n"
      "    m_p->set_ifpresent();\n"
      "  }\n"
      "  return m_p;\n"
      "}\n\n", name, name);
  }

  if (!use_runtime_2) {
    /* check template restriction */
    def = mputstr(def, "void check_restriction(template_res t_res, "
      "const char* t_name=NULL, boolean legacy = FALSE) const;\n");
    src = mputprintf(src,
      "void %s_template::check_restriction(template_res t_res, const char* t_name,\n"
      "boolean legacy) const\n"
      "{\n"
      "if (template_selection==UNINITIALIZED_TEMPLATE) return;\n"
      "switch ((t_name&&(t_res==TR_VALUE))?TR_OMIT:t_res) {\n"
      "case TR_VALUE:\n"
      "if (!is_ifpresent && template_selection==SPECIFIC_VALUE) return;\n"
      "break;\n"
      "case TR_OMIT:\n"
      "if (!is_ifpresent && (template_selection==OMIT_VALUE || "
        "template_selection==SPECIFIC_VALUE)) return;\n"
      "break;\n"
      "case TR_PRESENT:\n"
      "if (!match_omit(legacy)) return;\n"
      "break;\n"
      "default:\n"
      "return;\n"
      "}\n"
      "TTCN_error(\"Restriction `%%s' on template of type %%s violated.\", "
        "get_res_name(t_res), t_name ? t_name : \"%s\");\n"
      "}\n\n", name, dispname);
  }

  /* end of class */
  def = mputstr(def, "};\n\n");

  output->header.class_defs = mputstr(output->header.class_defs, def);
  Free(def);
  output->source.methods = mputstr(output->source.methods, src);
  Free(src);

  Free(enum_type);
  Free(unbound_value);
  Free(unknown_value);
}
