/*
 *
 *  Copyright (C) 2012-2020, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmnet
 *
 *  Author:  Michael Onken, Jan Schlamelcher
 *
 *  Purpose: Class representing configuration of an SCP.
 *
 */

#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
#include "dcmtk/dcmnet/scpcfg.h"
#include "dcmtk/dcmnet/diutil.h"

// ----------------------------------------------------------------------------

DcmSCPConfig::DcmSCPConfig() :
  m_assocConfig(),
  m_assocCfgProfileName("DEFAULT"),
  m_port(104),
  m_aetitle("DCMTK_SCP"),
  m_refuseAssociation(OFFalse),
  m_maxReceivePDULength(ASC_DEFAULTMAXPDU),
  m_connectionBlockingMode(DUL_BLOCK),
  m_dimseBlockingMode(DIMSE_BLOCKING),
  m_dimseTimeout(0),
  m_acseTimeout(30),
  m_verbosePCMode(OFFalse),
  m_connectionTimeout(1000),
  m_respondWithCalledAETitle(OFTrue),
  m_progressNotificationMode(OFTrue),
  m_tLayer(NULL)
{
}

// ----------------------------------------------------------------------------

DcmSCPConfig::~DcmSCPConfig()
{
  // nothing to do
}

// ----------------------------------------------------------------------------

DcmSCPConfig::DcmSCPConfig(const DcmSCPConfig &old) :
  m_assocConfig(old.m_assocConfig),
  m_assocCfgProfileName(old.m_assocCfgProfileName),
  m_port(old.m_port),
  m_aetitle(old.m_aetitle),
  m_refuseAssociation(old.m_refuseAssociation),
  m_maxReceivePDULength(old.m_maxReceivePDULength),
  m_connectionBlockingMode(old.m_connectionBlockingMode),
  m_dimseBlockingMode(old.m_dimseBlockingMode),
  m_dimseTimeout(old.m_dimseTimeout),
  m_acseTimeout(old.m_acseTimeout),
  m_verbosePCMode(old.m_verbosePCMode),
  m_connectionTimeout(old.m_connectionTimeout),
  m_respondWithCalledAETitle(old.m_respondWithCalledAETitle),
  m_progressNotificationMode(old.m_progressNotificationMode)
{
  // nothing more to do
}

// ----------------------------------------------------------------------------

DcmSCPConfig& DcmSCPConfig::operator=(const DcmSCPConfig &obj)
{
  /* No self assignment */
  if (this != &obj)
  {
    m_assocConfig = obj.m_assocConfig; // performs deep copy
    m_assocCfgProfileName = obj.m_assocCfgProfileName;
    m_port = obj.m_port;
    m_aetitle = obj.m_aetitle;
    m_refuseAssociation = obj.m_refuseAssociation;
    m_maxReceivePDULength = obj.m_maxReceivePDULength;
    m_connectionBlockingMode = obj.m_connectionBlockingMode;
    m_dimseBlockingMode = obj.m_dimseBlockingMode;
    m_dimseTimeout = obj.m_dimseTimeout;
    m_acseTimeout = obj.m_acseTimeout;
    m_verbosePCMode = obj.m_verbosePCMode;
    m_connectionTimeout = obj.m_connectionTimeout;
    m_respondWithCalledAETitle = obj.m_respondWithCalledAETitle;
    m_progressNotificationMode = obj.m_progressNotificationMode;
  }
  return *this;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCPConfig::evaluateIncomingAssociation(T_ASC_Association &assoc) const
{
  return m_assocConfig.evaluateAssociationParameters(m_assocCfgProfileName.c_str(), assoc);
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::forceAssociationRefuse(const OFBool doRefuse)
{
  m_refuseAssociation = doRefuse;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setMaxReceivePDULength(const Uint32 maxRecPDU)
{
  m_maxReceivePDULength = maxRecPDU;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setPort(const Uint16 port)
{
  m_port = port;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setAETitle(const OFString &aetitle)
{
  m_aetitle = aetitle;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setRespondWithCalledAETitle(const OFBool useCalled)
{
  m_respondWithCalledAETitle = useCalled;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setConnectionBlockingMode(const DUL_BLOCKOPTIONS blockingMode)
{
  m_connectionBlockingMode = blockingMode;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)
{
  m_dimseBlockingMode = blockingMode;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setDIMSETimeout(const Uint32 dimseTimeout)
{
  m_dimseTimeout = dimseTimeout;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setACSETimeout(const Uint32 acseTimeout)
{
  m_acseTimeout = acseTimeout;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setConnectionTimeout(const Uint32 timeout)
{
  m_connectionTimeout = timeout;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setVerbosePCMode(const OFBool mode)
{
  m_verbosePCMode = mode;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setHostLookupEnabled(const OFBool mode)
{
  dcmDisableGethostbyaddr.set(!mode);
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setProgressNotificationMode(const OFBool mode)
{
  m_progressNotificationMode = mode;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setAlwaysAcceptDefaultRole(const OFBool enabled)
{
  m_assocConfig.setAlwaysAcceptDefaultRole(enabled);
}

// ----------------------------------------------------------------------------

/* Get methods for SCP settings and current association information */

OFBool DcmSCPConfig::getRefuseAssociation() const
{
  return m_refuseAssociation;
}

// ----------------------------------------------------------------------------

Uint32 DcmSCPConfig::getMaxReceivePDULength() const
{
  return m_maxReceivePDULength;
}

// ----------------------------------------------------------------------------

Uint16 DcmSCPConfig::getPort() const
{
  return m_port;
}

// ----------------------------------------------------------------------------

const OFString &DcmSCPConfig::getAETitle() const
{
  return m_aetitle;
}

// ----------------------------------------------------------------------------

OFBool DcmSCPConfig::getRespondWithCalledAETitle() const
{
  return m_respondWithCalledAETitle;
}

// ----------------------------------------------------------------------------

DUL_BLOCKOPTIONS DcmSCPConfig::getConnectionBlockingMode() const
{
  return m_connectionBlockingMode;
}

// ----------------------------------------------------------------------------

T_DIMSE_BlockingMode DcmSCPConfig::getDIMSEBlockingMode() const
{
  return m_dimseBlockingMode;
}

// ----------------------------------------------------------------------------

Uint32 DcmSCPConfig::getDIMSETimeout() const
{
  return m_dimseTimeout;
}

// ----------------------------------------------------------------------------

Uint32 DcmSCPConfig::getConnectionTimeout() const
{
  return m_connectionTimeout;
}

// ----------------------------------------------------------------------------

Uint32 DcmSCPConfig::getACSETimeout() const
{
  return m_acseTimeout;
}

// ----------------------------------------------------------------------------

OFBool DcmSCPConfig::getVerbosePCMode() const
{
  return m_verbosePCMode;
}

// ----------------------------------------------------------------------------

OFBool DcmSCPConfig::getHostLookupEnabled() const
{
  return dcmDisableGethostbyaddr.get();
}

// ----------------------------------------------------------------------------

OFBool DcmSCPConfig::getProgressNotificationMode() const
{
  return m_progressNotificationMode;
}

// ----------------------------------------------------------------------------

OFBool DcmSCPConfig::transportLayerEnabled() const
{
  return (m_tLayer != NULL);
}

// ----------------------------------------------------------------------------

DcmTransportLayer * DcmSCPConfig::getTransportLayer() const
{
    return m_tLayer;
}

// ----------------------------------------------------------------------------

void DcmSCPConfig::setTransportLayer(DcmTransportLayer *tlayer)
{
  m_tLayer = tlayer;
}

// ----------------------------------------------------------------------------

// Reads association configuration from config file
OFCondition DcmSCPConfig::loadAssociationCfgFile(const OFString &assocFile)
{
  OFCondition result = EC_InvalidFilename;
  // delete any previous association configuration
  m_assocConfig.clear();
  if (!assocFile.empty())
  {
    OFString profileName;
    DCMNET_DEBUG("Loading association configuration file: " << assocFile);
    result = DcmAssociationConfigurationFile::initialize(m_assocConfig, assocFile.c_str(), OFFalse);
    if (result.bad())
    {
      DCMNET_ERROR("Unable to parse association configuration file: " << assocFile << ": " << result.text());
      m_assocConfig.clear();
    }
  }
  return result;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCPConfig::setAndCheckAssociationProfile(const OFString &profileName)
{
  if (profileName.empty())
    return EC_IllegalParameter;

  DCMNET_TRACE("Setting and checking SCP association profile " << profileName);
  OFString mangledName;
  OFCondition result = checkAssociationProfile(profileName, mangledName);
  if (result.good())
  {
    m_assocCfgProfileName = mangledName;
    DCMNET_TRACE("Setting SCP association profile to (mangled name) " << mangledName);
  }
  return result;
}

// ----------------------------------------------------------------------------

OFString DcmSCPConfig::getActiveAssociationProfile() const
{
  return m_assocCfgProfileName;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCPConfig::checkAssociationProfile(const OFString& profileName,
                                                  OFString& mangledName) const
{
  if (profileName.empty())
    return EC_IllegalParameter;

  DCMNET_TRACE("Checking SCP association profile " << profileName);
  mangledName.clear();
  OFCondition result;

  /* perform name mangling for config file key */
  const unsigned char *c = OFreinterpret_cast(const unsigned char *, profileName.c_str());
  while (*c)
  {
    if (! isspace(*c)) mangledName += OFstatic_cast(char, toupper(*c));
    ++c;
  }
  /* check profile */
  if (result.good() && !m_assocConfig.isKnownProfile(mangledName.c_str()))
  {
    DCMNET_ERROR("No association profile named \"" << profileName << "\" in association configuration, " <<
      "did you forget to add presentation contexts?");
    result = NET_EC_InvalidSCPAssociationProfile;
  }
  if (result.good() && !m_assocConfig.isValidSCPProfile(mangledName.c_str()))
  {
    DCMNET_ERROR("The association profile named \"" << profileName << "\" is not a valid SCP association profile");
    result = NET_EC_InvalidSCPAssociationProfile;
  }

  return result;
}


OFCondition DcmSCPConfig::addPresentationContext(const OFString &abstractSyntax,
                                                 const OFList<OFString> &xferSyntaxes,
                                                 const T_ASC_SC_ROLE role,
                                                 const OFString &profile)
{
  const OFString profileName = mangleProfileName(profile);
  if (profileName.empty() || xferSyntaxes.empty() || (role == ASC_SC_ROLE_NONE) || abstractSyntax.empty())
    return EC_IllegalParameter;
  OFCondition result;

  // check whether we already have a matching ts list and otherwise create one
  OFString DCMSCP_TS_KEY = m_assocConfig.findTSKey(xferSyntaxes);
  if ( DCMSCP_TS_KEY.empty() )
  {
    // use counter in order to create unique configuration keys if required.
    // increment counter in all cases since we could have produced a broken
    // ts list that we do not want use in another call to this function.
    static size_t count = 0;
    DCMSCP_TS_KEY += "TSKEY_";
    DCMSCP_TS_KEY += numToString(count);
    result = addNewTSList(DCMSCP_TS_KEY, xferSyntaxes);
    count++;
  }

  // create role key and amend configuration (if required)
  OFString DCMSCP_RO_KEY;
  DCMSCP_RO_KEY = profileName;
  DCMSCP_RO_KEY += "_ROLEKEY";
  result = m_assocConfig.createEmptyRoleList(DCMSCP_RO_KEY.c_str());
  if (result.good() && (role != ASC_SC_ROLE_DEFAULT))
  {
    result = m_assocConfig.addRole(DCMSCP_RO_KEY.c_str(), abstractSyntax.c_str(), role);
  }
  if (result.bad())
    return result;


  // create new profile if required and add presentation context as just defined.
  // we always use the same presentation context list.
  OFString DCMSCP_PC_KEY = profileName; DCMSCP_PC_KEY += "_PCKEY";
  const DcmProfileEntry* pEntry = NULL;
  if ( result.good() )
  {
    pEntry = m_assocConfig.getProfileEntry(profileName);
    if ( pEntry == NULL)
    {
      // finally add new presentation context to list and profile to configuration
      if ( result.good() ) result = m_assocConfig.addPresentationContext(DCMSCP_PC_KEY.c_str(), abstractSyntax.c_str(), DCMSCP_TS_KEY.c_str(), OFFalse);
      if ( result.good() ) result = m_assocConfig.addProfile(profileName.c_str(), DCMSCP_PC_KEY.c_str(), DCMSCP_RO_KEY.c_str());
    }
    else
    {
      // finally, add presentation context to existing profile
      result = m_assocConfig.addPresentationContext(pEntry->getPresentationContextKey(), abstractSyntax.c_str(), DCMSCP_TS_KEY.c_str(), OFFalse);
    }
  }
  return result;
}


void DcmSCPConfig::dumpPresentationContexts(STD_NAMESPACE ostream &out,
                                            OFString profileName)
{
  if ( profileName.empty() ) profileName = m_assocCfgProfileName;
  m_assocConfig.dumpProfiles(out, profileName);
}


OFString DcmSCPConfig::mangleProfileName(const OFString& profile) const
{
  /* perform name mangling for config profile key */
  const unsigned char *c = OFreinterpret_cast(const unsigned char *, profile.c_str());
  OFString mangledName;
  while (*c)
  {
    if (! isspace(*c)) mangledName += OFstatic_cast(char, toupper(*c));
    ++c;
  }
  return mangledName;
}


OFString DcmSCPConfig::numToString(const size_t num) const
{
  OFString result;
  OFStringStream stream;
  stream << num;
  stream << OFStringStream_ends;
  OFSTRINGSTREAM_GETSTR(stream, buffStr)
  result = buffStr;
  OFSTRINGSTREAM_FREESTR(buffStr)
  return result;
}


OFCondition DcmSCPConfig::addNewTSList(
  const OFString& tsListName,
  const OFList<OFString>& ts)
{
  // add ts to new ts list
  OFCondition result;
  OFListConstIterator(OFString) it = ts.begin();
  OFListConstIterator(OFString) endOfList = ts.end();
  while ((it != endOfList) && result.good())
  {
    result = m_assocConfig.addTransferSyntax(tsListName.c_str(), (*it).c_str());
    if ( result.bad() ) return result;
    it++;
  }
  return result;
}
