/*******************************************************************************
*
*  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 in the shape of an area the neutrons must hit
*
* %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/cylinder/sphere, 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.
*
* This conditional is a little special, because it needs to be placed in space.
*  It's center location is like a psd.
*
* 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:
* xwidth:               [m]      Width of target area
* yheight:              [m]      Height of target area
* 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 (on psd), if not set ignored
* time_max:             [s]      Max time (on psd), if not set ignored
* overwrite_logger_weight: [1]   Default = 0, overwrite = 1
* end_volume:           [1]      Not used yet
* last_volume_index:    [1]      Not used yet
* init:                 [string] Name of Union_init component (typically "init", default)
*
* CALCULATED PARAMETERS:
* this_material:            Structure that contains information on this material
* global_material_element:  Element of global_material_list which is a global variable
*
* GLOBAL PARAMETERS:
* global_material_list:     List of all defined materials, available in the global scope
*
* %L
*
* %E
******************************************************************************/

DEFINE COMPONENT Union_conditional_PSD

SETTING PARAMETERS(string target_loggers="NULL", int master_tagging=0, int tagging_extend_index = -1, xwidth, yheight,time_min=0, time_max=0, 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_PSD component"
#endif

int conditional_function_PSD(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) {

  // Transform to this coordinate system
  Coords local_position = transform_position(*position,data_union->p_PSD->PSD_position,data_union->p_PSD->PSD_rotation);
  Coords local_velocity = rot_apply(data_union->p_PSD->PSD_rotation,*velocity);
  
  // Prop_Z0
  if (local_velocity.z == 0) return 0;
  double dt = -local_position.z/local_velocity.z;
  if (dt<0) return 0;
  local_position.x = local_position.x + dt*local_velocity.x;
  local_position.y = local_position.y + dt*local_velocity.y;
  
  double detect_time = *time + dt;
  
  if (data_union->p_PSD->T_limit == 1) {
    if (detect_time < data_union->p_PSD->Tmin || detect_time > data_union->p_PSD->Tmax)
      return 0;
  }
  
  if (local_position.x > data_union->p_PSD->PSD_half_xwidth) return 0;
  if (local_position.x < -data_union->p_PSD->PSD_half_xwidth) return 0;
  if (local_position.y > data_union->p_PSD->PSD_half_yheight) return 0;
  if (local_position.y < -data_union->p_PSD->PSD_half_yheight) 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[1024];
   
   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 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;
}

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;
}
#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_PSD_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");
  
  // If time range is set, use it
  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 (xwidth < 0) {
    printf("ERROR: In Union conditional %s xwidth is less than 0!\n",NAME_CURRENT_COMP);
  }
  this_conditional_data.PSD_half_xwidth = 0.5*xwidth;
  
  if (yheight < 0) {
    printf("ERROR: In Union conditional %s yheight is less than 0!\n",NAME_CURRENT_COMP);
  }
  this_conditional_data.PSD_half_yheight = 0.5*yheight;

if (_getcomp_index(init) < 0) {
fprintf(stderr,"Union_conditional_PSD:%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.PSD_position = POS_A_CURRENT_COMP;
  add_position_pointer_to_list(global_positions_to_transform_list, &this_conditional_data.PSD_position);
  
  rot_copy(this_conditional_data.PSD_rotation,ROT_A_CURRENT_COMP);
  add_rotation_pointer_to_list(global_rotations_to_transform_list, &this_conditional_data.PSD_rotation);
  
  rot_transpose(ROT_A_CURRENT_COMP,this_conditional_data.PSD_t_rotation);
  add_rotation_pointer_to_list(global_rotations_to_transform_list, &this_conditional_data.PSD_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_PSD = &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_PSD, &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
    
    //printf("Reached target logger section \n");
    
    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);
    }
    
    // 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*);
    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_PSD,&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_PSD,&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_PSD, &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_PSD,&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_tagging.num_elements > 0) free(accepted_tagging.elements);

%}

END

