#include <cassert>
#include <list>
#include <string>

#include "llvm/Linker.h"
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/PassManager.h"

#include "llvm/ADT/OwningPtr.h"

#include "llvm/Bitcode/ReaderWriter.h"

#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/PassManagerBuilder.h"
#include "llvm/Support/system_error.h"

#include "llvm/Target/TargetData.h"

static llvm::cl::list<std::string>
InputFilenames(llvm::cl::Positional, llvm::cl::OneOrMore,
               llvm::cl::desc("<input bitcode files>"));

static llvm::cl::list<std::string>
OutputFilenames("o", llvm::cl::desc("Override output filename"),
                llvm::cl::value_desc("output bitcode file"));


static llvm::Module* getModuleFromFilename(std::string& Filename,
                                           llvm::LLVMContext& Ctx,
                                           std::string& ErrMsg) {
  llvm::OwningPtr<llvm::MemoryBuffer> MB;
  llvm::MemoryBuffer::getFile(Filename, MB);
  llvm::Module* M = llvm::ParseBitcodeFile(MB.get(), Ctx, &ErrMsg);
  assert(M && ErrMsg);
  return M;
}

static void optimizeModule(llvm::Module* M) {
  llvm::PassManager Passes;

  const std::string &ModuleDataLayout = M->getDataLayout();
  if (!ModuleDataLayout.empty())
    if (llvm::TargetData *TD = new llvm::TargetData(ModuleDataLayout))
      Passes.add(TD);

  Passes.add(llvm::createInternalizePass(true/* AllButMain*/));
#if 0
  FIXME REMOVE
  createStandardLTOPasses(&Passes,
                          /* Internalize = */false,
                          /* RunInliner = */true,
                          /* VerifyEach = */false);
#endif
  llvm::PassManagerBuilder PMBuilder;
  PMBuilder.populateLTOPassManager(Passes, false, true);
  Passes.run(*M);
}

static llvm::Module* linkFilesToModule(llvm::cl::list<std::string>& Inputs,
                                       llvm::LLVMContext& Ctx) {
  std::string ErrMsg;
  llvm::Module* M = getModuleFromFilename(Inputs[0], Ctx, ErrMsg);
  llvm::Linker Linker("llvm-ndk-link", M);

  for (unsigned i=1; i<Inputs.size(); ++i) {
    llvm::Module* M = getModuleFromFilename(Inputs[i], Ctx, ErrMsg);
    if (!Linker.LinkInModule(M, &ErrMsg)) {
      assert(false && ErrMsg);
    }
    optimizeModule(M);
  }
  M = Linker.releaseModule();

  llvm::PassManager PM;
  const std::string &ModuleDataLayout = M->getDataLayout();
  if (!ModuleDataLayout.empty())
    if (llvm::TargetData *TD = new llvm::TargetData(ModuleDataLayout))
      PM.add(TD);

#if 0
  FIXME REMOVE
  llvm::createStandardFunctionPasses(&PM, 3 /* OptLevel*/);
  llvm::createStandardModulePasses(&PM,
                                   3, /* OptimizationLevel */
                                   true, /* OptimizeSize */
                                   true, /* UnitAtATime */
                                   true, /* UnrollLoops */
                                   true, /* SimplifyLibCalls */
                                   false, /* HaveExceptions */
                                   NULL /* InliningPass */);
#endif

  llvm::PassManagerBuilder PMBuilder;
  //PMBuilder.OptLevel = 3;
  //PMBuilder.populateFunctionPassManager(PM);

  PMBuilder.OptLevel = 3;
  PMBuilder.SizeLevel = true;
  PMBuilder.DisableUnitAtATime = false;
  PMBuilder.DisableUnrollLoops = false;
  PMBuilder.DisableSimplifyLibCalls = false;
  PMBuilder.populateModulePassManager(PM);

  PM.run(*M);
  return M;
}

int main(int argc, char** argv) {
  llvm::llvm_shutdown_obj _ShutdownObj;
  llvm::cl::ParseCommandLineOptions(argc, argv, "P-NDK Link Tool");

  llvm::LLVMContext& Ctx = llvm::getGlobalContext();
  std::string ErrMsg;
  llvm::raw_fd_ostream FOS(OutputFilenames[0].c_str(), ErrMsg);
  assert(!FOS.has_error());

  // No need to link (just one file).
  // Output Directly.
  if (InputFilenames.size() == 1) {
    llvm::OwningPtr<llvm::Module> M(getModuleFromFilename(InputFilenames[0],
                                                           Ctx,
                                                           ErrMsg));
    llvm::WriteBitcodeToFile(M.get(), FOS);
    return 0;
  }

  llvm::OwningPtr<llvm::Module> M(linkFilesToModule(InputFilenames, Ctx));
  llvm::WriteBitcodeToFile(M.get(), FOS);
  assert(!FOS.has_error());
  return 0;
}