/*
 *
 *  Copyright (C) 2000-2022, 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: dcmpstat
 *
 *  Author: Marco Eichelberg
 *
 *  Purpose:
 *    classes: DVPSPrintSCP
 *
 */

#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */

#include "dcmtk/ofstd/ofstream.h"
#include "dcmtk/dcmpstat/dvpsprt.h"
#include "dcmtk/dcmpstat/dvpsdef.h"     /* for constants */
#include "dcmtk/dcmpstat/dviface.h"
#include "dcmtk/dcmpstat/dvpsfs.h"
#include "dcmtk/dcmpstat/dvpssp.h"
#include "dcmtk/dcmpstat/dvpshlp.h"
#include "dcmtk/ofstd/ofdatime.h"

#include "dcmtk/dcmpstat/dvpsov.h"      /* for DVPSOverlay, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsgl.h"      /* for DVPSGraphicLayer, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsrs.h"      /* for DVPSReferencedSeries, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsal.h"      /* for DVPSOverlayCurveActivationLayer, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsga.h"      /* for DVPSGraphicAnnotation, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpscu.h"      /* for DVPSCurve, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsvl.h"      /* for DVPSVOILUT, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsvw.h"      /* for DVPSVOIWindow, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsda.h"      /* for DVPSDisplayedArea, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpssv.h"      /* for DVPSSoftcopyVOI, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsib.h"      /* for DVPSImageBoxContent, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsab.h"      /* for DVPSAnnotationContent, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpstx.h"      /* for DVPSTextObject, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsgr.h"      /* for DVPSGraphicObject, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsri.h"      /* for DVPSReferencedImage, needed by MSVC5 with STL */

DVPSPrintSCP::DVPSPrintSCP(DVInterface &iface, const char *cfname)
: dviface(iface)
, cfgname(cfname)
, blockMode(DIMSE_BLOCKING)
, timeout(0)
, filmSession(NULL)
, presentationLUTList()
, storedPrintList()
, assoc(NULL)
, studyInstanceUID(DCM_StudyInstanceUID)
, psSeriesInstanceUID(DCM_SeriesInstanceUID)
, imageSeriesInstanceUID(DCM_SeriesInstanceUID)
, logSequence(NULL)
, acseSequence(NULL)
, logPath()
{
}

DVPSPrintSCP::~DVPSPrintSCP()
{
  dropAssociation();
  delete logSequence;
  delete acseSequence;
}

int DVPSPrintSCP::errorCond(OFCondition cond, const char *message)
{
  int result = cond.bad();
  if (result)
  {
    OFString temp_str;
    DCMPSTAT_WARN(message << OFendl << DimseCondition::dump(temp_str, cond));
  }
  return result;
}


DVPSAssociationNegotiationResult DVPSPrintSCP::negotiateAssociation(T_ASC_Network &net)
{
  DVPSAssociationNegotiationResult result = DVPSJ_success;
  char buf[BUFSIZ];
  OFBool dropAssoc = OFFalse;

  // get AETITLE from config file
  const char *aetitle = dviface.getTargetAETitle(cfgname);
  if (aetitle==NULL) aetitle = dviface.getNetworkAETitle(); // default if AETITLE is missing

  // get MaxPDU from config file
  unsigned long maxPDU = dviface.getTargetMaxPDU(cfgname);
  if (maxPDU == 0) maxPDU = DEFAULT_MAXPDU;
  else if (maxPDU > ASC_MAXIMUMPDUSIZE)
  {
    DCMPSTAT_INFO("max PDU size " << maxPDU << " too big, using default: " << DEFAULT_MAXPDU);
    maxPDU = DEFAULT_MAXPDU;
  }
  else if (maxPDU < ASC_MINIMUMPDUSIZE)
  {
    DCMPSTAT_INFO("max PDU size " << maxPDU << " too small, using default: " << DEFAULT_MAXPDU);
    maxPDU = DEFAULT_MAXPDU;
  }

  // check whether we want to support Presentation LUT
  OFBool supportPresentationLUT = dviface.getTargetPrinterSupportsPresentationLUT(cfgname);

  // check whether we want to support Implicit VR only
  OFBool implicitOnly = dviface.getTargetImplicitOnly(cfgname);

  // check whether we're expected to accept TLS associations
  OFBool useTLS = dviface.getTargetUseTLS(cfgname);

  void *associatePDU=NULL;
  unsigned long associatePDUlength=0;

  OFCondition cond = ASC_receiveAssociation(&net, &assoc, maxPDU, &associatePDU, &associatePDUlength, useTLS);
  if (errorCond(cond, "Failed to receive association:"))
  {
    dropAssoc = OFTrue;
    result = DVPSJ_error;
  }
  else
  {
    DCMPSTAT_INFO("Association Received ("
           << assoc->params->DULparams.callingPresentationAddress
           << ":" << assoc->params->DULparams.callingAPTitle << " -> "
           << assoc->params->DULparams.calledAPTitle
           << ") " << OFDateTime::getCurrentDateTime());

    OFString temp_str;
    DCMPSTAT_DUMP(ASC_dumpParameters(temp_str, assoc->params, ASC_ASSOC_RQ));

    ASC_setAPTitles(assoc->params, NULL, NULL, aetitle);

    /* Application Context Name */
    cond = ASC_getApplicationContextName(assoc->params, buf, sizeof(buf));
    if (cond.bad() || strcmp(buf, DICOM_STDAPPLICATIONCONTEXT) != 0)
    {
        /* reject: the application context name is not supported */
        DCMPSTAT_WARN("Bad AppContextName: " << buf);
        cond = refuseAssociation(OFTrue);
        dropAssoc = OFTrue;
        result = DVPSJ_error;
    } else {
      const char *abstractSyntaxes[] =
      {
        UID_VerificationSOPClass,
        UID_BasicGrayscalePrintManagementMetaSOPClass,
        UID_PresentationLUTSOPClass,
        UID_PrivateShutdownSOPClass
      };

      int numAbstractSyntaxes = 3;
      if (supportPresentationLUT) numAbstractSyntaxes = 4;

      const char* transferSyntaxes[] = { NULL, NULL, NULL };
      int numTransferSyntaxes = 0;
      if (implicitOnly)
      {
        transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax;
        numTransferSyntaxes = 1;
      } else {
        transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
        numTransferSyntaxes = 3;

        if (gLocalByteOrder == EBO_LittleEndian) {
          /* we are on a little endian machine */
          transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
          transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
        } else {
          /* we are on a big endian machine */
          transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
          transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
        }
      }

      /* accept presentation contexts */
      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
        assoc->params,
        (const char**)abstractSyntaxes, numAbstractSyntaxes,
        (const char**)transferSyntaxes, numTransferSyntaxes);
      errorCond(cond, "Cannot accept presentation contexts:");

    }

    /* check if we have negotiated the private "shutdown" SOP Class */
    if (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PrivateShutdownSOPClass))
    {
      cond = refuseAssociation(OFFalse);
      dropAssoc = OFTrue;
      result = DVPSJ_terminate;
    }
  } /* receiveAssociation successful */
  if (dropAssoc) dropAssociation();

  if (acseSequence && associatePDU)
  {
    addLogEntry(acseSequence, "A_ASSOCIATE_RQ");
    DcmItem *newItem = new DcmItem();
    if (newItem)
    {
      DcmElement *assocData = new DcmOtherByteOtherWord(PSTAT_DCM_AssociateData);
      if (assocData)
      {
        assocData->putUint8Array((Uint8 *) associatePDU, associatePDUlength);
        newItem->insert(assocData, OFTrue /*replaceOld*/);
        acseSequence->insert(newItem);
      } else delete newItem;
    }
  }
  delete[] (char *)associatePDU;
  return result;
}


OFCondition DVPSPrintSCP::refuseAssociation(OFBool isBadContext)
{
  T_ASC_RejectParameters rej;

  if (isBadContext)
  {
      rej.result = ASC_RESULT_REJECTEDTRANSIENT;
      rej.source = ASC_SOURCE_SERVICEUSER;
      rej.reason = ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED;
  } else {
      rej.result = ASC_RESULT_REJECTEDPERMANENT;
      rej.source = ASC_SOURCE_SERVICEUSER;
      rej.reason = ASC_REASON_SU_NOREASON;
  }

  void *associatePDU=NULL;
  unsigned long associatePDUlength=0;

  OFCondition cond = ASC_rejectAssociation(assoc, &rej, &associatePDU, &associatePDUlength);

  OFString temp_str;
  DCMPSTAT_DUMP(ASC_dumpParameters(temp_str, assoc->params, ASC_ASSOC_RJ));

  if (acseSequence && associatePDU)
  {
    addLogEntry(acseSequence, "A_ASSOCIATE_RJ");
    DcmItem *newItem = new DcmItem();
    if (newItem)
    {
      DcmElement *assocData = new DcmOtherByteOtherWord(PSTAT_DCM_AssociateData);
      if (assocData)
      {
        assocData->putUint8Array((Uint8 *) associatePDU, associatePDUlength);
        newItem->insert(assocData, OFTrue /*replaceOld*/);
        acseSequence->insert(newItem);
      } else delete newItem;
    }
  }
  delete[] (char *)associatePDU;
  errorCond(cond, "Association Reject Failed:");
  return cond;
}


void DVPSPrintSCP::dropAssociation()
{
  if (assoc == NULL) return;
  OFCondition cond = ASC_dropSCPAssociation(assoc);
  errorCond(cond, "Cannot Drop Association:");
  cond = ASC_destroyAssociation(&assoc);
  errorCond(cond, "Cannot Destroy Association:");
  assoc = NULL;
  return;
}


void DVPSPrintSCP::handleClient()
{
  void *associatePDU=NULL;
  unsigned long associatePDUlength=0;
  OFString temp_str;

  OFCondition cond = ASC_acknowledgeAssociation(assoc, &associatePDU, &associatePDUlength);
  DCMPSTAT_DUMP(ASC_dumpParameters(temp_str, assoc->params, ASC_ASSOC_AC));
  if (acseSequence && associatePDU)
  {
    addLogEntry(acseSequence, "A_ASSOCIATE_AC");
    DcmItem *newItem = new DcmItem();
    if (newItem)
    {
      DcmElement *assocData = new DcmOtherByteOtherWord(PSTAT_DCM_AssociateData);
      if (assocData)
      {
        assocData->putUint8Array((Uint8 *) associatePDU, associatePDUlength);
        newItem->insert(assocData, OFTrue /*replaceOld*/);
        acseSequence->insert(newItem);
      } else delete newItem;
    }
  }
  delete[] (char *)associatePDU;

  if (! errorCond(cond, "Cannot acknowledge association:"))
  {

    DCMPSTAT_INFO("Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"
        << ((ASC_countAcceptedPresentationContexts(assoc->params) == 0) ? "\n    (but no valid presentation contexts)" : ""));

    T_DIMSE_Message msg;
    T_ASC_PresentationContextID presID;
    cond = EC_Normal;
    DcmDataset *rawCommandSet=NULL;

    /* do real work */
    while (cond.good())
    {
      cond = DIMSE_receiveCommand(assoc, DIMSE_BLOCKING, 0, &presID, &msg, NULL, &rawCommandSet);
      /* did peer release, abort, or do we have a valid message ? */

      if (cond.good())
      {
    	addLogEntry(logSequence, "RECEIVE");
        if (rawCommandSet)
        {
          if (logSequence) logSequence->insert(new DcmItem(*rawCommandSet));
          delete rawCommandSet;
          rawCommandSet = NULL;
        } else {
          // should not happen
          if (logSequence) logSequence->insert(new DcmItem());
        }
      }

      if (cond.good())
      {
          /* process command */
          switch (msg.CommandField)
          {
            case DIMSE_C_ECHO_RQ:
              cond = handleCEcho(msg, presID);
              break;
            case DIMSE_N_GET_RQ:
              cond = handleNGet(msg, presID);
              break;
            case DIMSE_N_SET_RQ:
              cond = handleNSet(msg, presID);
              break;
            case DIMSE_N_ACTION_RQ:
              cond = handleNAction(msg, presID);
              break;
            case DIMSE_N_CREATE_RQ:
              cond = handleNCreate(msg, presID);
              break;
            case DIMSE_N_DELETE_RQ:
              cond = handleNDelete(msg, presID);
              break;
            default:
              cond = DIMSE_BADCOMMANDTYPE; /* unsupported command */
              dumpNMessage(msg, NULL, OFFalse);
              DCMPSTAT_ERROR("Cannot handle command: 0x"
                << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
                << OFstatic_cast(unsigned int, msg.CommandField));
              break;
          }
      }
      else
      {
          /* finish processing loop */
      }
    } /* while */

    if (logSequence) saveDimseLog();

    /* close association */
    if (cond == DUL_PEERREQUESTEDRELEASE)
    {
      DCMPSTAT_INFO("Association Release");
      cond = ASC_acknowledgeRelease(assoc);
      errorCond(cond, "Cannot release association:");
    }
    else if (cond == DUL_PEERABORTEDASSOCIATION)
    {
      DCMPSTAT_INFO("Association Aborted");
    }
    else
    {
      errorCond(cond, "DIMSE Failure (aborting association):");
      cond = ASC_abortAssociation(assoc);
      errorCond(cond, "Cannot abort association:");
    }
  }
  dropAssociation();
}


OFCondition DVPSPrintSCP::handleCEcho(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &rq.msg.CEchoRQ, STATUS_Success, NULL);
  return cond;
}

OFCondition DVPSPrintSCP::handleNGet(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  // initialize response message
  T_DIMSE_Message rsp;
  rsp.CommandField = DIMSE_N_GET_RSP;
  rsp.msg.NGetRSP.MessageIDBeingRespondedTo = rq.msg.NGetRQ.MessageID;
  rsp.msg.NGetRSP.AffectedSOPClassUID[0] = 0;
  rsp.msg.NGetRSP.DimseStatus = STATUS_Success;
  rsp.msg.NGetRSP.AffectedSOPInstanceUID[0] = 0;
  rsp.msg.NGetRSP.DataSetType = DIMSE_DATASET_NULL;
  rsp.msg.NGetRSP.opts = 0;

  OFCondition cond = EC_Normal;
  DcmDataset *rspDataset = NULL;

  if (rq.msg.NGetRQ.DataSetType == DIMSE_DATASET_PRESENT)
  {
     DcmDataset *dataset = NULL;
     // should not happen
     cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presID, &dataset, NULL, NULL);
     if (cond.good())
     {
       if (logSequence) logSequence->insert(new DcmItem(*dataset));
       dumpNMessage(rq, dataset, OFFalse);
     }
     delete dataset;
     if (cond.bad()) return cond;
  } else {
    dumpNMessage(rq, NULL, OFFalse);
    if (logSequence) logSequence->insert(new DcmItem());
  }

  OFString sopClass(rq.msg.NGetRQ.RequestedSOPClassUID);
  if (sopClass == UID_PrinterSOPClass)
  {
    // Print N-GET
    printerNGet(rq, rsp, rspDataset);
  } else {
    DCMPSTAT_WARN("N-GET unsupported for SOP class '" << sopClass << "'");
    rsp.msg.NGetRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
  }
  DcmDataset *rspCommand = NULL;
  addLogEntry(logSequence, "SEND");
  dumpNMessage(rsp, rspDataset, OFTrue);
  cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, NULL, rspDataset, NULL, NULL, &rspCommand);
  if (logSequence)
  {
    if (rspCommand) logSequence->insert(new DcmItem(*rspCommand));
    if (rspDataset) logSequence->insert(new DcmItem(*rspDataset));
    else logSequence->insert(new DcmItem());
  }
  delete rspCommand;
  delete rspDataset;
  return cond;
}


OFCondition DVPSPrintSCP::handleNSet(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  // initialize response message
  T_DIMSE_Message rsp;
  rsp.CommandField = DIMSE_N_SET_RSP;
  rsp.msg.NSetRSP.MessageIDBeingRespondedTo = rq.msg.NSetRQ.MessageID;
  rsp.msg.NSetRSP.AffectedSOPClassUID[0] = 0;
  rsp.msg.NSetRSP.DimseStatus = STATUS_Success;
  rsp.msg.NSetRSP.AffectedSOPInstanceUID[0] = 0;
  rsp.msg.NSetRSP.DataSetType = DIMSE_DATASET_NULL;
  rsp.msg.NSetRSP.opts = 0;

  OFCondition cond = EC_Normal;
  DcmDataset *rqDataset = NULL;
  DcmDataset *rspDataset = NULL;

  if (rq.msg.NSetRQ.DataSetType == DIMSE_DATASET_PRESENT)
  {
     cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presID, &rqDataset, NULL, NULL);
     if (cond.bad()) return cond;
     if (logSequence && rqDataset) logSequence->insert(new DcmItem(*rqDataset));
  } else if (logSequence) logSequence->insert(new DcmItem());

  dumpNMessage(rq, rqDataset, OFFalse);

  OFString sopClass(rq.msg.NSetRQ.RequestedSOPClassUID);
  if (sopClass == UID_BasicFilmSessionSOPClass)
  {
    // BFS N-SET
    filmSessionNSet(rq, rqDataset, rsp, rspDataset);
  } else if (sopClass == UID_BasicFilmBoxSOPClass)
  {
    // BFB N-SET
    filmBoxNSet(rq, rqDataset, rsp, rspDataset);
  } else if (sopClass == UID_BasicGrayscaleImageBoxSOPClass)
  {
    // BGIB N-SET
    imageBoxNSet(rq, rqDataset, rsp, rspDataset);
  } else {
    DCMPSTAT_WARN("N-SET unsupported for SOP class '" << sopClass << "'");
    rsp.msg.NSetRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
  }

  DcmDataset *rspCommand = NULL;
  addLogEntry(logSequence, "SEND");
  dumpNMessage(rsp, rspDataset, OFTrue);
  cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, NULL, rspDataset, NULL, NULL, &rspCommand);
  if (logSequence)
  {
    if (rspCommand) logSequence->insert(new DcmItem(*rspCommand));
    if (rspDataset) logSequence->insert(new DcmItem(*rspDataset));
    else logSequence->insert(new DcmItem());
  }
  delete rspCommand;
  delete rqDataset;
  delete rspDataset;
  return cond;
}


OFCondition DVPSPrintSCP::handleNAction(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  // initialize response message
  T_DIMSE_Message rsp;
  rsp.CommandField = DIMSE_N_ACTION_RSP;
  rsp.msg.NActionRSP.MessageIDBeingRespondedTo = rq.msg.NActionRQ.MessageID;
  rsp.msg.NActionRSP.AffectedSOPClassUID[0] = 0;
  rsp.msg.NActionRSP.DimseStatus = STATUS_Success;
  rsp.msg.NActionRSP.AffectedSOPInstanceUID[0] = 0;
  rsp.msg.NActionRSP.ActionTypeID = rq.msg.NActionRQ.ActionTypeID;
  rsp.msg.NActionRSP.DataSetType = DIMSE_DATASET_NULL;
  rsp.msg.NActionRSP.opts = O_NACTION_ACTIONTYPEID;

  OFCondition cond = EC_Normal;
  DcmDataset *rqDataset = NULL;

  if (rq.msg.NActionRQ.DataSetType == DIMSE_DATASET_PRESENT)
  {
     cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presID, &rqDataset, NULL, NULL);
     if (cond.bad()) return cond;
     if (logSequence && rqDataset) logSequence->insert(new DcmItem(*rqDataset));
  } else if (logSequence) logSequence->insert(new DcmItem());

  dumpNMessage(rq, rqDataset, OFFalse);

  OFString sopClass(rq.msg.NActionRQ.RequestedSOPClassUID);
  if (sopClass == UID_BasicFilmSessionSOPClass)
  {
    // BFS N-ACTION
    filmSessionNAction(rq, rsp);
  } else if (sopClass == UID_BasicFilmBoxSOPClass)
  {
    // BFB N-ACTION
    filmBoxNAction(rq, rsp);
  } else {
    DCMPSTAT_WARN("N-ACTION unsupported for SOP class '" << sopClass << "'");
    rsp.msg.NActionRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
  }

  DcmDataset *rspCommand = NULL;
  addLogEntry(logSequence, "SEND");
  dumpNMessage(rsp, NULL, OFTrue);
  cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, NULL, NULL, NULL, NULL, &rspCommand);
  if (logSequence)
  {
    if (rspCommand) logSequence->insert(new DcmItem(*rspCommand));
    logSequence->insert(new DcmItem());
  }
  delete rspCommand;
  delete rqDataset;
  return cond;
}


OFCondition DVPSPrintSCP::handleNCreate(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  // initialize response message
  T_DIMSE_Message rsp;
  rsp.CommandField = DIMSE_N_CREATE_RSP;
  rsp.msg.NCreateRSP.MessageIDBeingRespondedTo = rq.msg.NCreateRQ.MessageID;
  rsp.msg.NCreateRSP.AffectedSOPClassUID[0] = 0;
  rsp.msg.NCreateRSP.DimseStatus = STATUS_Success;
  if (rq.msg.NCreateRQ.opts & O_NCREATE_AFFECTEDSOPINSTANCEUID)
  {
    // instance UID is provided by SCU
    strncpy(rsp.msg.NCreateRSP.AffectedSOPInstanceUID, rq.msg.NCreateRQ.AffectedSOPInstanceUID, sizeof(DIC_UI));
  } else {
    // we generate our own instance UID
    dcmGenerateUniqueIdentifier(rsp.msg.NCreateRSP.AffectedSOPInstanceUID);
  }
  rsp.msg.NCreateRSP.DataSetType = DIMSE_DATASET_NULL;
  OFBool omitFlag = dviface.getTargetPrintSCPOmitSOPClassUIDFromCreateResponse(cfgname);
  if (omitFlag)
  {
    rsp.msg.NCreateRSP.opts = O_NCREATE_AFFECTEDSOPINSTANCEUID;
  } else {
    strncpy(rsp.msg.NCreateRSP.AffectedSOPClassUID, rq.msg.NCreateRQ.AffectedSOPClassUID, sizeof(DIC_UI));
    rsp.msg.NCreateRSP.opts = O_NCREATE_AFFECTEDSOPINSTANCEUID | O_NCREATE_AFFECTEDSOPCLASSUID;
  }

  OFCondition cond = EC_Normal;
  DcmDataset *rqDataset = NULL;
  DcmDataset *rspDataset = NULL;

  if (rq.msg.NCreateRQ.DataSetType == DIMSE_DATASET_PRESENT)
  {
     cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presID, &rqDataset, NULL, NULL);
     if (cond.bad()) return cond;
     if (logSequence && rqDataset) logSequence->insert(new DcmItem(*rqDataset));
  } else if (logSequence) logSequence->insert(new DcmItem());

  dumpNMessage(rq, rqDataset, OFFalse);

  OFString sopClass(rq.msg.NCreateRQ.AffectedSOPClassUID);
  if (sopClass == UID_BasicFilmSessionSOPClass)
  {
    // BFS N-CREATE
    filmSessionNCreate(rqDataset, rsp, rspDataset);
  } else if (sopClass == UID_BasicFilmBoxSOPClass)
  {
    // BFB N-CREATE
    filmBoxNCreate(rqDataset, rsp, rspDataset);
  } else if (sopClass == UID_PresentationLUTSOPClass)
  {
    // P-LUT N-CREATE
    presentationLUTNCreate(rqDataset, rsp, rspDataset);
  } else {
    DCMPSTAT_WARN("N-CREATE unsupported for SOP class '" << sopClass << "'");
    rsp.msg.NCreateRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
    rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
  }

  DcmDataset *rspCommand = NULL;
  addLogEntry(logSequence, "SEND");
  dumpNMessage(rsp, rspDataset, OFTrue);
  cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, NULL, rspDataset, NULL, NULL, &rspCommand);
  if (logSequence)
  {
    if (rspCommand) logSequence->insert(new DcmItem(*rspCommand));
    if (rspDataset) logSequence->insert(new DcmItem(*rspDataset));
    else logSequence->insert(new DcmItem());
  }
  delete rspCommand;
  delete rqDataset;
  delete rspDataset;
  return cond;
}

OFCondition DVPSPrintSCP::handleNDelete(T_DIMSE_Message& rq, T_ASC_PresentationContextID presID)
{
  // initialize response message
  T_DIMSE_Message rsp;
  rsp.CommandField = DIMSE_N_DELETE_RSP;
  rsp.msg.NDeleteRSP.MessageIDBeingRespondedTo = rq.msg.NDeleteRQ.MessageID;
  rsp.msg.NDeleteRSP.AffectedSOPClassUID[0] = 0;
  rsp.msg.NDeleteRSP.DimseStatus = STATUS_Success;
  rsp.msg.NDeleteRSP.AffectedSOPInstanceUID[0] = 0;
  rsp.msg.NDeleteRSP.DataSetType = DIMSE_DATASET_NULL;
  rsp.msg.NDeleteRSP.opts = 0;

  OFCondition cond = EC_Normal;
  if (rq.msg.NDeleteRQ.DataSetType == DIMSE_DATASET_PRESENT)
  {
     // should not happen
     DcmDataset *dataset = NULL;
     cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presID, &dataset, NULL, NULL);
     if (cond.good())
     {
       if (logSequence) logSequence->insert(new DcmItem(*dataset));
       dumpNMessage(rq, dataset, OFFalse);
     }
     delete dataset;
     if (cond.bad()) return cond;
  } else {
     if (logSequence) logSequence->insert(new DcmItem());
     dumpNMessage(rq, NULL, OFFalse);
  }

  OFString sopClass(rq.msg.NDeleteRQ.RequestedSOPClassUID);
  if (sopClass == UID_BasicFilmSessionSOPClass)
  {
    // BFS N-DELETE
    filmSessionNDelete(rq, rsp);
  } else if (sopClass == UID_BasicFilmBoxSOPClass)
  {
    // BFB N-DELETE
    filmBoxNDelete(rq, rsp);
  } else if (sopClass == UID_PresentationLUTSOPClass)
  {
    // P-LUT N-DELETE
    presentationLUTNDelete(rq, rsp);
  } else {
    DCMPSTAT_WARN("N-DELETE unsupported for SOP class '" << sopClass << "'");
    rsp.msg.NDeleteRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
  }

  DcmDataset *rspCommand = NULL;
  addLogEntry(logSequence, "SEND");
  dumpNMessage(rsp, NULL, OFTrue);
  cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &rsp, NULL, NULL, NULL, NULL, &rspCommand);
  if (logSequence)
  {
    if (rspCommand) logSequence->insert(new DcmItem(*rspCommand));
    logSequence->insert(new DcmItem());
  }
  delete rspCommand;
  return cond;
}


void DVPSPrintSCP::printerNGet(T_DIMSE_Message& rq, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  OFString printerInstance(UID_PrinterSOPInstance);
  if (printerInstance == rq.msg.NGetRQ.RequestedSOPInstanceUID)
  {
    OFBool result = OFTrue;
    rspDataset = new DcmDataset;
    if (rspDataset == NULL)
    {
      rsp.msg.NGetRSP.DimseStatus = STATUS_N_ProcessingFailure;
      result = OFFalse;
    }

    int i=0;
    DIC_US group=0;
    DIC_US element=0;
    while (i+1 < rq.msg.NGetRQ.ListCount)
    {
      group = rq.msg.NGetRQ.AttributeIdentifierList[i++];
      element = rq.msg.NGetRQ.AttributeIdentifierList[i++];
      if ((group == 0x2110)&&(element == 0x0010))
      {
       if (EC_Normal != DVPSHelper::putStringValue(rspDataset, DCM_PrinterStatus, DEFAULT_printerStatus)) result = OFFalse;
      }
      else if  ((group == 0x2110)&&(element == 0x0020))
      {
       if (EC_Normal != DVPSHelper::putStringValue(rspDataset, DCM_PrinterStatusInfo, DEFAULT_printerStatusInfo)) result = OFFalse;
      }
      else if (element == 0x0000)
      {
      	/* group length */
      }
      else
      {
        DCMPSTAT_WARN("cannot retrieve printer information: unsupported attribute ("
            << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4) << (int)group << ","
            << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4) << (int)element
            << ") in attribute list.");
        rsp.msg.NGetRSP.DimseStatus = STATUS_N_NoSuchAttribute;
        result = OFFalse;
      }
    }

    if (rq.msg.NGetRQ.ListCount == 0)
    {
     if (EC_Normal != DVPSHelper::putStringValue(rspDataset, DCM_PrinterStatus, DEFAULT_printerStatus)) result = OFFalse;
     if (EC_Normal != DVPSHelper::putStringValue(rspDataset, DCM_PrinterStatusInfo, DEFAULT_printerStatusInfo)) result = OFFalse;
    }

    if (result)
    {
      rsp.msg.NSetRSP.DataSetType = DIMSE_DATASET_PRESENT;
    } else {
      delete rspDataset;
      rspDataset = NULL;
      if (rsp.msg.NGetRSP.DimseStatus == 0) rsp.msg.NGetRSP.DimseStatus = STATUS_N_ProcessingFailure;
      result = OFFalse;
    }
  } else {
    DCMPSTAT_WARN("cannot retrieve printer information, instance UID is not well-known printer SOP instance UID.");
    rsp.msg.NGetRSP.DimseStatus = STATUS_N_NoSuchSOPInstance;
  }
}

void DVPSPrintSCP::filmSessionNSet(T_DIMSE_Message& rq, DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  if (filmSession && (filmSession->isInstance(rq.msg.NSetRQ.RequestedSOPInstanceUID)))
  {
    OFBool usePLUTinFilmSession = OFFalse;
    if (assoc && (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass)))
    {
      if (dviface.getTargetPrinterPresentationLUTinFilmSession(cfgname)) usePLUTinFilmSession = OFTrue;
    }

    DVPSFilmSession *newSession = new DVPSFilmSession(*filmSession);
    if (newSession)
    {
      if (newSession->printSCPSet(dviface, cfgname, rqDataset, rsp, rspDataset, usePLUTinFilmSession, presentationLUTList, storedPrintList))
      {
        delete filmSession;
        filmSession = newSession;
      } else delete newSession;
    } else {
      DCMPSTAT_WARN("cannot update film session, out of memory.");
      rsp.msg.NSetRSP.DimseStatus = STATUS_N_ProcessingFailure;
    }
  } else {
    // film session does not exist or wrong instance UID
    DCMPSTAT_WARN("cannot update film session, object not found.");
    rsp.msg.NSetRSP.DimseStatus = STATUS_N_NoSuchSOPInstance;
  }
}

void DVPSPrintSCP::filmBoxNSet(T_DIMSE_Message& rq, DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  OFBool usePLUTinFilmBox = OFFalse;
  if (assoc && (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass)))
  {
    if (! dviface.getTargetPrinterPresentationLUTinFilmSession(cfgname)) usePLUTinFilmBox = OFTrue;
  }
  storedPrintList.printSCPBasicFilmBoxSet(dviface, cfgname, rq, rqDataset, rsp, rspDataset, usePLUTinFilmBox, presentationLUTList);
}

void DVPSPrintSCP::filmSessionNAction(T_DIMSE_Message& rq, T_DIMSE_Message& rsp)
{
  if (filmSession && (filmSession->isInstance(rq.msg.NActionRQ.RequestedSOPInstanceUID)))
  {
    storedPrintList.printSCPBasicFilmSessionAction(dviface, cfgname, rsp, presentationLUTList);
  } else {
    // film session does not exist or wrong instance UID
    DCMPSTAT_WARN("cannot print film session, object not found.");
    rsp.msg.NActionRSP.DimseStatus = STATUS_N_NoSuchSOPInstance;
  }
}

void DVPSPrintSCP::filmBoxNAction(T_DIMSE_Message& rq, T_DIMSE_Message& rsp)
{
  storedPrintList.printSCPBasicFilmBoxAction(dviface, cfgname, rq, rsp, presentationLUTList);
}

void DVPSPrintSCP::filmSessionNCreate(DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  if (filmSession)
  {
    // film session exists already, refuse n-create
    DCMPSTAT_WARN("cannot create two film sessions concurrently.");
    rsp.msg.NCreateRSP.DimseStatus = STATUS_N_DuplicateSOPInstance;
    rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
  } else {
    OFBool usePLUTinFilmSession = OFFalse;
    if (assoc && (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass)))
    {
      if (dviface.getTargetPrinterPresentationLUTinFilmSession(cfgname)) usePLUTinFilmSession = OFTrue;
    }

    DVPSFilmSession *newSession = new DVPSFilmSession(DEFAULT_illumination, DEFAULT_reflectedAmbientLight);
    if (newSession)
    {
      DIC_AE peerTitle;
      peerTitle[0]=0;
      ASC_getAPTitles(assoc->params, peerTitle, sizeof(peerTitle), NULL, 0, NULL, 0);
      if (newSession->printSCPCreate(dviface, cfgname, rqDataset, rsp, rspDataset,
          peerTitle, usePLUTinFilmSession, presentationLUTList))
          filmSession = newSession;
      char uid[100];
      studyInstanceUID.putString(dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT));
      psSeriesInstanceUID.putString(dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT));
      imageSeriesInstanceUID.putString(dcmGenerateUniqueIdentifier(uid));
    } else {
      DCMPSTAT_WARN("cannot create film session, out of memory.");
      rsp.msg.NCreateRSP.DimseStatus = STATUS_N_ProcessingFailure;
      rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
    }
  }
}

void DVPSPrintSCP::filmBoxNCreate(DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  if (filmSession)
  {
    if (storedPrintList.haveFilmBoxInstance(rsp.msg.NCreateRSP.AffectedSOPInstanceUID))
    {
      DCMPSTAT_WARN("cannot create film box, requested SOP instance UID already in use.");
      rsp.msg.NCreateRSP.DimseStatus = STATUS_N_DuplicateSOPInstance;
      rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
    } else {
      DVPSStoredPrint *newSPrint = new DVPSStoredPrint(DEFAULT_illumination, DEFAULT_reflectedAmbientLight);
      if (newSPrint)
      {
        if (assoc) newSPrint->setOriginator(assoc->params->DULparams.callingAPTitle);

        // get AETITLE from config file
        const char *aetitle = dviface.getTargetAETitle(cfgname);
        if (aetitle==NULL) aetitle = dviface.getNetworkAETitle(); // default if AETITLE is missing
        newSPrint->setDestination(aetitle);
        newSPrint->setPrinterName(cfgname);

        OFBool usePLUTinFilmBox = OFFalse;
        OFBool usePLUTinFilmSession = OFFalse;
        if (assoc && (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass)))
        {
          if (dviface.getTargetPrinterPresentationLUTinFilmSession(cfgname)) usePLUTinFilmSession = OFTrue;
          else usePLUTinFilmBox = OFTrue;
        }
        if (newSPrint->printSCPCreate(dviface, cfgname, rqDataset, rsp, rspDataset, usePLUTinFilmBox,
          presentationLUTList, filmSession->getUID(), studyInstanceUID, psSeriesInstanceUID, imageSeriesInstanceUID))
        {
          if (usePLUTinFilmSession)
          {
            filmSession->copyPresentationLUTSettings(*newSPrint); // update P-LUT settings from film session
          }
          storedPrintList.insert(newSPrint);
        } else delete newSPrint;
      } else {
        DCMPSTAT_WARN("cannot create film box, out of memory.");
        rsp.msg.NCreateRSP.DimseStatus = STATUS_N_ProcessingFailure;
        rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
      }
    }
  } else {
    // no film session, refuse n-create
    DCMPSTAT_WARN("cannot create film box without film session.");
    rsp.msg.NCreateRSP.DimseStatus = STATUS_N_InvalidSOPInstance;
    rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
  }
}

void DVPSPrintSCP::presentationLUTNCreate(DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  if ((assoc==NULL) || (0 == ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass)))
  {
    DCMPSTAT_WARN("cannot create presentation LUT, not negotiated.");
    rsp.msg.NCreateRSP.DimseStatus = STATUS_N_NoSuchSOPClass;
    rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
  }
  else if (presentationLUTList.findPresentationLUT(rsp.msg.NCreateRSP.AffectedSOPInstanceUID))
  {
    DCMPSTAT_WARN("cannot create presentation LUT, requested SOP instance UID already in use.");
    rsp.msg.NCreateRSP.DimseStatus = STATUS_N_DuplicateSOPInstance;
    rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
  } else {
    DVPSPresentationLUT *newPLut = new DVPSPresentationLUT();
    if (newPLut)
    {
      OFBool matchRequired = dviface.getTargetPrinterPresentationLUTMatchRequired(cfgname);
      OFBool supports12Bit = dviface.getTargetPrinterSupports12BitTransmission(cfgname);
      if (newPLut->printSCPCreate(rqDataset, rsp, rspDataset, matchRequired, supports12Bit))
      {
        presentationLUTList.insert(newPLut);
      } else delete newPLut;
    } else {
      DCMPSTAT_WARN("cannot create presentation LUT, out of memory.");
      rsp.msg.NCreateRSP.DimseStatus = STATUS_N_ProcessingFailure;
      rsp.msg.NCreateRSP.opts = 0;  // don't include affected SOP instance UID
    }
  }
}

void DVPSPrintSCP::filmSessionNDelete(T_DIMSE_Message& rq, T_DIMSE_Message& rsp)
{
  if (filmSession && (filmSession->isInstance(rq.msg.NDeleteRQ.RequestedSOPInstanceUID)))
  {
    // delete film box hierarchy and film session object
    storedPrintList.clear();
    delete filmSession;
    filmSession = NULL;
  } else {
    // film session does not exist or wrong instance UID
    DCMPSTAT_WARN("cannot delete film session with instance UID '" << rq.msg.NDeleteRQ.RequestedSOPInstanceUID << "': object does not exist.");
    rsp.msg.NDeleteRSP.DimseStatus = STATUS_N_NoSuchSOPInstance;
  }
}

void DVPSPrintSCP::filmBoxNDelete(T_DIMSE_Message& rq, T_DIMSE_Message& rsp)
{
  storedPrintList.printSCPBasicFilmBoxDelete(rq, rsp);
}

void DVPSPrintSCP::presentationLUTNDelete(T_DIMSE_Message& rq, T_DIMSE_Message& rsp)
{
  // check whether references to this object exist. In this case, don't delete
  if (storedPrintList.usesPresentationLUT(rq.msg.NDeleteRQ.RequestedSOPInstanceUID))
  {
    DCMPSTAT_WARN("cannot delete presentation LUT '" << rq.msg.NDeleteRQ.RequestedSOPInstanceUID << "': object still in use.");
    rsp.msg.NDeleteRSP.DimseStatus = STATUS_N_ProcessingFailure;
  } else {
    presentationLUTList.printSCPDelete(rq, rsp);
  }
}

void DVPSPrintSCP::imageBoxNSet(T_DIMSE_Message& rq, DcmDataset *rqDataset, T_DIMSE_Message& rsp, DcmDataset *& rspDataset)
{
  OFBool usePLUT = OFFalse;
  if (assoc && (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PresentationLUTSOPClass))) usePLUT = OFTrue;
  storedPrintList.printSCPBasicGrayscaleImageBoxSet(dviface, cfgname, rq, rqDataset, rsp, rspDataset, usePLUT);
}

void DVPSPrintSCP::addLogEntry(DcmSequenceOfItems *seq, const char *text)
{
  if ((!seq)||(!text)) return;

  DcmItem *newItem = new DcmItem();
  if (!newItem) return;

  OFString aString;
  DcmElement *logEntryType = new DcmCodeString(PSTAT_DCM_LogEntryType);
  if (logEntryType)
  {
    logEntryType->putString(text);
    newItem->insert(logEntryType, OFTrue /*replaceOld*/);
  }

  DVPSHelper::currentDate(aString);
  DcmElement *logDate = new DcmDate(PSTAT_DCM_LogDate);
  if (logDate)
  {
    logDate->putString(aString.c_str());
    newItem->insert(logDate, OFTrue /*replaceOld*/);
  }

  DVPSHelper::currentTime(aString);
  DcmElement *logTime = new DcmTime(PSTAT_DCM_LogTime);
  if (logTime)
  {
    logTime->putString(aString.c_str());
    newItem->insert(logTime, OFTrue /*replaceOld*/);
  }
  seq->insert(newItem);
}


void DVPSPrintSCP::setDimseLogPath(const char *fname)
{
  if (fname)
  {
    logPath = fname;
    if (logSequence == NULL) logSequence = new DcmSequenceOfItems(PSTAT_DCM_LogSequence);
    if (acseSequence == NULL) acseSequence = new DcmSequenceOfItems(PSTAT_DCM_AcseSequence);
  } else {
    logPath.clear();
    delete logSequence;
    logSequence = NULL;
  }
}

void DVPSPrintSCP::saveDimseLog()
{
  if (logSequence == NULL) return;

  DcmFileFormat fformat;
  DcmDataset *dset = fformat.getDataset();
  if (! dset) return;

  dset->insert(logSequence, OFTrue /*replaceOld*/);
  logSequence = NULL;
  if (acseSequence) dset->insert(acseSequence, OFTrue /*replaceOld*/);
  acseSequence = NULL;

  char uid[80];
  OFString aString;

  const char *aetitle = dviface.getTargetAETitle(cfgname);
  if (aetitle==NULL) aetitle = dviface.getNetworkAETitle(); // default if AETITLE is missing
  aString = OFFIS_DTK_IMPLEMENTATION_VERSION_NAME;
  aString += " ";
  aString += aetitle;

  DcmElement *logReserve = new DcmLongString(PSTAT_DCM_LogReservation);
  if (logReserve)
  {
    logReserve->putString(aString.c_str());
    dset->insert(logReserve, OFTrue /*replaceOld*/);
  }

  DVPSHelper::putStringValue(dset, DCM_SOPClassUID, PSTAT_DIMSE_LOG_STORAGE_UID);
  DVPSHelper::putStringValue(dset, DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid));
  DVPSHelper::currentDate(aString);
  DVPSHelper::putStringValue(dset, DCM_InstanceCreationDate, aString.c_str());
  DVPSHelper::currentTime(aString);
  DVPSHelper::putStringValue(dset, DCM_InstanceCreationTime, aString.c_str());

  OFCondition cond = DVPSHelper::saveFileFormat(logPath.c_str(), &fformat, OFTrue);
  if (cond == EC_Normal)
  {
    DCMPSTAT_INFO("DIMSE communication log stored in in DICOM file '" << logPath << "'.");
  } else {
    DCMPSTAT_WARN("unable to store DIMSE communication log in file '" << logPath << "'.");
  }

  logPath.clear();
}

void DVPSPrintSCP::dumpNMessage(T_DIMSE_Message &msg, DcmItem *dataset, OFBool outgoing)
{
    OFString str;
    if (outgoing) {
        DIMSE_dumpMessage(str, msg, DIMSE_OUTGOING, dataset);
    } else {
        DIMSE_dumpMessage(str, msg, DIMSE_INCOMING, dataset);
    }
    DCMPSTAT_DUMP(str);
}
