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

internal String8List
lnk_arg_list_parse_windows_rules(Arena *arena, String8 string)
{
  Temp scratch = scratch_begin(&arena, 1);
  
  String8List list = {0};
  
  U8 *ptr = string.str;
  U8 *opl = string.str + string.size;
  while (ptr < opl) {
    // skip white space and new lines
    for (;;) {
      U64 size = (U64)(opl - ptr);
      UnicodeDecode uni = utf8_decode(ptr, size);
      if (uni.codepoint != ' ' && uni.codepoint != '\n' && uni.codepoint != '\r') {
        break;
      }
      ptr += uni.inc;
    }
    
    if (*ptr == '\0') {
      break;
    }
    
    String8List token_builder = {0};
    U8 *anchor = ptr;
    while (ptr < opl) {
      UnicodeDecode uni;
      
      uni = utf8_decode(ptr, (U64)(opl-ptr));
      if (uni.codepoint == '\0' || uni.codepoint == '\n' || uni.codepoint == '\r' || uni.codepoint == ' ') {
        break;
      }
      
      // handle string and strip quotes
      if (uni.codepoint == '"') {
        String8 text_before_quote = str8(anchor, (U64)(ptr - anchor));
        str8_list_push(scratch.arena, &token_builder, text_before_quote);
        
        // advance past starting quote
        ptr += uni.inc;
        anchor = ptr;
        
        U8 *quote_end = ptr;
        while (ptr < opl) {
          uni = utf8_decode(ptr, (U64)(opl - ptr));
          ptr += uni.inc;
          // skip escape char
          if (uni.codepoint == '\\') {
            uni = utf8_decode(ptr, (U64)(opl - ptr));
            ptr += uni.inc;
          } else if (uni.codepoint == '"' || uni.codepoint == '\0') {
            break; // found matching quote char
          }
          quote_end = ptr;
        }
        
        String8 text_inside_quotes = str8(anchor, (U64)(quote_end - anchor));
        str8_list_push(scratch.arena, &token_builder, text_inside_quotes);
        anchor = ptr;
      } else {
        ptr += uni.inc;
      }
    }
    
    // push remaining text 
    String8 text = str8(anchor, (U64)(ptr - anchor));
    str8_list_push(scratch.arena, &token_builder, text);
    
    // push token
    String8 token = str8_list_join(arena, &token_builder, NULL);
    if (token.size) {
      str8_list_push(arena,  &list, token);
    }
  }
  
  scratch_end(scratch);
  return list;
}

internal void
lnk_cmd_line_push_option_node(LNK_CmdLine *cmd_line, LNK_CmdOption *opt)
{
  SLLQueuePush(cmd_line->first_option, cmd_line->last_option, opt);
  cmd_line->option_count += 1;
}

internal LNK_CmdOption *
lnk_cmd_line_push_option_list(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8List value_strings)
{
  // fill out node
  LNK_CmdOption *opt = push_array_no_zero(arena, LNK_CmdOption, 1);
  opt->next          = 0;
  opt->string        = string;
  opt->value_strings = value_strings;

  // push node
  lnk_cmd_line_push_option_node(cmd_line, opt);

  return opt;
}

internal LNK_CmdOption *
lnk_cmd_line_push_option_string(Arena *arena, LNK_CmdLine *cmd_line, String8 string, String8 value)
{
  String8List value_list = str8_split_by_string_chars(arena, value, str8_lit(","), StringSplitFlag_KeepEmpties);
  LNK_CmdOption *opt = lnk_cmd_line_push_option_list(arena, cmd_line, string, value_list);
  return opt;
}

internal LNK_CmdOption *
lnk_cmd_line_push_option(Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value)
{
  return lnk_cmd_line_push_option_string(arena, cmd_line, str8_cstring(string), str8_cstring(value));
}

internal LNK_CmdOption *
lnk_cmd_line_push_option_if_not_present(Arena *arena, LNK_CmdLine *cmd_line, char *string, char *value)
{
  if (!lnk_cmd_line_has_option(*cmd_line, string)) {
    return lnk_cmd_line_push_option(arena, cmd_line, string, value);
  }
  return 0;
}

internal LNK_CmdLine
lnk_cmd_line_parse_windows_rules(Arena *arena, String8List arg_list)
{
  Temp scratch = scratch_begin(&arena, 1);

  LNK_CmdLine cmd_line = {0};

  for (String8Node *arg_node = arg_list.first; arg_node != 0; arg_node = arg_node->next) {
    String8 arg = arg_node->string;
    B32 is_option = str8_match(str8_lit("/"), arg, StringMatchFlag_RightSideSloppy) ||
                    str8_match(str8_lit("-"), arg, StringMatchFlag_RightSideSloppy);
    if (is_option) {
      U64 param_start_pos = str8_find_needle(arg, 0, str8_lit(":"), 0);
      String8 option_name = str8_chop(arg, arg.size - param_start_pos);

      // remove '/' or '-' from option name
      option_name = str8_skip(option_name, 1);

      // skip ':'
      String8 value_string = str8_skip(arg, param_start_pos + 1);

      // make value list
      String8List value_list = str8_split_by_string_chars(arena, value_string, str8_lit(","), 0);

      // push command
      lnk_cmd_line_push_option_list(arena, &cmd_line, option_name, value_list);
    } else {
      str8_list_push(arena, &cmd_line.input_list, arg);
    }
  }
  
  scratch_end(scratch);
  return cmd_line;
}

internal LNK_CmdOption *
lnk_cmd_line_option_from_string(LNK_CmdLine cmd_line, String8 string)
{
  LNK_CmdOption *opt;
  for (opt = cmd_line.first_option; opt != NULL; opt = opt->next) {
    if (str8_match(string, opt->string, StringMatchFlag_CaseInsensitive)) {
      break;
    }
  }
  return opt;
}

internal B32
lnk_cmd_line_has_option_string(LNK_CmdLine cmd_line, String8 string)
{
  LNK_CmdOption *opt = lnk_cmd_line_option_from_string(cmd_line, string);
  B32 has_option = (opt != 0);
  return has_option;
}

internal B32
lnk_cmd_line_has_option(LNK_CmdLine cmd_line, char *string)
{
  return lnk_cmd_line_has_option_string(cmd_line, str8_cstring(string));
}

internal String8List
lnk_unwrap_rsp(Arena *arena, String8List arg_list)
{
  Temp scratch = scratch_begin(&arena, 1);

  String8List result = {0};

  for (String8Node *curr = arg_list.first; curr != 0; curr = curr->next) {
    B32 is_rsp = str8_match(str8_lit("@"), curr->string, StringMatchFlag_RightSideSloppy);
    if (is_rsp) {
      // remove "@"
      String8 name = str8_skip(curr->string, 1);

      if (os_file_path_exists(name)) {
        // read rsp from disk
        String8 file = lnk_read_data_from_file_path(scratch.arena, name);
        
        // parse rsp
        String8List rsp_args = lnk_arg_list_parse_windows_rules(scratch.arena, file);
        
        // handle case where rsp references another rsp
        String8List list = lnk_unwrap_rsp(arena, rsp_args);

        // push arguments from rsp
        list = str8_list_copy(arena, &list);
        str8_list_concat_in_place(&result, &list);
       } else {
        lnk_error(LNK_Error_Cmdl, "unable to find rsp: %S", name);
      }
    } else {
      // push regular argument
      String8 str = push_str8_copy(arena, curr->string);
      str8_list_push(arena, &result, str);
    }
  }
  
  scratch_end(scratch);
  return result;
}

internal String8List
lnk_data_from_cmd_line(Arena *arena, LNK_CmdLine cmd_line)
{
  String8List result = {0};

  for (LNK_CmdOption *opt = cmd_line.first_option; opt != 0; opt = opt->next) {
    // separate directives
    if (opt != cmd_line.first_option) {
      str8_list_pushf(arena, &result, " ");
    }

    // push new directive
    str8_list_pushf(arena, &result, "/%.*s", str8_varg(opt->string));

    // do we have arguments?
    if (opt->value_strings.node_count > 0) {
      str8_list_pushf(arena, &result, ":");

      for (String8Node *value_node = opt->value_strings.first; value_node != 0; value_node = value_node->next) {
        // separate arguments
        if (value_node != opt->value_strings.first) {
          str8_list_pushf(arena, &result, ",");
        }

        // push argument
        B32 has_spaces = str8_find_needle(value_node->string, 0, str8_lit(" "), StringMatchFlag_CaseInsensitive) < value_node->string.size;
        if (has_spaces) {
          str8_list_pushf(arena, &result, "\"%.*s\"", str8_varg(value_node->string));
        } else {
          str8_list_pushf(arena, &result, "%.*s", str8_varg(value_node->string));
        }
      }
    }
  }

  // append inputs
  for (String8Node *input_node = cmd_line.input_list.first; input_node != 0; input_node = input_node->next) {
    if (input_node != cmd_line.input_list.first) {
      str8_list_pushf(arena, &result, " ");
    }
    str8_list_pushf(arena, &result, "\"%.*s\"", str8_varg(input_node->string));
  }

  return result;
}
