#include "AllocatorAnalysis.hpp"
#include "ArrayAnalysis.hpp"
#include "EscapeAnalysis.hpp"
#include "FinalizerAnalysis.hpp"
#include "OutputParameterAnalysis.hpp"
#include "InterproceduralDefUse.hpp"
#include "Results.hpp"
#include "Scheme.hpp"
#include "SchemeDepPaths.hpp"
#include "SharedLibraryManipulation.hpp"
#include "StructArrayMemberAnalysis.hpp"
#include "TransitiveDefUse.hpp"
#include "ValueCounter.hpp"

#include <llvm/Module.h>
#include <llvm/PassManager.h>
#include <llvm/Analysis/Dominators.h>
#include <llvm/Analysis/Passes.h>
#include <llvm/Support/CommandLine.h>
#include <llvm/Support/Debug.h>
#include <llvm/Support/ManagedStatic.h>
#include <llvm/System/Signals.h>
#include <llvm/Target/TargetData.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/Scalar.h>

#include <boost/format.hpp>
#include <boost/bind.hpp>

#include <map>
#include <vector>
#include <exception>
#include <memory>
#include <fstream>
#include <algorithm>
#include <cstdlib>
#include <cassert>

using namespace boost;
using namespace llvm;

// This is a little shim to make exceptions from Boost work.
namespace boost
{
  void throw_exception(std::exception const & e)
  {
    throw e;
  }
}


// Command line options
cl::opt<std::string>
InputFilename(cl::Positional, cl::desc("<input shared library>"), cl::init("-"));

cl::opt<std::string>
OutputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"));

cl::list<std::string>
DependencyFiles("d", cl::desc("Dependency file(s)"), cl::value_desc("dep"));

cl::list<std::string>
Annotations("a", cl::desc("External annotations to apply to the analysis."),
            cl::value_desc("annotation"));

cl::opt<bool> PermissiveFuncptr("permissive", cl::desc("Assume that no calls through function pointers allow their arguments to escape"), cl::init(false));
cl::opt<bool> NoNullConstructors("no-null-constructor", cl::desc("Do not consider functions that can return NULL to be constructors."), cl::init(false));
cl::opt<bool> ShowHiddenSymbols("show-hidden", cl::desc("Print out results for hidden symbols, too.  For debugging purposes."), cl::init(false));

/*!
  Determine which directory contains library dependency files.
  
  The contents of this directory are loaded at startup and
  serve as a base set of function properties.  If a function
  does not exist in the current library, this pool of information
  is consulted to fill in gaps.

  This is a compile time setting that may be overridden with an
  environment variable.
 */
static std::string getLibDir()
{
  char * rawEnv = std::getenv("SIGDUMP_DEPENDENCY_PATH");
  if(rawEnv) return rawEnv;

  // If the environment variable is not set, fall back to the
  // compile-time default
  return DEPENDENCY_DIR;
}

/*!
  This is the directory in which scheme code (modules and
  files to eval) is located.

  This is a compile time setting that may be overridden with
  an environement variable.
 */
static std::string getSchemeDir()
{
  char * rawEnv = std::getenv("SIGDUMP_SCHEME_PATH");
  if(rawEnv) return rawEnv;
  
  // If the environment variable is not set, fall back to the
  // compile-time default
  return SCHEME_LIBDIR;
}

// GetFileNameRoot - Helper function to get the basename of a filename.
static inline std::string GetFileNameRoot(const std::string &IFN)
{
  std::string outputFilename;
  int Len = IFN.length();
  if ((Len > 2) &&
      IFN[Len-3] == '.' &&
      IFN[Len-2] == 'b' &&
      IFN[Len-1] == 'c')
  {
    outputFilename = std::string(IFN.begin(), IFN.end()-3); // s/.bc/.s/
  }
  else
  {
    outputFilename = IFN;
  }
  return outputFilename;
}



static void setupPasses(PassManager & Passes, Module * M, results_t & res)
{
  Passes.add(new TargetData(M));
  
  Passes.add(createPromoteMemoryToRegisterPass());

  // LLVM provides several alias analyses - select one here.
  // This could be a runtime setting...
  Pass * alias_analysis =
    // createDSAAPass();
    // createSteensgaardPass();
    // createGlobalsModRefPass();
    createAndersensPass();
    //createModAndersensPass();

  Passes.add(alias_analysis);
  
  // Global value numbering pass; helps to make the alias
  // analysis much more robust
  Passes.add(createGVNPass());

  Passes.add(new StructArrayMember);
  
  Passes.add(new TransitiveDefUse);
  Passes.add(new InterproceduralDefUse);
  
  Passes.add(new ValueCounter);

  Passes.add(new EscapeAnalysis(&res));
  Passes.add(new ArrayAnalysis(&res));
  Passes.add(new AllocatorAnalysis(&res));
  Passes.add(new OutputParameterAnalysis(&res));
  Passes.add(new FinalizerAnalysis(&res));
}

static void collectOutput(Module &M, results_t & res)
{
  DEBUG(M.dump());

  // Step 1
  std::map<const Function*, const GlobalAlias*> functionAliases;
  for(Module::const_alias_iterator it = M.alias_begin(),
        ed = M.alias_end(); it != ed; ++it)
  {
    // This is a hidden alias (why?); ignore it.
    if(it->getVisibility())
      continue;

    if(const Function * fn =
       dyn_cast<const Function>(it->getAliasedGlobal()))
    {
      cerr << format("Alias: %s -> %s\n") % it->getName() % fn->getName();
      functionAliases.insert(std::make_pair(fn, &*it));
    }
  }

  // Remove statics from output
  for(Module::iterator it = M.begin(), ed = M.end(); it != ed; ++it)
  {
    if(it->isDeclaration())
      continue;

    if(!it->hasExternalLinkage() && !ShowHiddenSymbols)
    {
      cerr << format("Static symbol: %s\n") % it->getName();
      res.removeResultsFor(&*it);
    }
  }
  
  // Step 2 and 3
  for(Module::iterator it = M.begin(), ed = M.end(); it != ed; ++it)
  {
    if(it->isDeclaration())
      continue;
    
    if(!it->getVisibility())
    {
      // This is a public symbol, we don't need to do anything.
      // We could check further to see if there is also a visible
      // alias, but that is probably really rare.
      continue;
    }
    
    cerr << format("Hidden symbol: %s\n") % it->getName();
    // This is not a public symbol - see if we have a visible
    // alias for it.
    if(functionAliases.find(&*it) == functionAliases.end())
    {
      // There are none, this will not be exported.
      if(!ShowHiddenSymbols)
      {
        res.removeResultsFor(&*it);
      }
      continue;
    }

    // Register the alias with the analysis_results; it
    // will have to call into guile to do the renaming.
    const GlobalAlias * A = functionAliases[&*it];
    res.addAlias(it, A);
  }
}

int main(int argc, char **argv)
{
  // An object to manage the destruction of LLVM global statics.
  // This is required to get proper statistics reports with the
  // -stats and -time-passes flags.
  llvm_shutdown_obj X;

  cl::ParseCommandLineOptions(argc, argv, " LLVM Signature Dumper");

  try
  {
    // Initialize guile
    // This step includes loading partial interface definitions from the
    // shared library, which are then interned in guile as it loads.
    std::string sexpressions = extractPartialInterfaceDescription(InputFilename);
    guileInit(getSchemeDir(), sexpressions);


    // Dependency files are loaded in two phases:
    // 1) (Present) implicit dependencies
    //    These are determined by running ldd on the input library
    //    and loading any dependency files we happen to have that
    //    match the sonames.  Warnings are printed when an implicit
    //    dependency is not found.
    // 2) Explicit dependencies
    //    Specified via -d flags on the command line.
    //
    // Load each of the provided dependency files.  These are
    // just parsed for now - the analysis happens on-demand.
    // This needs to happen after Guile is initialized, since it
    // uses Scheme functions to do its job.
    std::vector<std::string> implicitDeps(findImplicitDependencies(InputFilename));
    DependencyFiles.insert(DependencyFiles.end(), implicitDeps.begin(), implicitDeps.end());
    std::for_each(DependencyFiles.begin(), DependencyFiles.end(),
                  boost::bind(guileLoadDep, getLibDir(), _1));

    std::for_each(Annotations.begin(), Annotations.end(), guileLoadAnnotation);
  
    sys::PrintStackTraceOnErrorSignal();

    std::auto_ptr<Module> M = assembleBitcodeFile(InputFilename);

    if(M.get() == 0)
    {
      cerr << format("%s: bytecode did not read correctly\n") % argv[0];
      return 1;
    }

    results_t res(M.get());
    PassManager Passes;
    setupPasses(Passes, M.get(), res);
    guileInternAnnotationData(M.get(), res);
    
    // Run our queue of passes all at once now, efficiently.
    Passes.run(*M.get());

    collectOutput(*M, res);
    if(!sexpressions.empty())
    {
      res.annotateSignature();
    }
    
    guileWriteOutput(OutputFilename);
    
    return 0;
  }
  catch(std::string msg)
  {
    cerr << format("%s: %s\n") % argv[0] % msg;
  }
  catch(...)
  {
    cerr << format("%s: Unknown exception\n") % argv[0];
  }

  return 1;
}

