-- match(.*cl6x.*)

-------------------------------------------------------------------------------
-- Copyright (c) 2018 Marcus Geelnard
--
-- This software is provided 'as-is', without any express or implied warranty.
-- In no event will the authors be held liable for any damages arising from the
-- use of this software.
--
-- Permission is granted to anyone to use this software for any purpose,
-- including commercial applications, and to alter it and redistribute it
-- freely, subject to the following restrictions:
--
--  1. The origin of this software must not be misrepresented; you must not
--     claim that you wrote the original software. If you use this software in
--     a product, an acknowledgment in the product documentation would be
--     appreciated but is not required.
--
--  2. Altered source versions must be plainly marked as such, and must not be
--     misrepresented as being the original software.
--
--  3. This notice may not be removed or altered from any source distribution.
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- This is a Lua re-implementation of the TI C6000 DSP compiler wrapper,
-- ti_c6x_wrapper_t.
--
-- Note: The Lua and C++ implementations are not interchangeable (cache entries
-- produced by one of them will not produce a cache hit for the other). The
-- main purpose of this implementation is to serve as an example.
-------------------------------------------------------------------------------

require_std("io")
require_std("os")
require_std("string")
require_std("table")
require_std("bcache")


-------------------------------------------------------------------------------
-- Internal helper functions.
-------------------------------------------------------------------------------

local function starts_with (s, substr)
  return s:sub(1, #substr) == substr
end

local function make_preprocessor_cmd (args, preprocessed_file)
  local preprocess_args = {}

  -- Drop arguments that we do not want/need.
  local drop_next_arg = false
  for i, arg in ipairs(args) do
    local drop_this_arg = drop_next_arg
    drop_next_arg = false
    if (arg == "--compile_only") or
       starts_with(arg, "--output_file=") or
       starts_with(arg, "-pp") or
       starts_with(arg, "--preproc_") then
      drop_this_arg = true
    end
    if not drop_this_arg then
      table.insert(preprocess_args, arg)
    end
  end

  -- Append the required arguments for producing preprocessed output.
  table.insert(preprocess_args, "--preproc_only")
  table.insert(preprocess_args, "--output_file=" .. preprocessed_file)

  return preprocess_args
end

local function append_response_file (args, file_name)
  -- Load the response file into a string.
  local f = assert(io.open(file_name, "rb"))
  local args_string = f:read("*all")
  f:close()
  args_string = args_string:gsub("\n", " ")

  -- Split the arguments.
  local new_args = bcache.split_args(args_string)
  for i, arg in ipairs(new_args) do
    table.insert(args, arg)
  end

  return args
end


-------------------------------------------------------------------------------
-- Wrapper interface implementation.
-------------------------------------------------------------------------------

function resolve_args ()
  -- Iterate over all args and load any response files that we encounter.
  local new_args = {}
  for i, arg in ipairs(ARGS) do
    local response_file
    if starts_with(arg, "--cmd_file=") then
      response_file = arg:sub(12)
    elseif starts_with(arg, "-@") then
      response_file = arg:sub(3)
    end
    if response_file ~= nil then
      new_args = append_response_file(new_args, response_file)
    else
      table.insert(new_args, arg)
    end
  end

  -- Replace the old args with the new args.
  ARGS = new_args
end

function get_build_files ()
  local files = {}
  local found_object_file = false
  local found_dep_file = false
  for i = 2, #ARGS do
    local next_idx = i + 1
    if starts_with(ARGS[i], "--output_file=") then
      if found_object_file then
        error("Only a single target object file can be specified.")
      end
      files["object"] = ARGS[i]:sub(15)
      found_object_file = true
    elseif starts_with(ARGS[i], "-ppd=") or starts_with(ARGS[i], "--preproc_dependency=") then
      if found_dep_file then
        error("Only a single dependency file can be specified.")
      end
      local eq = ARGS[i]:find("=")
      files["dep"] = ARGS[i]:sub(eq + 1)
      found_dep_file = true
    end
  end
  if not found_object_file then
    error("Unable to get the target object file.")
  end
  return files
end

function get_program_id ()
  -- TODO(m): Add things like executable file size too.

  -- Get the help string from the compiler (it includes the version string).
  local result = bcache.run({ARGS[1], "--help"})
  if result.return_code ~= 0 then
    error("Unable to get the compiler version information string.")
  end

  return result.std_out
end

function get_relevant_arguments ()
  local filtered_args = {}

  -- The first argument is the compiler binary without the path.
  table.insert(filtered_args, bcache.get_file_part(ARGS[1]))

  -- Note: We always skip the first arg since we have handled it already.
  local skip_next_arg = true
  for i, arg in ipairs(ARGS) do
    if not skip_next_arg then
      -- Generally unwanted argument (things that will not change how we go
      -- from preprocessed code to binary object files)?
      local first_two_chars = arg:sub(1, 2)
      local is_unwanted_arg = (first_two_chars == "-I") or
                              starts_with(arg, "--include") or
                              starts_with(arg, "--preinclude=") or
                              (first_two_chars == "-D") or
                              starts_with(arg, "--define=") or
                              starts_with(arg, "--c_file=") or
                              starts_with(arg, "--cpp_file=") or
                              starts_with(arg, "--output_file=")

      if not is_unwanted_arg then
        table.insert(filtered_args, arg)
      end
    else
      skip_next_arg = false
    end
  end

  return filtered_args
end

function preprocess_source ()
  -- Check if this is a compilation command that we support.
  local is_object_compilation = false
  local has_object_output = false
  for i, arg in ipairs(ARGS) do
    if arg == "--compile_only" then
      is_object_compilation = true
    elseif starts_with(arg, "--output_file=") then
      has_object_output = true
    elseif starts_with(arg, "--cmd_file=") or starts_with(arg, "-@") then
      error("Response files are currently not supported.")
    end
  end
  if (not is_object_compilation) or (not has_object_output) then
    error("Unsupported complation command.")
  end

  -- Run the preprocessor step.
  local preprocessed_file = os.tmpname()
  local preprocessor_args = make_preprocessor_cmd(ARGS, preprocessed_file)
  local result = bcache.run(preprocessor_args)
  if result.return_code ~= 0 then
    os.remove(preprocessed_file)
    error("Preprocessing command was unsuccessful.")
  end

  -- Read and return the preprocessed file.
  local f = assert(io.open(preprocessed_file, "rb"))
  local preprocessed_source = f:read("*all")
  f:close()
  os.remove(preprocessed_file)

  return preprocessed_source
end
