/* ----------------------------- MNI Header -----------------------------------
@NAME       : reply.c
@DESCRIPTION: Routines for dealing with dicom messages.
@GLOBALS    : 
@CREATED    : January 28, 1997 (Peter Neelin)
@MODIFIED   : 
 * $Log: reply.c,v $
 * Revision 6.4  2002-12-08 20:45:34  neelin
 * Added new implementation class uid for Siemens MREASE_VA21A
 *
 * Revision 6.3  1999/10/29 17:51:57  neelin
 * Fixed Log keyword
 *
 * Revision 6.2  1999/08/05 20:01:16  neelin
 * Check for broken Siemens software using a list of implementation UIDs.
 *
 * Revision 6.1  1998/05/19  19:27:43  neelin
 * Test for Siemens Vision machine by looking for implementation uid
 * rather than AE title
 *
 * Revision 6.0  1997/09/12  13:24:27  neelin
 * Release of minc version 0.6
 *
 * Revision 5.0  1997/08/21  13:25:26  neelin
 * Release of minc version 0.5
 *
 * Revision 4.1  1997/07/08  23:15:09  neelin
 * Added support for C_ECHO command.
 *
 * Revision 4.0  1997/05/07  20:06:20  neelin
 * Release of minc version 0.4
 *
 * Revision 1.1  1997/03/04  20:56:47  neelin
 * Initial revision
 *
@COPYRIGHT  :
              Copyright 1997 Peter Neelin, McConnell Brain Imaging Centre, 
              Montreal Neurological Institute, McGill University.
              Permission to use, copy, modify, and distribute this
              software and its documentation for any purpose and without
              fee is hereby granted, provided that the above copyright
              notice appear in all copies.  The author and McGill University
              make no representations about the suitability of this
              software for any purpose.  It is provided "as is" without
              express or implied warranty.
---------------------------------------------------------------------------- */

#include <dicomserver.h>

extern int Do_logging;

/* List of implementation UIDs for Siemens Vision scanners with broken 
   handling of transfer syntax. These should be in ascending order of
   software version, since the UID for version VB33A is used to identify 
   a change in element use. */
static char *SPI_Vision_Implementation_UIDs[] = {
   "2.16.840.1.113669.2.931128",
   "1.3.12.2.1107.5.1995.1",          /* Version VB33A */
   "1.3.12.2.1107.5.2",               /* Version MREASE_VA21A */ 
   NULL
};
/* Index into above array for version VB33A */
#define SPI_VISION_VB33A_INDEX 1
/* Global to indicate whether we have a pre VB33A version */
int SPI_Vision_version_pre33A = TRUE;

/* Macros */
#define SAVE_SHORT(group, elid, value) \
   acr_group_add_element(group, \
      acr_create_element_short(elid, (unsigned short) (value)))

/* ----------------------------- MNI Header -----------------------------------
@NAME       : uid_equal
@INPUT      : uid1
              uid2
@OUTPUT     : (nothing)
@RETURNS    : TRUE if uid's are equal, FALSE otherwise
@DESCRIPTION: Responds to READYq message
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : February 21, 1997 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
private int uid_equal(char *uid1, char *uid2)
{
   int len1, len2, i;

   len1 = strlen(uid1);
   len2 = strlen(uid2);

   /* Skip leading blanks */
   while (isspace(*uid1)) {uid1++;}
   while (isspace(*uid2)) {uid2++;}

   /* Skip trailing blanks */
   for (i=len1-1; (i >= 0) && isspace(uid1[i]); i++) {}
   if (isspace(uid1[i+1])) uid1[i+1] = '\0';
   for (i=len2-1; (i >= 0) && isspace(uid1[i]); i++) {}
   if (isspace(uid1[i+1])) uid1[i+1] = '\0';

   /* Compare the strings */
   return (strcmp(uid1, uid2) == 0);
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : make_message
@INPUT      : group_list
@OUTPUT     : (nothing)
@RETURNS    : output message.
@DESCRIPTION: Convert a group list into a message.
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : November 24, 1993 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
private Acr_Message make_message(Acr_Group group_list)
{
   Acr_Group next_group, group;
   Acr_Message output_message;

   /* Create the output message */
   output_message = acr_create_message();

   /* Loop through groups, adding them to the message */
   group = group_list;
   while (group != NULL) {
      next_group = acr_get_group_next(group);
      acr_set_group_next(group, NULL);
      acr_message_add_group(output_message, group);
      group = next_group;
   }

   return output_message;

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : associate_reply
@INPUT      : input_message
@OUTPUT     : project_name - name to use for project file
              pres_context_id - presentation context id to use for output. If
                 this is < 0, then an error occurred.
              byte_order - byte order to use for messages
              vr_encoding - VR encoding to use for messages
              maximum_length - maximum length of output PDU's
@RETURNS    : output_message
@DESCRIPTION: Responds to an associate request message
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : November 22, 1993 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
public Acr_Message associate_reply(Acr_Message input_message, 
                                   char **project_name,
                                   int *pres_context_id,
                                   Acr_byte_order *byte_order,
                                   Acr_VR_encoding_type *vr_encoding,
                                   long *maximum_length)
{
   Acr_Group group, input_group;
   Acr_Element element, item, subitem, sublist;
   Acr_Element out_item, out_subitem, out_sublist, out_list;
   int found_best;
   int best_pres_context_id, cur_pres_context_id;
   int best_transfer_syntax_priority;
   int use_implicit_little_endian;
   char *best_abstract_syntax, *best_transfer_syntax, *cur_syntax;
   int impuid;

   /* Print log message */
   if (Do_logging >= HIGH_LOGGING) {
      (void) fprintf(stderr, "\n\nReceived associate request message:\n");
      acr_dump_message(stderr, input_message);
   }

   /* Set default presentation context id to flag error */
   *pres_context_id = -1;

   /* Free project_name string if needed */
   if (*project_name != NULL) FREE(*project_name);

   /* Get the group list */
   input_group = acr_get_message_group_list(input_message);

   /* Get the project name from the DICOM application name */
   *project_name = strdup(acr_find_string(input_group, 
                                          DCM_PDU_Called_Ap_title, ""));

   /* Check that the project file is okay. If it is not found, try the host
      name */
   if (read_project_file(*project_name, NULL, NULL, NULL, NULL, 0)) {
      FREE(*project_name);
      *project_name = NULL;
      if (read_project_file(*project_name, NULL, NULL, NULL, NULL, 0)) {
         return associate_reply_reject(input_message, 
                                       ACR_ASSOC_RJ_CALLED_AP_TITLE_UNREC);
      }
   }

   /* Check for Siemens Vision implementation that lies about its 
      transfer syntaxes */
   use_implicit_little_endian = FALSE;
   for (impuid=0; SPI_Vision_Implementation_UIDs[impuid] != NULL; impuid++) {
      if (uid_equal(acr_find_string(input_group, 
                                    DCM_PDU_Implementation_class_uid, ""),
                    SPI_Vision_Implementation_UIDs[impuid])) {
         use_implicit_little_endian = TRUE;
         SPI_Vision_version_pre33A = (impuid < SPI_VISION_VB33A_INDEX);
         break;
      }
   }

   /* Get maximum length */
   *maximum_length = acr_find_long(input_group, DCM_PDU_Maximum_length, 0L);

   /* Get presentation context list */
   best_pres_context_id = -1;
   best_abstract_syntax = NULL;
   element = 
      acr_find_group_element(input_group, DCM_PDU_Presentation_context_list);
   if ((element == NULL) || !acr_element_is_sequence(element)) {
      (void) fprintf(stderr, "No presentation context list found\n");
      return associate_reply_reject(input_message, ACR_ASSOC_RJ_NO_REASON);
   }

   /* Loop over presentation contexts */
   found_best = FALSE;
   for (item = (Acr_Element) acr_get_element_data(element);
        (item != NULL) && acr_element_is_sequence(item) && !found_best;
        item = acr_get_element_next(item)) {

      /* Get presentation context info */
      sublist = (Acr_Element) acr_get_element_data(item);

      /* Get abstract syntax */
      subitem = acr_find_element_id(sublist, 
                                    DCM_PDU_Abstract_syntax);
      if (subitem == NULL) continue;
      cur_syntax = acr_get_element_string(subitem);

      /* Check whether this is either MR abstract syntax or we have
         already found one (we take the first one if we cannot find
         the one that we want) */
      if (uid_equal(cur_syntax, FAVORITE_ABSTRACT_SYNTAX))
         found_best = TRUE;
      else if (best_abstract_syntax != NULL) 
         continue;

      /* Save the abstract syntax */
      best_abstract_syntax = cur_syntax;

      /* Get presentation context id */
      subitem = acr_find_element_id(sublist, 
                                    DCM_PDU_Presentation_context_id);
      if (subitem != NULL)
         best_pres_context_id = acr_get_element_short(subitem);
      else {
         (void) fprintf(stderr, 
                        "No presentation context - internal error\n");
         return associate_reply_reject(input_message, ACR_ASSOC_RJ_NO_REASON);
      }

      /* Look for an appropriate transfer syntax */
      best_transfer_syntax = NULL;
      best_transfer_syntax_priority = 0;
      for (subitem = sublist;
           subitem != NULL;
           subitem=acr_find_element_id(acr_get_element_next(subitem), 
                                       DCM_PDU_Transfer_syntax)) {

         /* Check for syntaxes in descending order of preference */
         cur_syntax = acr_get_element_string(subitem);
         if (uid_equal(cur_syntax, ACR_EXPLICIT_VR_BIG_END_UID)) {
            if (best_transfer_syntax_priority < 3) {
               best_transfer_syntax_priority = 3;
               best_transfer_syntax = cur_syntax;
            }
         }
         else if (uid_equal(cur_syntax, ACR_EXPLICIT_VR_LITTLE_END_UID)) {
            if (best_transfer_syntax_priority < 2) {
               best_transfer_syntax_priority = 2;
               best_transfer_syntax = cur_syntax;
            }
         }
         else if (uid_equal(cur_syntax, ACR_IMPLICIT_VR_LITTLE_END_UID)) {
            if (best_transfer_syntax_priority < 1) {
               best_transfer_syntax_priority = 1;
               best_transfer_syntax = cur_syntax;
            }
         }

      }         /* Loop over transfer syntaxes */
   
   }            /* Loop over presentation contexts */


   /* Check for machines that lie about their ability to do 
      different transfer syntaxes */
   if (use_implicit_little_endian) {
      best_transfer_syntax = ACR_IMPLICIT_VR_LITTLE_END_UID;
   }


   /* Check that we found something useful */
   if ((best_pres_context_id < 0) || (best_abstract_syntax == NULL) ||
       (best_transfer_syntax == NULL)) {
      (void) fprintf(stderr, 
                     "Did not find understandable presentation context\n");
      return associate_reply_reject(input_message, ACR_ASSOC_RJ_NO_REASON);
   }

   /* Set the transfer syntax information */
   if (uid_equal(best_transfer_syntax, ACR_EXPLICIT_VR_BIG_END_UID)) {
      *byte_order = ACR_BIG_ENDIAN;
      *vr_encoding = ACR_EXPLICIT_VR;
   }
   else if (uid_equal(best_transfer_syntax, ACR_EXPLICIT_VR_LITTLE_END_UID)) {
      *byte_order = ACR_LITTLE_ENDIAN;
      *vr_encoding = ACR_EXPLICIT_VR;
   }
   else if (uid_equal(best_transfer_syntax, ACR_IMPLICIT_VR_LITTLE_END_UID)) {
      *byte_order = ACR_LITTLE_ENDIAN;
      *vr_encoding = ACR_IMPLICIT_VR;
   }
   else {
      (void) fprintf(stderr, 
                     "Did not understand transfer syntax.\n");
      return associate_reply_reject(input_message, ACR_ASSOC_RJ_NO_REASON);
   }

   /* Create the reply */
   group = acr_create_group(DCM_PDU_GRPID);

   /* Save the PDU type */
   SAVE_SHORT(group, DCM_PDU_Type, ACR_PDU_ASSOC_AC);

   /* Save the caller and calling AE titles */
   acr_group_add_element(group,
      acr_create_element_string(DCM_PDU_Called_Ap_title,
         acr_find_string(input_group, DCM_PDU_Called_Ap_title, "")));
   acr_group_add_element(group,
      acr_create_element_string(DCM_PDU_Calling_Ap_title,
         acr_find_string(input_group, DCM_PDU_Calling_Ap_title, "")));

   /* Add the application context name */
   acr_group_add_element(group,
      acr_create_element_string(DCM_PDU_Application_context,
         acr_find_string(input_group, DCM_PDU_Application_context, 
                         ACR_APPLICATION_CONTEXT_UID)));

   /* Loop over presentation contexts */
   item = (Acr_Element) 
      acr_get_element_data
         (acr_find_group_element(input_group, 
                                 DCM_PDU_Presentation_context_list));
   out_list = NULL;
   for (;(item != NULL); item = acr_get_element_next(item)) {

      if (!acr_element_is_sequence(item)) continue;

      /* Get presentation context info */
      sublist = (Acr_Element) acr_get_element_data(item);

      /* Save the id */
      subitem = acr_find_element_id(sublist, 
                                    DCM_PDU_Presentation_context_id);
      if (subitem == NULL) continue;
      cur_pres_context_id = acr_get_element_short(subitem);

      /* Create the presentation context */
      out_sublist = NULL;
      out_subitem = acr_create_element_short(DCM_PDU_Presentation_context_id,
                                             cur_pres_context_id);
      out_sublist = acr_element_list_add(out_sublist, out_subitem);

      /* Accept or reject */
      out_subitem = acr_create_element_short(DCM_PDU_Result,
         ((cur_pres_context_id == best_pres_context_id) ?
          ACR_ASSOC_PR_CN_ACCEPT : ACR_ASSOC_PR_CN_REJECT));
      out_sublist = acr_element_list_add(out_sublist, out_subitem);
        
      /* Add the transfer syntax */
      out_subitem = acr_create_element_string(DCM_PDU_Transfer_syntax,
                                              best_transfer_syntax);
      out_sublist = acr_element_list_add(out_sublist, out_subitem);

      /* Add this context to the list */
      out_item = 
         acr_create_element_sequence(DCM_PDU_Presentation_context_reply, 
                                     out_sublist);
      out_list = acr_element_list_add(out_list, out_item);
   }

   /* Create the presentation context list element */
   element = 
      acr_create_element_sequence(DCM_PDU_Presentation_context_reply_list, 
                                  out_list);
   acr_group_add_element(group, element);

   /* Add the user information */
   acr_group_add_element(group, 
                         acr_create_element_long(DCM_PDU_Maximum_length, 0L));

   /* Set the presentation context id to indicate success */
   *pres_context_id = best_pres_context_id;

   return make_message(group);

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : associate_reply_reject
@INPUT      : input_message
@OUTPUT     : reason
@RETURNS    : output_message
@DESCRIPTION: Responds to an associate request message with a rejection
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : February 21, 1997 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
/* ARGSUSED */
public Acr_Message associate_reply_reject(Acr_Message input_message, 
                                          int reason)
{
   Acr_Group group;

   /* Create the reply */
   group = acr_create_group(DCM_PDU_GRPID);

   /* Save the PDU type */
   SAVE_SHORT(group, DCM_PDU_Type, ACR_PDU_ASSOC_RJ);

   /* Give the result, source and reason */
   SAVE_SHORT(group, DCM_PDU_Result, ACR_ASSOC_RJ_PERM);
   SAVE_SHORT(group, DCM_PDU_Source, ACR_ASSOC_RJ_USER);
   SAVE_SHORT(group, DCM_PDU_Reason, reason);

   return make_message(group);
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : release_reply
@INPUT      : input_message
@OUTPUT     : (nothing)
@RETURNS    : output_message
@DESCRIPTION: Responds to READYq message
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : November 22, 1993 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
public Acr_Message release_reply(Acr_Message input_message)
{
   Acr_Group group;

   /* Print log message */
   if (Do_logging >= HIGH_LOGGING) {
      (void) fprintf(stderr, "\n\nReceived release request message:\n");
      acr_dump_message(stderr, input_message);
   }

   /* Create the reply */
   group = acr_create_group(DCM_PDU_GRPID);

   /* Save the PDU type */
   SAVE_SHORT(group, DCM_PDU_Type, ACR_PDU_REL_RP);

   return make_message(group);
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : abort_reply
@INPUT      : input_message
@OUTPUT     : (nothing)
@RETURNS    : output_message
@DESCRIPTION: Responds to GCENDq message
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : November 22, 1993 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
public Acr_Message abort_reply(Acr_Message input_message)
{
   Acr_Group group;

   /* Print log message */
   if (Do_logging >= HIGH_LOGGING) {
      (void) fprintf(stderr, "\n\nReceived abort message:\n");
      acr_dump_message(stderr, input_message);
   }

   /* Create the reply */
   group = acr_copy_group_list(acr_get_message_group_list(input_message));

   return make_message(group);
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : data_reply
@INPUT      : input_message
              file_prefix
@OUTPUT     : new_file_name (must be freed by caller)
@RETURNS    : output_message
@DESCRIPTION: Responds to SENDq message
@METHOD     : 
@GLOBALS    : 
@CALLS      : 
@CREATED    : November 22, 1993 (Peter Neelin)
@MODIFIED   : 
---------------------------------------------------------------------------- */
public Acr_Message data_reply(Acr_Message input_message)
{
   Acr_Group group, input_group;
   int reply_command;

   /* Print log message */
   if (Do_logging >= HIGH_LOGGING) {
      (void) fprintf(stderr, "\n\nReceived data message:\n");
      acr_dump_message(stderr, input_message);
   }

   /* Get the input group list */
   input_group = acr_get_message_group_list(input_message);

   /* Figure out the reply that we need */
   switch (acr_find_short(input_group, ACR_Command, -1)) {
   case ACR_C_STORE_RQ:
      reply_command = ACR_C_STORE_RSP; break;
   case ACR_C_ECHO_RQ:
      reply_command = ACR_C_ECHO_RSP; break;
   default:
      reply_command = ACR_C_ECHO_RSP; break;
   }

   /* Create the reply */
   group = acr_create_group(ACR_MESSAGE_GID);

   /* Save appropriate stuff */
   acr_group_add_element(group,
      acr_create_element_string(ACR_Affected_SOP_class_UID, 
         acr_find_string(input_group, ACR_Affected_SOP_class_UID, "")));
   SAVE_SHORT(group, ACR_Command, reply_command);
   SAVE_SHORT(group, ACR_Message_id_brt, 
              acr_find_short(input_group, ACR_Message_id, 0));
   SAVE_SHORT(group, ACR_Dataset_type, ACR_NULL_DATASET);
   SAVE_SHORT(group, ACR_Status, ACR_SUCCESS);
   if (reply_command == ACR_C_STORE_RSP) {
      acr_group_add_element(group,
         acr_create_element_string(ACR_Affected_SOP_instance_UID, 
            acr_find_string(input_group, ACR_Affected_SOP_instance_UID, "")));
   }

   return make_message(group);

}

