/*******************************************************************\

Module: Visual Studio CL Mode

Author: CM Wintersteiger, 2006

\*******************************************************************/

/// \file
/// Visual Studio CL Mode

#include "ms_cl_mode.h"

#ifdef _WIN32
#define EX_OK 0
#define EX_USAGE 64
#define EX_SOFTWARE 70
#else
#include <sysexits.h>
#endif

#include <iostream>

#include <util/config.h>
#include <util/file_util.h>
#include <util/get_base_name.h>
#include <util/message.h>
#include <util/prefix.h>

#include "compile.h"
#include "ms_cl_version.h"

static bool has_directory_suffix(const std::string &path)
{
  // MS CL decides whether a parameter is a directory on the
  // basis of the / or \\ suffix; it doesn't matter
  // whether the directory actually exists.
  return path.empty() ? false :
                        path.back()=='/' || path.back()=='\\';
}

/// does it.
int ms_cl_modet::doit()
{
  if(cmdline.isset('?') ||
     cmdline.isset("help"))
  {
    help();
    return EX_OK;
  }

  compilet compiler(cmdline, message_handler, cmdline.isset("WX"));

  #if 0
  bool act_as_ld=
    has_prefix(base_name, "link") ||
    has_prefix(base_name, "goto-link");
  #endif

  const auto verbosity = eval_verbosity(
    cmdline.get_value("verbosity"), messaget::M_ERROR, message_handler);

  ms_cl_versiont ms_cl_version;
  ms_cl_version.get("cl.exe");

  debug() << "Visual Studio mode " << ms_cl_version << eom;

  // model validation
  compiler.validate_goto_model = cmdline.isset("validate-goto-model");

  // get configuration
  config.set(cmdline);

  if(ms_cl_version.target == ms_cl_versiont::targett::x86)
    config.ansi_c.set_32();
  else if(ms_cl_version.target == ms_cl_versiont::targett::ARM)
    config.ansi_c.set_32();
  else if(ms_cl_version.target == ms_cl_versiont::targett::x86)
    config.ansi_c.set_64();

  config.ansi_c.mode=configt::ansi_ct::flavourt::VISUAL_STUDIO;
  compiler.object_file_extension="obj";

  // determine actions to be undertaken

  if(cmdline.isset('E') || cmdline.isset('P'))
    compiler.mode=compilet::PREPROCESS_ONLY;
  else if(cmdline.isset('c'))
    compiler.mode=compilet::COMPILE_ONLY;
  else
    compiler.mode=compilet::COMPILE_LINK_EXECUTABLE;

  if(cmdline.isset("std"))
  {
    const std::string std_string = cmdline.get_value("std");

    if(
      std_string == ":c++14" || std_string == "=c++14" ||
      std_string == ":c++17" || std_string == "=c++17" ||
      std_string == ":c++latest" || std_string == "=c++latest")
    {
      // we don't have any newer version at the moment
      config.cpp.set_cpp14();
    }
    else if(std_string == ":c++11" || std_string == "=c++11")
    {
      // this isn't really a Visual Studio variant, we just do this for GCC
      // command-line compatibility
      config.cpp.set_cpp11();
    }
    else
      warning() << "unknown language standard " << std_string << eom;
  }
  else
    config.cpp.set_cpp14();

  compiler.echo_file_name=true;

  if(cmdline.isset("Fo"))
  {
    std::string Fo_value = cmdline.get_value("Fo");

    // this could be a directory or a file name
    if(has_directory_suffix(Fo_value))
    {
      compiler.output_directory_object = Fo_value;

      if(!is_directory(Fo_value))
        warning() << "not a directory: " << Fo_value << eom;
    }
    else
      compiler.output_file_object = Fo_value;
  }

  if(
    compiler.mode == compilet::COMPILE_ONLY &&
    cmdline.args.size() > 1 &&
    compiler.output_directory_object.empty())
  {
    error() << "output directory required for /c with multiple input files"
            << eom;
    return EX_USAGE;
  }

  if(cmdline.isset("Fe"))
  {
    compiler.output_file_executable=cmdline.get_value("Fe");

    // this could be a directory
    if(
      has_directory_suffix(compiler.output_file_executable) &&
      cmdline.args.size() >= 1)
    {
      if(!is_directory(compiler.output_file_executable))
        warning() << "not a directory: "
                  << compiler.output_file_executable << eom;

      compiler.output_file_executable+=
        get_base_name(cmdline.args[0], true) + ".exe";
    }
  }
  else
  {
    // We need at least one argument.
    // CL uses the first file name it gets!
    if(cmdline.args.size()>=1)
      compiler.output_file_executable=
        get_base_name(cmdline.args[0], true)+".exe";
  }

  if(cmdline.isset('J'))
    config.ansi_c.char_is_unsigned=true;

  if(verbosity > messaget::M_STATISTICS)
  {
    std::list<std::string>::iterator it;

    std::cout << "Defines:\n";
    for(it=config.ansi_c.defines.begin();
        it!=config.ansi_c.defines.end();
        it++)
    {
      std::cout << "  " << (*it) << '\n';
    }

    std::cout << "Undefines:\n";
    for(it=config.ansi_c.undefines.begin();
        it!=config.ansi_c.undefines.end();
        it++)
    {
      std::cout << "  " << (*it) << '\n';
    }

    std::cout << "Preprocessor Options:\n";
    for(it=config.ansi_c.preprocessor_options.begin();
        it!=config.ansi_c.preprocessor_options.end();
        it++)
    {
      std::cout << "  " << (*it) << '\n';
    }

    std::cout << "Include Paths:\n";
    for(it=config.ansi_c.include_paths.begin();
        it!=config.ansi_c.include_paths.end();
        it++)
    {
      std::cout << "  " << (*it) << '\n';
    }

    std::cout << "Library Paths:\n";
    for(it=compiler.library_paths.begin();
        it!=compiler.library_paths.end();
        it++)
    {
      std::cout << "  " << (*it) << '\n';
    }

    std::cout << "Output file (object): "
              << compiler.output_file_object << '\n';
    std::cout << "Output file (executable): "
              << compiler.output_file_executable << '\n';
  }

  // Parse input program, convert to goto program, write output
  return compiler.doit() ? EX_USAGE : EX_OK;
}

/// display command line help
void ms_cl_modet::help_mode()
{
  std::cout << "goto-cl understands the options of CL plus the following.\n\n";
}
