#pragma once

#include <map>
#include <memory>
#include <string>
#include <vector>

namespace llvm {
class CallInst;
class Function;
class Module;
class Type;
} // namespace llvm

// Combines DXIL raytracing shaders together into a compute shader.
//
// The incoming module should contain the following functions if the
// corresponding intrinsic are called by the specified shaders, if called:
//    Fallback_TraceRay()
//    Fallback_Ignore()
//    Fallback_AcceptHitAndEndSearch()
//    Fallback_ReportHit()
//
// Fallback_TraceRay() will be called with the original arguments, substituting
// the offset of the payload on the stack for the actual payload.
// Fallback_TraceRay() will also be used to replace calls to TraceRayTest().
//
// ReportHit() returns a boolean. But to handle the abort of the intersection
// shader when AcceptHitAndEndSearch() is called we need a third return value.
// Fallback_ReportHit() should return an integer < 0 for end search, 0 for
// ignore, and > 0 for accept.
//
// The module should also contain a single call to Fallback_Scheduler() in the
// entry shader for the raytracing compute shader.
//
// resizeStack() needs to be called after inlining everything in the compute
// shader.
//
// Currently the main scheduling loop and the implementation for intrinsic
// functions come from an internal runtime module.
class DxrFallbackCompiler {
public:
  typedef std::map<int, std::string> IntToFuncNameMap;

  // If findCalledShaders is true, then the list of shaderNames is expanded to
  // include shader functions (functions with attribute "exp-shader") that are
  // called by functions in shaderNames. Shader entry state IDs are still
  // returned only for those originally in shaderNames. findCalledShaders used
  // for testing.
  DxrFallbackCompiler(llvm::Module *mod,
                      const std::vector<std::string> &shaderNames,
                      unsigned maxAttributeSize, unsigned stackSizeInBytes,
                      bool findCalledShaders = false);

  // 0 - no debug output
  // 1 - dump initial combined module, compiled module, and final linked module
  // 2 - dump intermediate stages of SFT to console
  // 3 - dump intermediate stages of SFT to file
  void setDebugOutputLevel(int val);

  // Returns the entry state id for each of shaderNames. The transformations
  // are performed in place on the module.
  void compile(std::vector<int> &shaderEntryStateIds,
               std::vector<unsigned int> &shaderStackSizes,
               IntToFuncNameMap *pCachedMap);
  void link(std::vector<int> &shaderEntryStateIds,
            std::vector<unsigned int> &shaderStackSizes,
            IntToFuncNameMap *pCachedMap);
  // TODO: Ideally we would run this after inlining everything at the end of
  // compile. Until we figure out to do this, we will call the function after
  // the final link.
  static void resizeStack(llvm::Function *F, unsigned stackSizeInBytes);

private:
  typedef std::map<int, llvm::Function *> IntToFuncMap;
  typedef std::map<std::string, llvm::Function *> StringToFuncMap;

  llvm::Module *m_module = nullptr;
  const std::vector<std::string> &m_entryShaderNames;
  unsigned m_stackSizeInBytes = 0;
  unsigned m_maxAttributeSize = 0;
  bool m_findCalledShaders = false;
  int m_debugOutputLevel = 0;

  StringToFuncMap m_shaderMap;

  void initShaderMap(std::vector<std::string> &shaderNames);
  void linkRuntime();
  void lowerAnyHitControlFlowFuncs();
  void lowerReportHit();
  void lowerTraceRay(llvm::Type *runtimeDataArgTy);
  void createStateFunctions(IntToFuncMap &stateFunctionMap,
                            std::vector<int> &shaderEntryStateIds,
                            std::vector<unsigned int> &shaderStackSizes,
                            int baseStateId,
                            const std::vector<std::string> &shaderNames,
                            llvm::Type *runtimeDataArgTy);
  void createLaunchParams(llvm::Function *func);
  void createStack(llvm::Function *func);
  void createStateDispatch(llvm::Function *func,
                           const IntToFuncMap &stateFunctionMap,
                           llvm::Type *runtimeDataArgTy);
  void lowerIntrinsics();

  llvm::Type *getRuntimeDataArgType();
  llvm::Function *createDispatchFunction(const IntToFuncMap &stateFunctionMap,
                                         llvm::Type *runtimeDataArgTy);

  // These functions return calls only in shaders in m_shaderMap.
  std::vector<llvm::CallInst *>
  getCallsInShadersToFunction(const std::string &funcName);
  std::vector<llvm::CallInst *>
  getCallsInShadersToFunctionWithPrefix(const std::string &funcNamePrefix);
};
