/******************************************************************************
 * 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:
 *   Balasko, Jeno
 *   Raduly, Csaba
 *
 ******************************************************************************/
#include "SigParam.hh"

#include "Type.hh"
#include "CompilerError.hh"
#include "AST.hh"

namespace Common {

// =================================
// ===== SignatureParam
// =================================

SignatureParam::SignatureParam(param_direction_t p_d, Type *p_t,
                               Identifier *p_i)
  : param_direction(p_d), param_type(p_t), param_id(p_i)
{
  if (!p_t || !p_i) FATAL_ERROR("SignatureParam::SignatureParam()");
  param_type->set_ownertype(Type::OT_SIG_PAR, this);
}

SignatureParam::~SignatureParam()
{
  delete param_type;
  delete param_id;
}

SignatureParam *SignatureParam::clone() const
{
  FATAL_ERROR("SignatureParam::clone");
}

void SignatureParam::set_fullname(const string& p_fullname)
{
  Node::set_fullname(p_fullname);
  param_type->set_fullname(p_fullname);
}

void SignatureParam::set_my_scope(Scope *p_scope)
{
  param_type->set_my_scope(p_scope);
}

void SignatureParam::dump(unsigned level) const
{
  switch(param_direction) {
  case PARAM_IN: DEBUG(level,"in"); break;
  case PARAM_OUT: DEBUG(level,"out"); break;
  case PARAM_INOUT: DEBUG(level,"inout");break;
  default: FATAL_ERROR("SignatureParam::dump()"); break;
  }
  param_type->dump(level+1);
  param_id->dump(level+2);
}

// =================================
// ===== SignatureParamList
// =================================

SignatureParamList::~SignatureParamList()
{
  for (size_t i = 0; i < params_v.size(); i++) delete params_v[i];
  params_v.clear();
  params_m.clear();
  in_params_v.clear();
  out_params_v.clear();
}

SignatureParamList *SignatureParamList::clone() const
{
  FATAL_ERROR("SignatureParam::clone");
}

void SignatureParamList::set_fullname(const string& p_fullname)
{
  Node::set_fullname(p_fullname);
  for (size_t i = 0; i < params_v.size(); i++) {
    SignatureParam *param = params_v[i];
    param->set_fullname(p_fullname + "." + param->get_id().get_dispname());
  }
}

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

void SignatureParamList::add_param(SignatureParam *p_param)
{
  if (!p_param || checked) FATAL_ERROR("SignatureParamList::add_param()");
  params_v.add(p_param);
}

size_t SignatureParamList::get_nof_in_params() const
{
  if (!checked) FATAL_ERROR("SignatureParamList::get_nof_in_params()");
  return in_params_v.size();
}

SignatureParam *SignatureParamList::get_in_param_byIndex(size_t n) const
{
  if (!checked) FATAL_ERROR("SignatureParamList::get_in_param_byIndex()");
  return in_params_v[n];
}

size_t SignatureParamList::get_nof_out_params() const
{
  if (!checked) FATAL_ERROR("SignatureParamList::get_nof_out_params()");
  return out_params_v.size();
}

SignatureParam *SignatureParamList::get_out_param_byIndex(size_t n) const
{
  if (!checked) FATAL_ERROR("SignatureParamList::get_out_param_byIndex()");
  return out_params_v[n];
}

bool SignatureParamList::has_param_withName(const Identifier& p_name) const
{
  if (!checked) FATAL_ERROR("SignatureParamList::has_param_withName()");
  return params_m.has_key(p_name.get_name());
}

const SignatureParam *SignatureParamList::get_param_byName
  (const Identifier& p_name) const
{
  if (!checked) FATAL_ERROR("SignatureParamList::get_param_byName()");
  return params_m[p_name.get_name()];
}

void SignatureParamList::chk(Type *p_signature)
{
  if (checked) return;
  checked = true;
  for (size_t i = 0; i < params_v.size(); i++) {
    SignatureParam *param = params_v[i];
    const Identifier& id = param->get_id();
    const string& name = id.get_name();
    const char *dispname_str = id.get_dispname().c_str();
    if (params_m.has_key(name)) {
      param->error("Duplicate parameter identifier: `%s'", dispname_str);
      params_m[name]->note("Parameter `%s' is already defined here",
        dispname_str);
    } else params_m.add(name, param);
    Error_Context cntxt(param, "In parameter `%s'", dispname_str);
    bool is_nonblock = p_signature->is_nonblocking_signature();
    switch (param->get_direction()) {
    case SignatureParam::PARAM_IN:
      in_params_v.add(param);
      break;
    case SignatureParam::PARAM_OUT:
      if (is_nonblock) param->error("A non-blocking signature cannot have "
        "`out' parameter");
      out_params_v.add(param);
      break;
    case SignatureParam::PARAM_INOUT:
      if (is_nonblock) param->error("A non-blocking signature cannot have "
        "`inout' parameter");
      in_params_v.add(param);
      out_params_v.add(param);
      break;
    default:
      FATAL_ERROR("SignatureParamList::chk()");
    }
    Type *param_type = param->get_type();
    param_type->set_genname(p_signature->get_genname_own(), name);
    param_type->set_parent_type(p_signature);
    param_type->chk();
    param_type->chk_embedded(false, "the type of a signature parameter");
  }
}

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

// =================================
// ===== SignatureExceptions
// =================================

SignatureExceptions::~SignatureExceptions()
{
  for (size_t i = 0; i < exc_v.size(); i++) delete exc_v[i];
  exc_v.clear();
  exc_m.clear();
}

SignatureExceptions *SignatureExceptions::clone() const
{
  FATAL_ERROR("SignatureExceptions::clone");
}

void SignatureExceptions::add_type(Type *p_type)
{
  if (!p_type) FATAL_ERROR("SignatureExceptions::add_type()");
  exc_v.add(p_type);
}

bool SignatureExceptions::has_type(Type *p_type)
{
  if (!p_type) FATAL_ERROR("SignatureExceptions::has_type()");
  if (p_type->get_type_refd_last()->get_typetype() == Type::T_ERROR)
    return true;
  else return exc_m.has_key(p_type->get_typename());
}

size_t SignatureExceptions::get_nof_compatible_types(Type *p_type)
{
  if (!p_type) FATAL_ERROR("SignatureExceptions::get_nof_compatible_types()");
  if (p_type->get_type_refd_last()->get_typetype() == Type::T_ERROR) {
    // Return a positive answer for erroneous types.
    return 1;
  } else {
    size_t ret_val = 0;
    for (size_t i = 0; i < exc_v.size(); i++)
      // Don't allow type compatibility.  The types must match exactly.
      if (exc_v[i]->is_compatible(p_type, NULL, NULL))
        ret_val++;
    return ret_val;
  }
}

void SignatureExceptions::chk(Type *p_signature)
{
  Error_Context cntxt(this, "In exception list");
  for (size_t i = 0; i < exc_v.size(); i++) {
    Type *type = exc_v[i];
    type->set_genname(p_signature->get_genname_own(), Int2string(i + 1));
    type->set_parent_type(p_signature);
    type->chk();
    if (type->get_typetype() == Type::T_ERROR) continue;
    type->chk_embedded(false, "on the exception list of a signature");
    const string& type_name = type->get_typename();
    if (exc_m.has_key(type_name)) {
      type->error("Duplicate type in exception list");
      exc_m[type_name]->note("Type `%s' is already given here",
        type_name.c_str());
    } else exc_m.add(type_name, type);
  }
}

void SignatureExceptions::chk(Assignment* p_def)
{
  Error_Context cntxt(this, "In exception list");
  for (size_t i = 0; i < exc_v.size(); i++) {
    Type* type = exc_v[i];
    type->set_genname(p_def->get_id().get_name(), string("_exception_") + Int2string(i + 1));
    type->chk();
    if (type->get_typetype() == Type::T_ERROR) continue;
    Type* type_last = type->get_type_refd_last();
    bool type_error = false;
    char* error_msg_start = NULL;
    switch (type_last->get_typetype()) {
    case Type::T_PORT:
      type_error = true;
      error_msg_start = mprintf("Port type `%s'", type_last->get_typename().c_str());
      break;
    case Type::T_SIGNATURE:
      type_error = true;
      error_msg_start = mprintf("Signature `%s'", type_last->get_typename().c_str());
      break;
    case Type::T_DEFAULT:
      type_error = true;
      error_msg_start = mcopystr("Default type");
      break;
    default:
      break;
    }
    if (type_error) {
      type->error("%s cannot be on the exception list of a%s %s",
        error_msg_start, p_def->get_asstype() == Assignment::A_FUNCTION ? "" : "n", p_def->get_assname());
    }
    Free(error_msg_start);
    const string& type_name = type->get_exception_name();
    if (exc_m.has_key(type_name)) {
      type->error("Duplicate type in exception list");
      exc_m[type_name]->note("Type `%s' is already given here",
        type_name.c_str());
    } else exc_m.add(type_name, type);
  }
}

void SignatureExceptions::set_fullname(const string& p_fullname)
{
  Node::set_fullname(p_fullname);
  for (size_t i = 0; i < exc_v.size(); i++)
    exc_v[i]->set_fullname(p_fullname + ".<type" + Int2string(i + 1) + ">");
}

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

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

} /* namespace Common */
