//
// $Id: $
//
// lib2def extracts the template symbols from a list of .lib files and puts them
// into a single .def file, which is printed to standard output. This tool is
// used to workaround a problem with Borland C++ importing template
// instantiations from DLLs. It requires the Borland tool TDUMP to be located on
// the path.
//
// Usage: lib2def [-Ldir...] libfile...
//

#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <cstdio>
#include <sys/stat.h>
#include <unmangle.h>

void print_usage ()
{
  std::cerr << "Usage: lib2def [-Ldir...] libfile...\n";
}

int find_lib (const std::string& lib_name,
              const std::vector<std::string>& lib_search_paths,
              std::string& lib_path)
{
  for (size_t i = 0; i < lib_search_paths.size (); ++i)
    {
      struct stat stat_buf;
      std::string test_path = lib_search_paths[i] + lib_name;
      if (stat (test_path.c_str (), &stat_buf) == 0)
        {
          lib_path = test_path;
          return 0;
        }
    }

  return -1;
}

bool is_template_symbol (const std::string& symbol)
{
  char unmangled_symbol[8192];
  _umKind unmangled_kind = _rtl_unmangle(const_cast<char*>(symbol.c_str()),
      unmangled_symbol, sizeof(unmangled_symbol), 0, 0, 1);
  if (unmangled_kind & UM_ERRMASK)
    return true; // Can't unmangle symbol so force import

  bool have_less_than = false;
  bool have_greater_than = false;
  for (char* p = unmangled_symbol; *p; ++p)
    {
      switch (*p)
        {
        case '<':
          have_less_than = true;
          break;
        case '>':
          have_greater_than = true;
          break;
        default:
          break;
        }
    }

  return (have_less_than && have_greater_than);
}

int process_tdump_line (const std::string& line)
{
  if (line.size () == 0 || line.compare (0, 5, "Turbo") == 0
      || line.find ("Display of") != std::string::npos)
    return 0;

  std::istringstream is (line);
  
  std::string impdef;
  is >> impdef;
  if (impdef != "Impdef:")
    return -1;

  std::string name_or_ord;
  is >> name_or_ord;
  if (name_or_ord != "(Name)" && name_or_ord != "(Ord)")
    return -1;

  is >> std::ws;
  std::string module;
  std::getline (is, module, is.widen ('.'));
  std::string ordinal;
  std::getline (is, ordinal, is.widen ('='));
  std::string symbol;
  std::getline (is, symbol);
  if (!is)
    return -1;

  if (symbol == "___CPPdebugHook")
    return 0;

  if (!is_template_symbol (symbol))
    return 0;

  std::cout << "  " << module << "." << symbol << "\n";

  return 0;
}

int process_lib (const std::string& lib_path)
{
  std::string temp_file_name = std::tmpnam (0);
  std::string cmd_line = "tdump -li -m " + lib_path  + " > " + temp_file_name;
  if (std::system (cmd_line.c_str ()) != 0)
    return -1;

  int status = 0;
  std::ifstream is (temp_file_name.c_str ());
  std::string line;
  while (std::getline (is, line))
    if ((status = process_tdump_line (line)) == -1)
      break;
  is.close ();

  std::remove (temp_file_name.c_str ());

  return status;
}

int main (int argc,
          char* argv[])
{
  std::vector<std::string> lib_search_paths;
  lib_search_paths.push_back ("");

  bool printed_imports = false;
  int argi = 1;
  while (argi < argc)
    {
      if (argv[argi][0] == '-' || argv[argi][0] == '/')
        {
          if (argv[argi][1] == 'L')
            {
              if (argv[argi][2])
                {
                  lib_search_paths.push_back (&argv[argi][2]);
                  lib_search_paths.back () += "/";
                }
              else
                {
                  ++argi;
                  lib_search_paths.push_back (argv[argi]);
                  lib_search_paths.back () += "/";
                }
            }
          else
            {
              // Other options are simply ignored, to allow all
              // linker flags to be passed to this program.
            }
        }
      else
        {
          std::string lib_path;
          if (find_lib (argv[argi], lib_search_paths, lib_path) == -1)
            {
              std::cerr << "Warning: cannot locate " << argv[argi] << "\n";
              ++argi;
              continue;
            }

          if (!printed_imports)
            {
              std::cout << "IMPORTS\n";
              printed_imports = true;
            }

          if (process_lib (lib_path) == -1)
            {
              std::cerr << "Warning: cannot process " << argv[argi] << "\n";
            }
        }

      ++argi;
    }

  if (!printed_imports)
    {
      std::cout << "IMPORTS\n";
    }

  return 0;
}
