#include "MCATestBase.h"
#include "Views/SummaryView.h"
#include "llvm/MCA/CustomBehaviour.h"
#include "llvm/MCA/InstrBuilder.h"
#include "llvm/MCA/Pipeline.h"
#include "llvm/MCA/SourceMgr.h"
#include "llvm/MCA/View.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/WithColor.h"
#include <string>

using namespace llvm;
using namespace mca;

const Target *MCATestBase::getLLVMTarget() const {
  std::string Error;
  return TargetRegistry::lookupTarget(TheTriple, Error);
}

mca::PipelineOptions MCATestBase::getDefaultPipelineOptions() {
  mca::PipelineOptions PO(/*MicroOpQueue=*/0, /*DecoderThroughput=*/0,
                          /*DispatchWidth=*/0,
                          /*RegisterFileSize=*/0,
                          /*LoadQueueSize=*/0, /*StoreQueueSize=*/0,
                          /*AssumeNoAlias=*/true,
                          /*EnableBottleneckAnalysis=*/false);
  return PO;
}

void MCATestBase::SetUp() {
  TheTarget = getLLVMTarget();
  ASSERT_NE(TheTarget, nullptr);

  STI.reset(TheTarget->createMCSubtargetInfo(TheTriple, CPUName, MAttr));
  ASSERT_TRUE(STI);
  ASSERT_TRUE(STI->isCPUStringValid(CPUName));

  MRI.reset(TheTarget->createMCRegInfo(TheTriple));
  ASSERT_TRUE(MRI);

  auto MCOptions = getMCTargetOptions();
  MAI.reset(TheTarget->createMCAsmInfo(*MRI, TheTriple, MCOptions));
  ASSERT_TRUE(MAI);

  Ctx = std::make_unique<MCContext>(TheTriple, MAI.get(), MRI.get(), STI.get());
  MOFI.reset(TheTarget->createMCObjectFileInfo(*Ctx, /*PIC=*/false));
  Ctx->setObjectFileInfo(MOFI.get());

  MCII.reset(TheTarget->createMCInstrInfo());
  ASSERT_TRUE(MCII);

  MCIA.reset(TheTarget->createMCInstrAnalysis(MCII.get()));
  ASSERT_TRUE(MCIA);

  IP.reset(TheTarget->createMCInstPrinter(TheTriple, /*AssemblerDialect=*/0,
                                          *MAI, *MCII, *MRI));
  ASSERT_TRUE(IP);
}

Error MCATestBase::runBaselineMCA(
    json::Object &Result, ArrayRef<MCInst> Insts, ArrayRef<mca::View *> Views,
    const mca::PipelineOptions *PO,
    ArrayRef<std::pair<StringRef, StringRef>> Descs) {
  mca::Context MCA(*MRI, *STI);

  // Enable instruments when descriptions are provided
  auto IM =
      std::make_unique<mca::InstrumentManager>(*STI, *MCII, !Descs.empty());
  mca::InstrBuilder IB(*STI, *MCII, *MRI, MCIA.get(), *IM, /*CallLatency=*/100);

  SmallVector<mca::Instrument *> Instruments;
  SmallVector<mca::UniqueInstrument> InstrumentsOwner;
  for (const auto &Desc : Descs) {
    auto I = IM->createInstrument(Desc.first, Desc.second);
    Instruments.push_back(I.get());
    InstrumentsOwner.push_back(std::move(I));
  }
  SmallVector<std::unique_ptr<mca::Instruction>> LoweredInsts;
  for (const auto &MCI : Insts) {
    Expected<std::unique_ptr<mca::Instruction>> Inst =
        IB.createInstruction(MCI, Instruments);
    if (!Inst) {
      if (auto NewE =
              handleErrors(Inst.takeError(),
                           [this](const mca::InstructionError<MCInst> &IE) {
                             std::string InstructionStr;
                             raw_string_ostream SS(InstructionStr);
                             WithColor::error() << IE.Message << '\n';
                             IP->printInst(&IE.Inst, 0, "", *STI, SS);
                             WithColor::note()
                                 << "instruction: " << InstructionStr << '\n';
                           })) {
        // Default case.
        return NewE;
      }
    } else {
      LoweredInsts.emplace_back(std::move(Inst.get()));
    }
  }

  mca::CircularSourceMgr SM(LoweredInsts, /*Iterations=*/1);

  // Empty CustomBehaviour.
  auto CB = std::make_unique<mca::CustomBehaviour>(*STI, SM, *MCII);

  mca::PipelineOptions ThePO = PO ? *PO : getDefaultPipelineOptions();
  auto P = MCA.createDefaultPipeline(ThePO, SM, *CB);

  SmallVector<std::unique_ptr<mca::View>, 1> DefaultViews;
  if (Views.empty()) {
    // By default, we only add SummaryView.
    auto SV = std::make_unique<SummaryView>(STI->getSchedModel(), Insts,
                                            ThePO.DispatchWidth);
    P->addEventListener(SV.get());
    DefaultViews.emplace_back(std::move(SV));
  } else {
    for (auto *V : Views)
      P->addEventListener(V);
  }

  // Run the pipeline.
  Expected<unsigned> Cycles = P->run();
  if (!Cycles)
    return Cycles.takeError();

  for (const auto *V : Views)
    Result[V->getNameAsString()] = V->toJSON();
  for (const auto &V : DefaultViews)
    Result[V->getNameAsString()] = V->toJSON();

  return Error::success();
}
