%option prefix="<%= scanner.function_prefix %>"
%option full
%option never-interactive
%option read
%option nounput
%option noyywrap noreject noyymore nodefault
%{
#include <ruby.h>
#include <uuid/uuid.h>
/* Data types */
typedef struct {
  char *key;
  char *value;
} KVPAIR;
const KVPAIR EOF_KVPAIR = {"EOF", "EOF"};
/* prototypes */
char *strip_ends(char *);
VALUE <%= scanner.main_function_name %>(VALUE);
void new_uuid(char *str_ptr);
void raise_error_for_string_too_long(VALUE string);
void include_message_in_token_hash(VALUE message, VALUE token_hash);
void add_uuid_to_token_hash(VALUE token_hash);
void push_kv_pair_to_hash(KVPAIR key_value, VALUE token_hash);
void concat_word_to_string(KVPAIR key_value, VALUE token_hash);
/* Set the scanner name, and return type */
#define YY_DECL KVPAIR <%= scanner.entry_point %>(void)
#define yyterminate() return EOF_KVPAIR
/* Ruby 1.8 and 1.9 compatibility */
#if !defined(RSTRING_LEN) 
# define RSTRING_LEN(x) (RSTRING(x)->len) 
# define RSTRING_PTR(x) (RSTRING(x)->ptr) 
#endif 

%}

/* Definitions */

CATCHALL (.|"\n")

<% scanner.scanner_defns.each do |scanner_defn| %>
<%= scanner_defn.scanner_code %>
<% end %>

%%
  /* 
    Actions 
 */
  
<% scanner.scanner_rules.each do |scanner_rule| %>
<%= scanner_rule.scanner_code %>
<% end %>
{CATCHALL} /* ignore */
%%

char *strip_ends(char *string) {
  string[yyleng-1] = '\0';
  ++string;
  return string;
}

void uuid_unparse_upper_sans_dash(const uuid_t uu, char *out)
{
        sprintf(out,
                "%02X%02X%02X%02X"
                "%02X%02X"
                "%02X%02X"
                "%02X%02X"
                "%02X%02X%02X%02X%02X%02X",
                uu[0], uu[1], uu[2], uu[3],
                uu[4], uu[5],
                uu[6], uu[7],
                uu[8], uu[9],
                uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]);
}

void new_uuid(char *str_ptr){
  uuid_t new_uuid;
  uuid_generate_time(new_uuid);
  uuid_unparse_upper_sans_dash(new_uuid, str_ptr);
}

void raise_error_for_string_too_long(VALUE string){
  if( RSTRING_LEN(string) > 1000000){
    rb_raise(rb_eArgError, "string too long for <%=scanner.scanner_name %>! max length is 1,000,000 chars"); 
  }
}

<%= scanner.rdoc %>
VALUE <%= scanner.main_function_name %>(VALUE self) {
  KVPAIR kv_result;
  int scan_complete = 0;
  int building_words_to_string = 0;
  VALUE token_hash = rb_hash_new();
  
  BEGIN(INITIAL);
  
  /* error out on absurdly large strings */
  raise_error_for_string_too_long(self);
  /* {:message => self()} */
  include_message_in_token_hash(self, token_hash);
  /* {:id => UUID} */
  add_uuid_to_token_hash(token_hash);
  yy_scan_string(RSTRING_PTR(self));
  while (scan_complete == 0) {
    kv_result = <%= scanner.entry_point %>();
    if (kv_result.key == "EOF"){
      scan_complete = 1;
    }
    else if (kv_result.key == "strings"){
      /* build a string until we get a non-word */
      if (building_words_to_string == 0){
        building_words_to_string = 1;
        push_kv_pair_to_hash(kv_result, token_hash);
      }
      else{
        concat_word_to_string(kv_result, token_hash);
      }    
    }    
    else {
      building_words_to_string = 0;
      push_kv_pair_to_hash(kv_result, token_hash);
    }
  }
  yy_delete_buffer(YY_CURRENT_BUFFER);
  return rb_obj_dup(token_hash);
}

void add_uuid_to_token_hash(VALUE token_hash) {
  char new_uuid_str[33];
  new_uuid(new_uuid_str);
  VALUE hsh_key_id = ID2SYM(rb_intern("id"));
  VALUE hsh_val_id = rb_tainted_str_new2(new_uuid_str);
  rb_hash_aset(token_hash, hsh_key_id, hsh_val_id);
}

void include_message_in_token_hash(VALUE message, VALUE token_hash) {
  /* {:message => self()} */  
  VALUE hsh_key_msg = ID2SYM(rb_intern("message"));
  rb_hash_aset(token_hash, hsh_key_msg, message);
}

void concat_word_to_string(KVPAIR key_value, VALUE token_hash) {
  char * space = " ";
  VALUE hsh_key = ID2SYM(rb_intern(key_value.key));
  VALUE hsh_value = rb_hash_aref(token_hash, hsh_key);
  VALUE string = rb_ary_entry(hsh_value, -1);
  rb_str_cat(string, space, 1);
  rb_str_cat(string, key_value.value, yyleng);
}

void push_kv_pair_to_hash(KVPAIR key_value, VALUE token_hash) {
  VALUE hsh_key = ID2SYM(rb_intern(key_value.key));
  VALUE hsh_value = rb_hash_aref(token_hash, hsh_key);
  VALUE ary_for_token_type = rb_ary_new();
  switch (TYPE(hsh_value)) {
    case T_NIL:
      rb_ary_push(ary_for_token_type, rb_tainted_str_new2(key_value.value));
      rb_hash_aset(token_hash, hsh_key, ary_for_token_type);
      break;
    case T_ARRAY:
      rb_ary_push(hsh_value, rb_tainted_str_new2(key_value.value));
      break;
  }
}

void <%=scanner.init_function_name %>() {
  rb_define_method(rb_cString, "<%= scanner.scanner_name %>", <%= scanner.main_function_name %>, 0);
}