/******************************************************************************
 * Copyright (c) 2000-2025 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:
 *   Balasko, Jeno
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Bence Janos
 *
 ******************************************************************************/
#include "EnumItem.hh"
#include "Value.hh"
#include "Int.hh"
#include <stdlib.h>

namespace Common {

// =================================
// ===== EnumItemIntValues
// =================================

EnumItemIntValues::EnumItemIntValues(size_t p_nof_values)
: nof_values(p_nof_values)
{
  if (nof_values == 0) {
    FATAL_ERROR("EnumItemIntValues::EnumItemIntValues()");
  }
  values = new int[nof_values];
  intervals = new boolean[nof_values];
}

EnumItemIntValues::EnumItemIntValues(const EnumItemIntValues& p_other)
: nof_values(p_other.nof_values)
{
  values = new int[nof_values];
  intervals = new boolean[nof_values];
  for (size_t i = 0; i < nof_values; ++i) {
    values[i] = p_other.values[i];
    intervals[i] = p_other.intervals[i];
  }
}

EnumItemIntValues::~EnumItemIntValues()
{
  delete[] values;
  delete[] intervals;
}

void EnumItemIntValues::set_value(size_t p_idx, int p_value, bool starts_interval)
{
  if (p_idx >= nof_values) {
    FATAL_ERROR("EnumItemIntValues::set_value()");
  }
  values[p_idx] = p_value;
  intervals[p_idx] = starts_interval;
}

bool EnumItemIntValues::has_value(int p_value) const
{
  bool in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    if (values[i] == p_value) {
      return true;
    }
    if (in_interval && p_value >= values[i - 1] && p_value <= values[i]) {
      return true;
    }
    in_interval = intervals[i];
  }
  return false;
}

bool EnumItemIntValues::intersects(const EnumItemIntValues& p_other) const
{
  bool in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    bool in_interval_o = false;
    for (size_t j = 0; j < p_other.nof_values; ++j) {
      if (values[i] == p_other.values[j]) {
        return true;
      }
      if (in_interval && p_other.values[j] >= values[i - 1] && p_other.values[j] <= values[i]) {
        return true;
      }
      if (in_interval_o && values[i] >= p_other.values[j - 1] && values[i] <= p_other.values[j]) {
        return true;
      }
      if (in_interval && in_interval_o && 
          values[i - 1] <= p_other.values[j] && values[i] >= p_other.values[j - 1]) {
        return true;
      }
      in_interval_o = p_other.intervals[j];
    }
    in_interval = intervals[i];
  }
  return false;
}

bool EnumItemIntValues::contains(const EnumItemIntValues& p_other) const
{
  bool in_interval_o = false;
  for (size_t j = 0; j < p_other.nof_values; ++j) {
    bool in_interval = false;
    bool found = false;
    for (size_t i = 0; i < nof_values; ++i) {
      if (!in_interval_o || in_interval) {
        if (!in_interval_o && p_other.values[j] == values[i]) {
          found = true;
        }
        else if (in_interval && !in_interval_o &&
            p_other.values[j] >= values[i - 1] && p_other.values[j] <= values[i]) {
          found = true;
        }
        else if (in_interval && in_interval_o && 
            p_other.values[j - 1] >= values[i - 1] && p_other.values[j] <= values[i]) {
          found = true;
        }
      }
      in_interval = intervals[i];
    }
    if (!found) {
      return false;
    }
    in_interval_o = p_other.intervals[j];
  }
  return true;
}

bool EnumItemIntValues::next_unused(Int& p_value) const
{
  bool in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    if (values[i] == p_value) {
      ++p_value;
      return true;
    }
    if (in_interval && p_value >= values[i - 1] && p_value <= values[i]) {
      p_value = values[i] + 1;
      return true;
    }
    in_interval = intervals[i];
  }
  return false;
}

void EnumItemIntValues::finalize()
{
  bool* unused = new bool[nof_values];
  bool in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    if (in_interval && values[i] == values[i - 1]) {
      // convert a value range with only one element into a single value
      // e.g.: (1..1) -> 1
      intervals[i - 1] = false;
      unused[i] = true;
    }
    else {
      unused[i] = false;
    }
    in_interval = intervals[i];
  }
  in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    bool in_interval2 = false;
    if (!intervals[i]) {
      for (size_t j = 0; j < nof_values; ++j) {
        if (i != j && !unused[j]) {
          if (!in_interval2 && !intervals[j] && !in_interval &&
              (values[i] == values[j] - 1 || values[i] == values[j] + 1)) {
            // merge two single values into a value range
            // e.g.: (1, 2) -> (1..2)
            // converting two single values into a value range without reallocating anything is a bit tricky
            values[i] = (values[j] > values[i]) ? values[i] : values[j]; // determine the lower bound from positions i and j
            // shift everything between positions i+1 and j to the right by one (i is always less than j);
            // this will overwrite position j, which is fine, its value is not needed anymore;
            // the resulting value range will be at positions i and i+1
            for (size_t k = j; k > i + 1; --k) {
              values[k] = values[k - 1];
              intervals[k] = intervals[k - 1];
            }
            values[i + 1] = values[i] + 1; // calculate the upper bound
            intervals[i] = TRUE;
            intervals[i + 1] = FALSE;
            break;
          }
          else if (in_interval2) {
            if (in_interval && values[i - 1] <= values[j] + 1 && values[i] >= values[j - 1] - 1) {
              // merge two value ranges if they intersect, or if they are near each other
              // e.g.: (2..5, 4..7) -> (2..7); (8..10, 4..7) -> (4..10)
              values[j - 1] = (values[j - 1] > values[i - 1]) ? values[i - 1] : values[j - 1];
              values[j] = (values[j] > values[i]) ? values[j] : values[i];
              unused[i - 1] = true;
              unused[i] = true;
              break;
            }
            if (!in_interval && values[i] >= values[j - 1] - 1 && values[i] <= values[j] + 1) {
              // merge single value with value range
              if (values[i] == values[j - 1] - 1) { // (3, 4..7) -> (3..7)
                --values[j - 1];
              }
              else if (values[i] == values[j] + 1) { // (8, 4..7) -> (4..8)
                ++values[j];
              }
              unused[i] = true;
              break;
            }
          }
        }
        in_interval2 = intervals[j];
      }
    }
    in_interval = intervals[i];
  }
  size_t pos = 0;
  for (size_t i = 0; i < nof_values; ++i) {
    if (!unused[i]) {
      if (pos != i) {
        values[pos] = values[i];
        intervals[pos] = intervals[i];
      }
      ++pos;
    }
  }
  delete[] unused;
  nof_values = pos;
}

bool EnumItemIntValues::chk_this_value_internal(Value* p_val, const char* p_val_descr) const
{
  p_val = p_val->get_value_refd_last();
  if (p_val->is_unfoldable()) {
    p_val->error("Numeric value associated with an enumerated item is not known at compile-time");
    return false;
  }
  if (p_val->get_expr_returntype(Type::EXPECTED_CONSTANT) != Type::T_INT) {
    p_val->error("Integer value was expected");
    return false;
  }
  int_val_t* enum_val_int = p_val->get_val_Int();
  if (*enum_val_int > INT_MAX ||
      static_cast<Int>(static_cast<int>(enum_val_int->get_val())) != enum_val_int->get_val()) {
    p_val->error("%s (%s) associated with an enumerated item is too large to be represented in memory",
      p_val_descr, enum_val_int->t_str().c_str());
    return false;
  }
  return true;
}

bool EnumItemIntValues::chk_this_value(Value* p_val, const string& p_ei_name) const
{
  if (!chk_this_value_internal(p_val, "The numeric value")) {
    return false;
  }
  int_val_t* enum_val_int = p_val->get_val_Int();
  if (!has_value(enum_val_int->get_val())) {
    p_val->error("Numeric value %s is not in the list of values specified for enumerated item `%s'",
      enum_val_int->t_str().c_str(), p_ei_name.c_str());
    return false;
  }
  return true;
}

bool EnumItemIntValues::chk_this_value_range(Value* p_min, Value* p_max,
                                             const string& p_ei_name, Location* p_loc) const
{
  if (p_min == NULL) {
    p_loc->error("The lower bound in a value range associated with an enumerated item cannot be -infinity");
    return false;
  }
  if (p_max == NULL) {
    p_loc->error("The upper bound in a value range associated with an enumerated item cannot be infinity");
    return false;
  }
  if (!chk_this_value_internal(p_min, "The lower bound in a value range") ||
      !chk_this_value_internal(p_max, "The upper bound in a value range")) {
    return false;
  }
  int min_int = p_min->get_val_Int()->get_val();
  int max_int = p_max->get_val_Int()->get_val();
  bool in_interval = false;
  for (size_t i = 0; i < nof_values; ++i) {
    if ((in_interval && min_int >= values[i - 1] && max_int <= values[i]) ||
        (!in_interval && min_int == max_int && min_int == values[i])) {
      return true;
    }
    in_interval = intervals[i];
  }
  p_loc->error("Value range (%d .. %d) is not in the list of values specified for enumerated item `%s'",
    min_int, max_int, p_ei_name.c_str());
  return false;
}

// =================================
// ===== EnumItem
// =================================

EnumItem::EnumItem(Identifier *p_name, Value *p_value)
: Node(), Location(), name(p_name), values(NULL), value(p_value),
  int_single_value(NULL), int_values(NULL), ext_add(false), int_value_calculated(false)
{
  if (!p_name) FATAL_ERROR("NULL parameter: Common::EnumItem::EnumItem()");
}

EnumItem::EnumItem(Identifier *p_name, vector<SubTypeParse>* p_values)
: Node(), Location(), name(p_name), values(NULL), value(NULL),
  int_single_value(NULL), int_values(NULL), ext_add(false), int_value_calculated(false)
{
  if (!p_name) FATAL_ERROR("NULL parameter: Common::EnumItem::EnumItem()");
  if (p_values != NULL) {
    if (p_values->size() == 1 && (*p_values)[0]->get_selection() == SubTypeParse::STP_SINGLE) {
      value = (*p_values)[0]->StealSingle();
      delete (*p_values)[0];
      p_values->clear();
      delete p_values;
    }
    else {
      values = p_values;
    }
  }
}

EnumItem::EnumItem(const EnumItem& p)
: Node(p), Location(p)
{
  name=p.name->clone();
  if (p.values != NULL) {
    values = new vector<SubTypeParse>;
    for (size_t i = 0; i < p.values->size(); i++) {
      SubTypeParse *stp = 0;
      switch((*p.values)[i]->get_selection()) {
      case SubTypeParse::STP_SINGLE:
        stp = new SubTypeParse((*p.values)[i]->Single());
        break;
      case SubTypeParse::STP_RANGE:
        stp = new SubTypeParse((*p.values)[i]->Min(),
          (*p.values)[i]->MinExclusive(),
          (*p.values)[i]->Max(),
          (*p.values)[i]->MaxExclusive());
        break;
      case SubTypeParse::STP_LENGTH:
        FATAL_ERROR("EnumItem::EnumItem(EnumItem&): STP_LENGTH");
        break;
      default:
        FATAL_ERROR("EnumItem::EnumItem()");
        break;
      }
      values->add(stp);
    }
  }
  else {
    values = NULL;
  }
  value = (p.value != NULL) ? p.value->clone() : NULL;
  int_value_calculated = false;
  if (p.int_single_value != NULL) {
    int_single_value = new Int(*p.int_single_value);
    int_value_calculated = true;
  } else {
    int_single_value = NULL;
  }
  if (p.int_values != NULL) {
    int_values = new EnumItemIntValues(*p.int_values);
    int_value_calculated = true;
  } else {
    int_values = NULL;
  }
  ext_add = p.ext_add;
}

EnumItem::~EnumItem()
{
  delete name;
  if (values != NULL) {
    for (size_t i = 0; i < values->size(); i++) {
      delete (*values)[i];
    }
    values->clear();
    delete values;
  }
  delete value;
  delete int_single_value;
  delete int_values;
}

EnumItem *EnumItem::clone() const
{
  return new EnumItem(*this);
}

void EnumItem::set_fullname(const string& p_fullname)
{
  Node::set_fullname(p_fullname);
  if(value) value->set_fullname(p_fullname);
}

void EnumItem::set_my_scope(Scope *p_scope)
{
  if(value) value->set_my_scope(p_scope);
}

void EnumItem::set_single_value(Value *p_value)
{
  if(!p_value) FATAL_ERROR("NULL parameter: Common::EnumItem::set_value()");
  if (value != NULL || values != NULL) {
    FATAL_ERROR("Common::EnumItem::set_value()");
  }
  value=p_value;
  delete int_single_value;
  int_single_value = NULL;
  int_value_calculated = false;
  calculate_int_value();
}

void EnumItem::set_text(const string& p_text)
{
  text = p_text;
  
  // De escape special XML characters if any found, into descaped_text
  descaped_text = p_text;
  size_t pos = 0;
  while ((pos = descaped_text.find("&apos;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 6, "'");
  }
  while ((pos = descaped_text.find("&quot;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 6, "\\\"");
  }
  while ((pos = descaped_text.find("&lt;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 4, "<");
  }
  while ((pos = descaped_text.find("&gt;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 4, ">");
  }
  while ((pos = descaped_text.find("&amp;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, "&");
  }
  while ((pos = descaped_text.find("&#60;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, "<");
  }
  while ((pos = descaped_text.find("&#62;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, ">");
  }
  while ((pos = descaped_text.find("&#34;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, "\\\"");
  }
  while ((pos = descaped_text.find("&#38;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, "&");
    pos++;
  }
  while ((pos = descaped_text.find("&#39;", 0)) != descaped_text.size()) {
    descaped_text.replace(pos, 5, "'");
  }
}

bool EnumItem::calculate_int_value_internal(Value* p_value, bool p_is_range_limit, Int& p_int_res)
{
  Value *v = NULL;
  p_value->set_lowerid_to_ref();
  if (p_value->get_valuetype() == Value::V_REFD || p_value->get_valuetype() == Value::V_EXPR) {
    v = p_value->get_value_refd_last();
  } else {
    v = p_value;
  }
  
  if (v->is_unfoldable()) {
    p_value->error("A value known at compile time was expected for enumeration `%s'",
      name->get_dispname().c_str());
    return false;
  }
  
  switch (v->get_valuetype()) {
    case Value::V_INT: {
      int_val_t* int_val_ptr = v->get_val_Int();
      if (*int_val_ptr > INT_MAX ||
          static_cast<Int>(static_cast<int>(int_val_ptr->get_val())) != int_val_ptr->get_val()) {
        p_value->error("The numeric value of enumeration `%s' (%s) is "
          "too large for being represented in memory",
          name->get_dispname().c_str(), (int_val_ptr->t_str()).c_str());
        return false;
      }
      p_int_res = int_val_ptr->get_val();
      break; }
    case Value::V_BSTR:
      if (!p_is_range_limit) {
        p_int_res = strtol(v->get_val_str().c_str(), NULL, 2);
        break;
      }
      // else fall through
    case Value::V_OSTR:
    case Value::V_HSTR:
      if (!p_is_range_limit) {
        p_int_res = strtol(v->get_val_str().c_str(), NULL, 16);
        break;
      }
      // else fall through
    default:
      p_value->error("INTEGER%s value was expected for enumeration `%s'",
        p_is_range_limit ? "" : " or BITSTRING or OCTETSTRING or HEXSTRING", name->get_dispname().c_str());
      return false;
  }
  return true;
}

bool EnumItem::calculate_int_value()
{
  if (int_value_calculated) {
    return int_values != NULL;
  }
  int_value_calculated = true;
  if (value != NULL) {
    Int int_res;
    if (!calculate_int_value_internal(value, false, int_res)) {
      return false;
    }
    int_single_value = new Int(int_res);
    int_values = new EnumItemIntValues(1);
    int_values->set_value(0, int_res, false);
    return true;
  }
  else if (values != NULL) {
    size_t nof_values = 0;
    for (size_t i = 0; i < values->size(); ++i) {
      switch((*values)[i]->get_selection()) {
      case SubTypeParse::STP_SINGLE:
        ++nof_values;
        break;
      case SubTypeParse::STP_RANGE:
        nof_values += 2;
        break;
      case SubTypeParse::STP_LENGTH:
      default:
        FATAL_ERROR("EnumItem::calculate_int_value()");
        break;
      }
    }
    int_values = new EnumItemIntValues(nof_values);
    size_t idx = 0;
    Int int_res1;
    Int int_res2;
    bool ret_val = true;
    for (size_t i = 0; i < values->size(); ++i) {
      SubTypeParse* stp = (*values)[i];
      switch(stp->get_selection()) {
      case SubTypeParse::STP_SINGLE:
        if (calculate_int_value_internal(stp->Single(), false, int_res1)) {
          int_values->set_value(idx++, int_res1, false);
        }
        else {
          ret_val = false;
        }
        break;
      case SubTypeParse::STP_RANGE:
        if (stp->Min() == NULL) {
          error("-infinity is not a valid lower boundary "
            "in the numeric value range of enumeration `%s'",
            name->get_dispname().c_str());
          ret_val = false;
        }
        else if (!calculate_int_value_internal(stp->Min(), true, int_res1)) {
          ret_val = false;
        }
        if (stp->Max() == NULL) {
          error("infinity is not a valid upper boundary "
            "in the numeric value range of enumeration `%s'",
            name->get_dispname().c_str());
          ret_val = false;
        }
        else if (!calculate_int_value_internal(stp->Max(), true, int_res2)) {
          ret_val = false;
        }
        if (ret_val) {
          if (stp->MinExclusive()) {
            ++int_res1;
          }
          if (stp->MaxExclusive()) {
            --int_res2;
          }
          if (int_res1 > int_res2) {
            stp->Min()->error("The lower boundary is greater than the upper boundary "
              "in the numeric value range of enumeration `%s'",
              name->get_dispname().c_str());
            ret_val = false;
          }
          else {
            int_values->set_value(idx++, int_res1, true);
            int_values->set_value(idx++, int_res2, false);
          }
        }
        break;
      case SubTypeParse::STP_LENGTH:
      default:
        FATAL_ERROR("EnumItem::calculate_int_value()");
        break;
      }
    }
    if (!ret_val) {
      delete int_values;
      int_values = NULL;
      return false;
    }
    int_values->finalize();
    return true;
  }
  return false;
}

void EnumItem::dump(unsigned level) const
{
  name->dump(level);
  if(value) {
    DEBUG(level, "with value:");
    value->dump(level+1);
  }
  else if (values != NULL) {
    DEBUG(level, "with values:");
    for (size_t i = 0; i < values->size(); ++i) {
      SubTypeParse* stp = (*values)[i];
      switch(stp->get_selection()) {
      case SubTypeParse::STP_SINGLE:
        DEBUG(level + 1, "single value:");
        stp->Single()->dump(level + 1);
        break;
      case SubTypeParse::STP_RANGE:
        if (stp->Min() != NULL) {
          DEBUG(level + 1, "lower bound:");
          stp->Min()->dump(level + 1);
        }
        else {
          DEBUG(level + 1, "no lower bound");
        }
        if (stp->Max() != NULL) {
          DEBUG(level + 1, "upper bound:");
          stp->Max()->dump(level + 1);
        }
        else {
          DEBUG(level + 1, "no upper bound");
        }
        break;
      default:
        FATAL_ERROR("EnumItem::dump()");
        break;
      }
    }
  }
}

// =================================
// ===== EnumItems
// =================================

EnumItems::EnumItems(const EnumItems& p)
: Node(p), my_scope(0)
{
  for (size_t i = 0; i < p.eis_v.size(); i++) add_ei(p.eis_v[i]->clone());
}

EnumItems::~EnumItems()
{
  for(size_t i = 0; i < eis_v.size(); i++) delete eis_v[i];
  eis_v.clear();
  eis_m.clear();
}

void EnumItems::release_eis()
{
  eis_v.clear();
  eis_m.clear();
}

EnumItems* EnumItems::clone() const
{
  return new EnumItems(*this);
}

void EnumItems::set_fullname(const string& p_fullname)
{
  Node::set_fullname(p_fullname);
  for (size_t i = 0; i < eis_v.size(); i++) {
    EnumItem *ei = eis_v[i];
    ei->set_fullname(p_fullname + "." + ei->get_name().get_dispname());
  }
}

void EnumItems::set_my_scope(Scope *p_scope)
{
  my_scope = p_scope;
  for(size_t i = 0; i < eis_v.size(); i++)
    eis_v[i]->set_my_scope(p_scope);
}

void EnumItems::add_ei(EnumItem *p_ei)
{
  if(!p_ei)
    FATAL_ERROR("NULL parameter: Common::EnumItems::add_ei()");
  eis_v.add(p_ei);
  const Identifier& id = p_ei->get_name();
  const string& name = id.get_name();
  if (!eis_m.has_key(name)) eis_m.add(name, p_ei);
  p_ei->set_fullname(get_fullname()+"."+id.get_dispname());
  p_ei->set_my_scope(my_scope);
}

bool EnumItems::has_ei_with_multiple_values() const
{
  for (size_t i = 0; i < eis_v.size(); i++) {
    if (eis_v[i]->get_single_value() == NULL &&
        eis_v[i]->get_values() != NULL) {
      return true;
    }
  }
  return false;
}

void EnumItems::dump(unsigned level) const
{
  for (size_t i = 0; i < eis_v.size(); i++) eis_v[i]->dump(level);
}

}
