///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// DxcPixCompilationInfo.cpp                                                 //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
// Defines all of the entrypoints for DXC's PIX interfaces for returning     //
// compilation parameters such as target profile, entry point, etc.          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "dxc/Support/WinIncludes.h"

#include "dxc/dxcapi.h"
#include "dxc/dxcpix.h"

#include "dxc/Support/FileIOHelper.h"
#include "dxc/Support/Global.h"
#include "dxc/Support/microcom.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MSFileSystem.h"

#include "DxcPixBase.h"
#include "DxcPixDxilDebugInfo.h"

#include <functional>

#include "dxc/Support/WinIncludes.h"

#include "DxcPixCompilationInfo.h"
#include "DxcPixDxilDebugInfo.h"

#include "dxc/Support/Global.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"

#include "DxilDiaSession.h"

#include <unordered_map>

namespace dxil_debug_info {

struct CompilationInfo : public IDxcPixCompilationInfo {
private:
  DXC_MICROCOM_TM_REF_FIELDS();

  dxil_dia::Session *m_pSession;
  llvm::NamedMDNode *m_contents;
  llvm::NamedMDNode *m_defines;
  llvm::NamedMDNode *m_mainFileName;
  llvm::NamedMDNode *m_arguments;

public:
  CompilationInfo(IMalloc *pMalloc, dxil_dia::Session *pSession);

  DXC_MICROCOM_TM_ADDREF_RELEASE_IMPL();
  DXC_MICROCOM_TM_ALLOC(CompilationInfo);

  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject) final {
    return DoBasicQueryInterface<IDxcPixCompilationInfo>(this, iid, ppvObject);
  }
  virtual STDMETHODIMP GetSourceFile(DWORD SourceFileOrdinal, BSTR *pSourceName,
                                     BSTR *pSourceContents) override;
  virtual STDMETHODIMP GetArguments(BSTR *pArguments) override;
  virtual STDMETHODIMP GetMacroDefinitions(BSTR *pMacroDefinitions) override;
  virtual STDMETHODIMP GetEntryPointFile(BSTR *pEntryPointFile) override;
  virtual STDMETHODIMP GetHlslTarget(BSTR *pHlslTarget) override;
  virtual STDMETHODIMP GetEntryPoint(BSTR *pEntryPoint) override;
};

CompilationInfo::CompilationInfo(IMalloc *pMalloc, dxil_dia::Session *pSession)
    : m_pMalloc(pMalloc), m_pSession(pSession) {
  auto *Module = m_pSession->DxilModuleRef().GetModule();
  m_contents =
      Module->getNamedMetadata(hlsl::DxilMDHelper::kDxilSourceContentsMDName);
  if (!m_contents)
    m_contents = Module->getNamedMetadata("llvm.dbg.contents");

  m_defines =
      Module->getNamedMetadata(hlsl::DxilMDHelper::kDxilSourceDefinesMDName);
  if (!m_defines)
    m_defines = Module->getNamedMetadata("llvm.dbg.defines");

  m_mainFileName = Module->getNamedMetadata(
      hlsl::DxilMDHelper::kDxilSourceMainFileNameMDName);
  if (!m_mainFileName)
    m_mainFileName = Module->getNamedMetadata("llvm.dbg.mainFileName");

  m_arguments =
      Module->getNamedMetadata(hlsl::DxilMDHelper::kDxilSourceArgsMDName);
  if (!m_arguments)
    m_arguments = Module->getNamedMetadata("llvm.dbg.args");
}

static void MDStringOperandToBSTR(llvm::MDOperand const &mdOperand,
                                  BSTR *pBStr) {
  llvm::StringRef MetadataAsStringRef =
      llvm::dyn_cast<llvm::MDString>(mdOperand)->getString();
  std::string StringWithTerminator(MetadataAsStringRef.begin(),
                                   MetadataAsStringRef.size());
  CA2W cv(StringWithTerminator.c_str());
  CComBSTR BStr;
  BStr.Append(cv);
  BStr.Append(L"\0", 1);
  *pBStr = BStr.Detach();
}

STDMETHODIMP
CompilationInfo::GetSourceFile(DWORD SourceFileOrdinal, BSTR *pSourceName,
                               BSTR *pSourceContents) {
  *pSourceName = nullptr;
  *pSourceContents = nullptr;

  if (SourceFileOrdinal >= m_contents->getNumOperands()) {
    return E_INVALIDARG;
  }

  llvm::MDTuple *FileTuple =
      llvm::cast<llvm::MDTuple>(m_contents->getOperand(SourceFileOrdinal));

  MDStringOperandToBSTR(FileTuple->getOperand(0), pSourceName);
  MDStringOperandToBSTR(FileTuple->getOperand(1), pSourceContents);

  return S_OK;
}

STDMETHODIMP CompilationInfo::GetArguments(BSTR *pArguments) {
  llvm::MDNode *argsNode = m_arguments->getOperand(0);

  // Don't return any arguments that denote things that are returned via
  // other methods in this class (and that PIX isn't expecting to see
  // in the arguments list):
  const char *specialCases[] = {
      "/T", "-T", "-D", "/D", "-E", "/E",
  };

  // Concatenate arguments into one string
  CComBSTR pBSTR;
  for (llvm::MDNode::op_iterator it = argsNode->op_begin();
       it != argsNode->op_end(); ++it) {
    llvm::StringRef strRef = llvm::dyn_cast<llvm::MDString>(*it)->getString();

    bool skip = false;
    bool skipTwice = false;
    for (unsigned i = 0; i < _countof(specialCases); i++) {
      // It's legal for users to specify, for example, /Emain or /E main:
      if (strRef == specialCases[i]) {
        skipTwice = true;
        skip = true;
        break;
      } else if (strRef.startswith(specialCases[i])) {
        skip = true;
        break;
      }
    }

    if (skip) {
      if (skipTwice)
        ++it;
      continue;
    }

    std::string str(strRef.begin(), strRef.size());
    CA2W cv(str.c_str());
    pBSTR.Append(cv);
    pBSTR.Append(L" ", 1);
  }
  pBSTR.Append(L"\0", 1);
  *pArguments = pBSTR.Detach();
  return S_OK;
}

STDMETHODIMP CompilationInfo::GetMacroDefinitions(BSTR *pMacroDefinitions) {
  llvm::MDNode *definesNode = m_defines->getOperand(0);
  // Concatenate definitions into one string separated by spaces
  CComBSTR pBSTR;
  for (llvm::MDNode::op_iterator it = definesNode->op_begin();
       it != definesNode->op_end(); ++it) {
    llvm::StringRef strRef = llvm::dyn_cast<llvm::MDString>(*it)->getString();
    std::string str(strRef.begin(), strRef.size());

    // PIX is expecting quoted strings as the definitions. So if no quotes were
    // given, add them now:
    auto findEquals = str.find_first_of("=");
    if (findEquals != std::string::npos && findEquals + 1 < str.length()) {
      std::string definition = str.substr(findEquals + 1);
      if (definition.front() != '\"') {
        definition.insert(definition.begin(), '\"');
      }
      if (definition.back() != '\"') {
        definition.push_back('\"');
      }
      std::string name = str.substr(0, findEquals);
      str = name + "=" + definition;
    }

    CA2W cv(str.c_str());
    pBSTR.Append(L"-D", 2);
    pBSTR.Append(cv);
    pBSTR.Append(L" ", 1);
  }
  pBSTR.Append(L"\0", 1);
  *pMacroDefinitions = pBSTR.Detach();
  return S_OK;
}

STDMETHODIMP
CompilationInfo::GetEntryPointFile(BSTR *pEntryPointFile) {
  llvm::StringRef strRef = llvm::dyn_cast<llvm::MDString>(
                               m_mainFileName->getOperand(0)->getOperand(0))
                               ->getString();
  std::string str(strRef.begin(),
                  strRef.size()); // To make sure str is null terminated
  CA2W cv(str.c_str());
  CComBSTR pBSTR;
  pBSTR.Append(cv);
  *pEntryPointFile = pBSTR.Detach();
  return S_OK;
}

STDMETHODIMP
CompilationInfo::GetHlslTarget(BSTR *pHlslTarget) {
  CA2W cv(m_pSession->DxilModuleRef().GetShaderModel()->GetName());
  CComBSTR pBSTR;
  pBSTR.Append(cv);
  *pHlslTarget = pBSTR.Detach();
  return S_OK;
}

STDMETHODIMP
CompilationInfo::GetEntryPoint(BSTR *pEntryPoint) {
  auto name = m_pSession->DxilModuleRef().GetEntryFunctionName();
  CA2W cv(name.c_str());
  CComBSTR pBSTR;
  pBSTR.Append(cv);
  *pEntryPoint = pBSTR.Detach();
  return S_OK;
}

} // namespace dxil_debug_info

HRESULT
dxil_debug_info::CreateDxilCompilationInfo(IMalloc *pMalloc,
                                           dxil_dia::Session *pSession,
                                           IDxcPixCompilationInfo **ppResult) {
  return NewDxcPixDxilDebugInfoObjectOrThrow<CompilationInfo>(ppResult, pMalloc,
                                                              pSession);
}
