// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

////////////////////////////////
//~ rjf: Command Line Parsing Functions

internal CmdLineOpt **
cmd_line_slot_from_string(CmdLine *cmd_line, String8 string)
{
  CmdLineOpt **slot = 0;
  if(cmd_line->option_table_size != 0)
  {
    U64 hash = u64_hash_from_str8(string);
    U64 bucket = hash % cmd_line->option_table_size;
    slot = &cmd_line->option_table[bucket];
  }
  return slot;
}

internal CmdLineOpt *
cmd_line_opt_from_slot(CmdLineOpt **slot, String8 string)
{
  CmdLineOpt *result = 0;
  for(CmdLineOpt *var = *slot; var; var = var->hash_next)
  {
    if(str8_match(string, var->string, 0))
    {
      result = var;
      break;
    }
  }
  return result;
}

internal void
cmd_line_push_opt(CmdLineOptList *list, CmdLineOpt *var)
{
  SLLQueuePush(list->first, list->last, var);
  list->count += 1;
}

internal CmdLineOpt *
cmd_line_insert_opt(Arena *arena, CmdLine *cmd_line, String8 string, String8List values)
{
  CmdLineOpt *var = 0;
  CmdLineOpt **slot = cmd_line_slot_from_string(cmd_line, string);
  CmdLineOpt *existing_var = cmd_line_opt_from_slot(slot, string);
  if(existing_var != 0)
  {
    var = existing_var;
  }
  else
  {
    var = push_array(arena, CmdLineOpt, 1);
    var->hash_next = *slot;
    var->hash = u64_hash_from_str8(string);
    var->string = push_str8_copy(arena, string);
    var->value_strings = values;
    StringJoin join = {0};
    join.pre = str8_lit("");
    join.sep = str8_lit(",");
    join.post = str8_lit("");
    var->value_string = str8_list_join(arena, &var->value_strings, &join);
    *slot = var;
    cmd_line_push_opt(&cmd_line->options, var);
  }
  return var;
}

internal CmdLine
cmd_line_from_string_list(Arena *arena, String8List command_line)
{
  CmdLine parsed = {0};
  parsed.exe_name = command_line.first->string;
  parsed.option_table_size = 64;
  parsed.option_table = push_array(arena, CmdLineOpt *, parsed.option_table_size);
  
  //- rjf: parse options / inputs
  B32 after_passthrough_option = 0;
  B32 first_passthrough = 1;
  for(String8Node *node = command_line.first->next, *next = 0; node != 0; node = next)
  {
    next = node->next;
    
    //- rjf: look at --, -, or / (only on Windows) at the start of an
    // argument to determine if it's a flag option. all arguments after a
    // single "--" (with no trailing string on the command line will be
    // considered as passthrough input strings.
    B32 is_option = 0;
    String8 option_name = node->string;
    if(!after_passthrough_option)
    {
      is_option = 1;
      if(str8_match(node->string, str8_lit("--"), 0))
      {
        after_passthrough_option = 1;
        is_option = 0;
      }
      else if(str8_match(str8_prefix(node->string, 2), str8_lit("--"), 0))
      {
        option_name = str8_skip(option_name, 2);
      }
      else if(str8_match(str8_prefix(node->string, 1), str8_lit("-"), 0))
      {
        option_name = str8_skip(option_name, 1);
      }
      else if(operating_system_from_context() == OperatingSystem_Windows &&
              str8_match(str8_prefix(node->string, 1), str8_lit("/"), 0))
      {
        option_name = str8_skip(option_name, 1);
      }
      else
      {
        is_option = 0;
      }
    }
    
    //- rjf: string is an option
    if(is_option)
    {
      // rjf: unpack option prefix
      B32 has_values = 0;
      U64 value_signifier_position1 = str8_find_needle(option_name, 0, str8_lit(":"), 0);
      U64 value_signifier_position2 = str8_find_needle(option_name, 0, str8_lit("="), 0);
      U64 value_signifier_position = Min(value_signifier_position1, value_signifier_position2);
      String8 value_portion_this_string = str8_skip(option_name, value_signifier_position+1);
      if(value_signifier_position < option_name.size)
      {
        has_values = 1;
      }
      option_name = str8_prefix(option_name, value_signifier_position);
      
      // rjf: parse option's values
      String8List values = {0};
      if(has_values)
      {
        for(String8Node *n = node; n; n = n->next)
        {
          next = n->next;
          String8 string = n->string;
          if(n == node)
          {
            string = value_portion_this_string;
          }
          U8 splits[] = { ',' };
          String8List values_in_this_string = str8_split(arena, string, splits, ArrayCount(splits), 0);
          for(String8Node *sub_val = values_in_this_string.first; sub_val; sub_val = sub_val->next)
          {
            str8_list_push(arena, &values, sub_val->string);
          }
          if(!str8_match(str8_postfix(n->string, 1), str8_lit(","), 0) &&
             (n != node || value_portion_this_string.size != 0))
          {
            break;
          }
        }
      }
      
      // rjf: store
      cmd_line_insert_opt(arena, &parsed, option_name, values);
    }
    
    //- rjf: default path - treat as a passthrough input
    else if(!str8_match(node->string, str8_lit("--"), 0) || !first_passthrough)
    {
      str8_list_push(arena, &parsed.inputs, node->string);
      first_passthrough = 0;
    }
  }
  
  //- rjf: fill argc/argv
  parsed.argc = command_line.node_count;
  parsed.argv = push_array(arena, char *, parsed.argc);
  {
    U64 idx = 0;
    for(String8Node *n = command_line.first; n != 0; n = n->next)
    {
      parsed.argv[idx] = (char *)push_str8_copy(arena, n->string).str;
      idx += 1;
    }
  }
  
  return parsed;
}

internal CmdLineOpt *
cmd_line_opt_from_string(CmdLine *cmd_line, String8 name)
{
  return cmd_line_opt_from_slot(cmd_line_slot_from_string(cmd_line, name), name);
}

internal String8List 
cmd_line_strings(CmdLine *cmd_line, String8 name)
{
  String8List result = {0};
  CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name);
  if(var != 0)
  {
    result = var->value_strings;
  }
  return result;
}

internal String8     
cmd_line_string(CmdLine *cmd_line, String8 name)
{
  String8 result = {0};
  CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name);
  if(var != 0)
  {
    result = var->value_string;
  }
  return result;
}

internal B32
cmd_line_has_flag(CmdLine *cmd_line, String8 name)
{
  CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name);
  return (var != 0);
}

internal B32
cmd_line_has_argument(CmdLine *cmd_line, String8 name)
{
  CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name);
  return (var != 0 && var->value_strings.node_count > 0);
}
