/*******************************************************************************
*
*  McStas, neutron ray-tracing package
*  Copyright(C) 2007 Risoe National Laboratory.
*
* %I
* Written by: Mads Bertelsen
* Date: 20.08.15
* Version: $Revision: 0.1 $
* Origin: University of Copenhagen
*
* A conditional component that filter on basic variables of exit state
*
* %D
* Part of the Union components, a set of components that work together and thus
*  sperates geometry and physics within McStas.
* The use of this component requires other components to be used.
*
* 1) One specifies a number of processes using process components
* 2) These are gathered into material definitions using Union_make_material
* 3) Geometries are placed using Union_box / Union_cylinder, assigned a material
* 4) A Union_master component placed after all of the above
*
* Only in step 4 will any simulation happen, and per default all geometries
*  defined before this master, but after the previous will be simulated here.
*
* There is a dedicated manual available for the Union_components
*
* This is a conditional component that affects the loggers in the target_loggers
*  string. When a logger is affected, it will only record events if the
*  conditional is true at the end of the simulation of a ray in the master.
* Conditionals can be used to for example limit the loggers to rays that end
*  within a certain energy range, time interval or similar.
*
* One can apply several conditionals to each logger if desired.
*
* In the extend section of a master, the tagging conditional can be acsessed by
*  the variable name tagging_conditional_extend. Beware, that it only works as
*  long as the tagging system is active, so you may want to increase the number
*  of histories allowed by that master component before stopping.
*
* overwrite_logger_weight can be used to force the loggers this conditional
*  controls to write the final weight for each scattering event, instead of the
*  recorded value.
*
* %P
* INPUT PARAMETERS:
* target_loggers:       [string] Comma seperated list of loggers this conditional should affect
* master_tagging:       [1]      Apply this conditional to the tagging system in next master
* tagging_extend_index: [1]      Not currently used
* time_min:             [s]      Min time, if not set ignored
* time_max:             [s]      Max time, if not set ignored
* E_min:                [meV]    Max energy, if not set ignored
* E_max:                [meV]    Min energy, if not set ignored
* total_order_min:      [1]      Min scattering order, if not set ignored
* total_order_max:      [1]      Max scattering order, if not set ignored
* overwrite_logger_weight: [1]   Default = 0, overwrite = 1
* last_volume_index:    [1]      Not used yet
* init:                 [string] Name of Union_init component (typically "init", default)
*
*
* CALCULATED PARAMETERS:
*
* GLOBAL PARAMETERS:
*
* %L
*
* %E
******************************************************************************/

DEFINE COMPONENT Union_conditional_standard

SETTING PARAMETERS(string target_loggers="NULL", int master_tagging=0, int tagging_extend_index = -1, time_min=0, time_max=0, E_min=0, E_max=0, total_order_min=0, total_order_max=0, last_volume_index=-1,overwrite_logger_weight=0, string init="init") // E limit, |k| limit, volumes to have visited, processes that was used, end volume, last process


/* Neutron parameters: (x,y,z,vx,vy,vz,t,sx,sy,sz,p) */

SHARE
%{
  #ifndef Union
  #error "The Union_init component must be included before this Union_conditional_standard component"
  #endif

  int
  conditional_function_standard (union conditional_data_union* data_union, Coords* position, Coords* velocity, double* weight, double* time, int* current_volume,
                                 int* total_number_of_scattering, int* number_of_scattering_per_volume, int** number_of_scattering_per_volume_process) {

    // printf(" Inside conditional function \n");
    if (data_union->p_standard->T_limit == 1) {
      if (*time < data_union->p_standard->Tmin || *time > data_union->p_standard->Tmax)
        return 0;
    }

    if (data_union->p_standard->E_limit == 1) {
      // VS2E = 5.22704E-6 m_n / 2e
      double E = 5.22704E-6 * (velocity->x * velocity->x + velocity->y * velocity->y + velocity->z * velocity->z);
      if (E < data_union->p_standard->Emin || E > data_union->p_standard->Emax)
        return 0;
    }

    if (data_union->p_standard->Total_scat_limit == 1) {
      if (*total_number_of_scattering < data_union->p_standard->Total_scat_min || *total_number_of_scattering > data_union->p_standard->Total_scat_max)
        return 0;
    }

    if (data_union->p_standard->volume_index != -1) {
      if (*current_volume != data_union->p_standard->volume_index)
        return 0;
    }

    // All conditions fulfilled
    return 1;
  }

  // Only need to define linking function for conditionals once.
  // Does not need to redefine functions from: make_material / loggers, as one of each needs to be defined before.
  #ifndef UNION_CONDITIONAL
  #define UNION_CONDITIONAL Dummy
  // Linking function for loggers, finds the indicies of the specified loggers on global_logger_lists
  int
  manual_linking_logger_function_conditional (char* input_string, struct pointer_to_global_logger_list* global_logger_list,
                                              struct pointer_to_1d_int_list* accepted_loggers) {
    // Need to check a input_string of text for an occurance of name. If it is in the inputstring, yes return 1, otherwise 0.
    char* token;
    int loop_index;
    char local_string[512];

    strcpy (local_string, input_string);
    // get the first token
    token = strtok (local_string, ",");

    // walk through other tokens
    while (token != NULL) {
      // printf( " %s\n", token );
      for (loop_index = 0; loop_index < global_logger_list->num_elements; loop_index++) {
        if (strcmp (token, global_logger_list->elements[loop_index].name) == 0) {
          add_element_to_int_list (accepted_loggers, loop_index);
          break;
        }
      }

      // Updates the token
      token = strtok (NULL, ",");
    }

    return accepted_loggers->num_elements;
  }

  int
  manual_linking_abs_logger_function_conditional (char* input_string, struct pointer_to_global_abs_logger_list* global_abs_logger_list,
                                                  struct pointer_to_1d_int_list* accepted_abs_loggers) {
    // Need to check a input_string of text for an occurance of name. If it is in the inputstring, yes return 1, otherwise 0.
    char* token;
    int loop_index;
    char local_string[512];

    strcpy (local_string, input_string);
    // get the first token
    token = strtok (local_string, ",");

    // walk through other tokens
    while (token != NULL) {
      // printf( " %s\n", token );
      for (loop_index = 0; loop_index < global_abs_logger_list->num_elements; loop_index++) {
        if (strcmp (token, global_abs_logger_list->elements[loop_index].name) == 0) {
          add_element_to_int_list (accepted_abs_loggers, loop_index);
          break;
        }
      }

      // Updates the token
      token = strtok (NULL, ",");
    }

    return accepted_abs_loggers->num_elements;
  }

  /*
  // Linking function for masters, finds the indicies of the specified masters on the global_master_list
  int manual_linking_tagging_function_conditional(char *input_string, struct global_tagging_conditional_list_struct *global_tagging_list, struct
  pointer_to_1d_int_list *accepted_tagging) {
      // Need to check a input_string of text for an occurance of name. If it is in the inputstring, yes return 1, otherwise 0.
     char *token;
     int loop_index,found;
     char local_string[512];

     strcpy(local_string,input_string);
     // get the first token
     token = strtok(local_string,",");

     // walk through other tokens
     while( token != NULL )
     {
        found = 0;
        for (loop_index=0;loop_index<global_tagging_list->num_elements;loop_index++) {
          if (strcmp(token,global_tagging_list->elements[loop_index].name) == 0) {
            add_element_to_int_list(accepted_tagging,loop_index);
            found = 1;
            break;
          }
        }

        if (found == 0) {
          add_element_to_int_list(accepted_tagging,loop_index);
          add_element_to_tagging_conditional_list(

          add_function_to_conditional_list(global_tagging_list.elements[accepted_tagging.elements[loop_index]].conditional_list,&conditional_function_standard,&this_data_union);
        }

        // Updates the token
        token = strtok(NULL,",");
     }

     return accepted_tagging->num_elements;
  }
  */

  int
  count_commas (char* string) {
    int return_value = 0;

    int index;
    for (index = 0; index < strlen (string); index++) {
      // printf("%c \n",string[index]);
      if (string[index] == ',')
        return_value++;
    }

    // printf("number_of_commas = %d \n",return_value);
    return return_value;
  }
  #endif
%}

DECLARE
%{
  int loop_index;

  // This function pointer is used to switch the logger function pointers for active and inactive logger functions.
  // Need to have the same input (defined in logger_pointer_set_struct in union-lib.c)
  // void (*temp_function_pointer)(Coords*, double*, double*, double, double, double, int, int, int, struct logger_struct*, struct logger_with_data_struct*);
  int return_value;
  struct pointer_to_1d_int_list accepted_loggers_all;
  struct pointer_to_1d_int_list accepted_loggers_specific;
  struct pointer_to_1d_int_list accepted_abs_loggers_all;
  struct pointer_to_1d_int_list accepted_abs_loggers_specific;
  // struct pointer_to_1d_int_list accepted_tagging = {0,NULL};

  struct logger_struct* found_logger;
  struct abs_logger_struct* found_abs_logger;

  struct conditional_standard_struct this_conditional_data;
  union conditional_data_union this_data_union;

  struct global_tagging_conditional_element_struct* new_tagging_element;
%}

INITIALIZE
%{
  accepted_loggers_all.elements = NULL;
  accepted_loggers_all.num_elements = 0;
  accepted_loggers_specific.elements = NULL;
  accepted_loggers_specific.num_elements = 0;
  accepted_abs_loggers_all.elements = NULL;
  accepted_abs_loggers_all.num_elements = 0;
  accepted_abs_loggers_specific.elements = NULL;
  accepted_abs_loggers_specific.num_elements = 0;

  // printf("starded conditional initialize \n");
  //  Check if an E range has been set
  if (E_min == E_max)
    // If not, do not check the final energy
    this_conditional_data.E_limit = 0;
  else
    // If it has, check the final energy
    this_conditional_data.E_limit = 1;

  // Likewise for time
  if (time_min == time_max)
    this_conditional_data.T_limit = 0;
  else
    this_conditional_data.T_limit = 1;

  this_conditional_data.Tmin = time_min;
  this_conditional_data.Tmax = time_max;

  if (last_volume_index != -1)
    this_conditional_data.volume_index = last_volume_index;

  // Initialize storage from input
  if (E_min < 0) {
    printf ("ERROR, Union logger \"%s\" had E_min < 0.\n", NAME_CURRENT_COMP);
    exit (1);
  }
  this_conditional_data.Emin = E_min; // The names need to be different to avoid defines ruining the day

  // Initialize storage from input
  if (E_max < 0) {
    printf ("ERROR, Union logger \"%s\" had E_max < 0.\n", NAME_CURRENT_COMP);
    exit (1);
  }
  this_conditional_data.Emax = E_max; // The names need to be different to avoid defines ruining the day

  if (total_order_max != 0)
    this_conditional_data.Total_scat_limit = 1;
  else
    this_conditional_data.Total_scat_limit = 0;

  if (total_order_max < total_order_min) {
    printf ("ERROR, Union logger \"%s\" had total_order_max < total_oder_min.\n", NAME_CURRENT_COMP);
    exit (1);
  }
  this_conditional_data.Total_scat_max = total_order_max;
  this_conditional_data.Total_scat_min = total_order_min;

  if (_getcomp_index (init) < 0) {
    fprintf (stderr, "Union_conditional_standard:%s: Error identifying Union_init component, %s is not a known component name.\n", NAME_CURRENT_COMP, init);
    exit (-1);
  }

  struct global_positions_to_transform_list_struct* global_positions_to_transform_list = COMP_GETPAR3 (Union_init, init, global_positions_to_transform_list);
  struct global_rotations_to_transform_list_struct* global_rotations_to_transform_list = COMP_GETPAR3 (Union_init, init, global_rotations_to_transform_list);
  // Test position and rotation stored in a data storage, and pointers assigned to transform lists
  this_conditional_data.test_position = POS_A_CURRENT_COMP;
  add_position_pointer_to_list (global_positions_to_transform_list, &this_conditional_data.test_position);

  rot_copy (this_conditional_data.test_rotation, ROT_A_CURRENT_COMP);
  add_rotation_pointer_to_list (global_rotations_to_transform_list, &this_conditional_data.test_rotation);

  rot_transpose (ROT_A_CURRENT_COMP, this_conditional_data.test_t_rotation);
  add_rotation_pointer_to_list (global_rotations_to_transform_list, &this_conditional_data.test_t_rotation);

  // Set the data union to be for this specific type of conditional, and supply it with a pointer to its static data
  this_data_union.p_standard = &this_conditional_data;

  struct global_tagging_conditional_list_struct* global_tagging_conditional_list = COMP_GETPAR3 (Union_init, init, global_tagging_conditional_list);

  if (master_tagging == 1) {
    // Apply this conditional to the next master components tagging system
    // When a master is encountered, the current_index is increased, meaning it is always the next master that gets the additional conditional
    if (global_tagging_conditional_list->num_elements < global_tagging_conditional_list->current_index + 1) {
      // printf("allocating new element for global_tagging_conditional_list \n");
      //  New element needs to be allocated first
      new_tagging_element = malloc (sizeof (struct global_tagging_conditional_element_struct));
      new_tagging_element->extend_index = tagging_extend_index;
      new_tagging_element->conditional_list.num_elements = 0;
      strcpy (new_tagging_element->name, NAME_CURRENT_COMP);

      // printf("adding new element to global_tagging_conditional_list \n");
      add_element_to_tagging_conditional_list (global_tagging_conditional_list, *new_tagging_element);
      // printf("added new element to global_tagging_conditional_list \n");

    } else {
      // Making the name reflect all the conditionals used
      strcat (global_tagging_conditional_list->elements[global_tagging_conditional_list->current_index].name, "-");
      strcat (global_tagging_conditional_list->elements[global_tagging_conditional_list->current_index].name, NAME_CURRENT_COMP);
    }
    // Add the conditional element to the current list
    add_function_to_conditional_list (&global_tagging_conditional_list->elements[global_tagging_conditional_list->current_index].conditional_list,
                                      &conditional_function_standard, &this_data_union);
  }

  struct pointer_to_global_logger_list* global_specific_volumes_logger_list = COMP_GETPAR3 (Union_init, init, global_specific_volumes_logger_list);
  struct pointer_to_global_logger_list* global_all_volume_logger_list = COMP_GETPAR3 (Union_init, init, global_all_volume_logger_list);
  struct pointer_to_global_abs_logger_list* global_specific_volumes_abs_logger_list = COMP_GETPAR3 (Union_init, init, global_specific_volumes_abs_logger_list);
  struct pointer_to_global_abs_logger_list* global_all_volume_abs_logger_list = COMP_GETPAR3 (Union_init, init, global_all_volume_abs_logger_list);

  // Need to find the loggers which need to have this conditional applied
  if (target_loggers && strlen (target_loggers) && strcmp (target_loggers, "NULL") && strcmp (target_loggers, "0")) {
    // Certain loggers were selected, find the indicies in the global_specific_volumes_logger_list / global_all_volume_list
    // Create accepted_loggers_specific / accepted_loggers_all

    return_value = manual_linking_logger_function_conditional (target_loggers, global_all_volume_logger_list, &accepted_loggers_all);
    return_value += manual_linking_logger_function_conditional (target_loggers, global_specific_volumes_logger_list, &accepted_loggers_specific);
    return_value += manual_linking_abs_logger_function_conditional (target_loggers, global_all_volume_abs_logger_list, &accepted_abs_loggers_all);
    return_value += manual_linking_abs_logger_function_conditional (target_loggers, global_specific_volumes_abs_logger_list, &accepted_abs_loggers_specific);

    // return_value += manual_linking_tagging_function_conditional(target_loggers, &global_tagging_conditional_list, &accepted_tagging);

    // Need to find a list over tokens that were not taken by either all_volume or specific_volumes

    if (return_value < count_commas (target_loggers) + 1) {
      // Fewer links than tokens were made
      printf ("Union conditional component named \"%s\" did not find all loggers in it's target_logger string \"%s\". \n", NAME_CURRENT_COMP, target_loggers);
      printf ("  A conditional component needs to be linked to a logger in order to function. \n");
      printf ("  The Union logger component must be defined before the conditional that tries to link to it. \n");
      printf ("  A comma separated list of Union logger component names can be given if the conditional should affect many loggers. \n");

      exit (1);
    }

    void (*temp_function_pointer) (Coords*, double*, double*, double, double, double, int, int, int, struct logger_struct*, struct logger_with_data_struct*);
    void (*temp_abs_function_pointer) (Coords*, double*, double, double, int, int, struct abs_logger_struct*, struct abs_logger_with_data_struct*);
    for (loop_index = 0; loop_index < accepted_loggers_all.num_elements; loop_index++) {
      // printf("all loop \n");

      // For each accepted logger do these tasks:
      //  if not conditional function pointer is assigned yet, switch the active and inactive record function pointers in the logger
      //  Assing a function pointer to the conditional function pointer (maybe a list of these if more conditions are to be used)

      found_logger = global_all_volume_logger_list->elements[accepted_loggers_all.elements[loop_index]].logger;

      if (found_logger->conditional_list.num_elements == 0) {
        // Switching function pointers, only necessary for the first application of a conditional
        // This makes the logger record to a temporary storage that is transfered to the permanent in case the conditional is true istead of directly to the
        // permanent storage.
        temp_function_pointer = found_logger->function_pointers.active_record_function;
        found_logger->function_pointers.active_record_function = found_logger->function_pointers.inactive_record_function;
        found_logger->function_pointers.inactive_record_function = temp_function_pointer;
      }

      if (overwrite_logger_weight == 1)
        found_logger->function_pointers.select_t_to_p = 2;

      // Add a pointer to this conditional function to the list of conditionals for this logger
      add_function_to_conditional_list (&found_logger->conditional_list, &conditional_function_standard, &this_data_union);
    }
    for (loop_index = 0; loop_index < accepted_loggers_specific.num_elements; loop_index++) {
      // printf("specific loop \n");
      //  For each accepted logger do these tasks:
      //   if not conditional function pointer is assigned yet, switch the active and inactive record function pointers in the logger
      //   Assing a function pointer to the conditional function pointer (maybe a list of these if more conditions are to be used)

      found_logger = global_specific_volumes_logger_list->elements[accepted_loggers_specific.elements[loop_index]].logger;

      if (found_logger->conditional_list.num_elements == 0) {
        // Switching function pointers, only necessary for the first application of a conditional
        // This makes the logger record to a temporary storage that is transfered to the permanent in case the conditional is true istead of directly to the
        // permanent storage.
        temp_function_pointer = found_logger->function_pointers.active_record_function;
        found_logger->function_pointers.active_record_function = found_logger->function_pointers.inactive_record_function;
        found_logger->function_pointers.inactive_record_function = temp_function_pointer;
      }

      if (overwrite_logger_weight == 1)
        found_logger->function_pointers.select_t_to_p = 2;

      // Add a pointer to this conditional function to the list of conditionals for this logger
      add_function_to_conditional_list (&found_logger->conditional_list, &conditional_function_standard, &this_data_union);
    }
    for (loop_index = 0; loop_index < accepted_abs_loggers_all.num_elements; loop_index++) {
      // printf("all loop \n");

      // For each accepted logger do these tasks:
      //  if not conditional function pointer is assigned yet, switch the active and inactive record function pointers in the logger
      //  Assing a function pointer to the conditional function pointer (maybe a list of these if more conditions are to be used)

      found_abs_logger = global_all_volume_abs_logger_list->elements[accepted_abs_loggers_all.elements[loop_index]].abs_logger;

      if (found_abs_logger->conditional_list.num_elements == 0) {
        // Switching function pointers, only necessary for the first application of a conditional
        // This makes the logger record to a temporary storage that is transfered to the permanent in case the conditional is true istead of directly to the
        // permanent storage.
        temp_abs_function_pointer = found_abs_logger->function_pointers.active_record_function;
        found_abs_logger->function_pointers.active_record_function = found_abs_logger->function_pointers.inactive_record_function;
        found_abs_logger->function_pointers.inactive_record_function = temp_abs_function_pointer;
      }

      // Add a pointer to this conditional function to the list of conditionals for this logger
      add_function_to_conditional_list (&found_abs_logger->conditional_list, &conditional_function_standard, &this_data_union);
    }
    for (loop_index = 0; loop_index < accepted_abs_loggers_specific.num_elements; loop_index++) {
      // printf("specific loop \n");
      //  For each accepted logger do these tasks:
      //   if not conditional function pointer is assigned yet, switch the active and inactive record function pointers in the logger
      //   Assing a function pointer to the conditional function pointer (maybe a list of these if more conditions are to be used)

      found_abs_logger = global_specific_volumes_abs_logger_list->elements[accepted_abs_loggers_specific.elements[loop_index]].abs_logger;

      if (found_abs_logger->conditional_list.num_elements == 0) {
        // Switching function pointers, only necessary for the first application of a conditional
        // This makes the logger record to a temporary storage that is transfered to the permanent in case the conditional is true istead of directly to the
        // permanent storage.
        temp_abs_function_pointer = found_abs_logger->function_pointers.active_record_function;
        found_abs_logger->function_pointers.active_record_function = found_abs_logger->function_pointers.inactive_record_function;
        found_abs_logger->function_pointers.inactive_record_function = temp_abs_function_pointer;
      }

      // Add a pointer to this conditional function to the list of conditionals for this logger
      add_function_to_conditional_list (&found_logger->conditional_list, &conditional_function_standard, &this_data_union);
    }
    /*
    for (loop_index=0;loop_index<accepted_tagging.num_elements;loop_index++) {
      //printf("master loop \n");
      // Adding a conditional to the tagging part of a master does not need any change in the record functions, as it doesn't work as a logger
      add_function_to_conditional_list(&global_tagging_conditional_list->elements[accepted_tagging.elements[loop_index]].conditional_list,&conditional_function_standard,&this_data_union);
    }
    */

  } else {
    // What to do if no target_logger string given? Apply to all loggers? Apply to overall tagging system? Apply to former logger?
    // Give error.
    if (master_tagging == 0) {
      printf ("ERROR: Union conditional component named \"%s\" need the target_logger string, or to be applied to the next master using master_tagging=1. \n",
              NAME_CURRENT_COMP);
      exit (1);
    }
  }

  // printf("completed conditional initialize \n");
 %}

TRACE
%{
%}

SAVE
%{
%}

FINALLY
%{
  // Remember to clean up allocated lists
  if (accepted_loggers_specific.num_elements > 0)
    free (accepted_loggers_specific.elements);
  if (accepted_loggers_all.num_elements > 0)
    free (accepted_loggers_all.elements);
  if (accepted_abs_loggers_specific.num_elements > 0)
    free (accepted_abs_loggers_specific.elements);
  if (accepted_abs_loggers_all.num_elements > 0)
    free (accepted_abs_loggers_all.elements);
  // if (accepted_tagging.num_elements > 0) free(accepted_tagging.elements);
%}

END

