/*******************************************************************************
*
*  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)
*
*
* OUTPUT PARAMETERS:
*
* GLOBAL PARAMETERS:
*
* %L
*
* %E
******************************************************************************/

DEFINE COMPONENT Union_conditional_standard
DEFINITION PARAMETERS ()
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
OUTPUT PARAMETERS ()

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

SHARE
%{
#ifndef Union
#define Union $Revision: 0.8 $

%include "Union_functions.c"
%include "Union_initialization.c"

#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_functions.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

