/// \file PromoteOriginalName.cpp

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//

#include <set>
#include <string>

#include "revng/Model/Pass/PromoteOriginalName.h"
#include "revng/Model/Pass/RegisterModelPass.h"

using namespace llvm;
using namespace model;

static RegisterModelPass R("promote-original-name",
                           "Promote OriginalName fields to CustomName ensuring "
                           "the validity of the model is preserved",
                           model::promoteOriginalName);

class SymbolPromoter {
private:
  std::set<Identifier> GlobalSymbols;
  std::set<Identifier> TakenLocalSymbols;

public:
  void dump() const debug_function {
    for (const Identifier &ID : GlobalSymbols)
      dbg << ID.str().str() << "\n";
  }

public:
  void recordGlobalSymbols(auto &Collection, auto Unwrap) {
    for (auto &Entry2 : Collection) {
      auto *Entry = Unwrap(Entry2);
      if (not Entry->CustomName().empty()) {
        GlobalSymbols.insert(Entry->CustomName());
      }
    }
  }

  void recordLocalSymbols(auto &Collection, auto Unwrap) {
    for (auto &Entry2 : Collection) {
      auto *Entry = Unwrap(Entry2);
      if (not Entry->CustomName().empty()) {
        TakenLocalSymbols.insert(Entry->CustomName());
      }
    }
  }

  void promoteGlobalSymbols(auto &Collection, auto Unwrap) {
    promoteSymbolsImpl(Collection, Unwrap, GlobalSymbols, TakenLocalSymbols);
  }

  void promoteLocalSymbols(auto &Collection, auto Unwrap) {
    std::set<Identifier> LocalBucket;
    promoteSymbolsImpl(Collection, Unwrap, LocalBucket, GlobalSymbols);
  }

private:
  void promoteSymbolsImpl(auto &Collection,
                          auto Unwrap,
                          std::set<Identifier> &Namespace,
                          const std::set<Identifier> &Taken) {
    // TODO: collapse uint8_t typedefs into the primitive type
    for (auto &Wrapped : Collection) {
      auto *Entry = Unwrap(Wrapped);
      if (Entry->CustomName().empty() and not Entry->OriginalName().empty()) {
        // We have an OriginalName but not CustomName
        auto Name = Identifier::fromString(Entry->OriginalName());

        while (Taken.contains(Name) or Namespace.contains(Name))
          Name += "_";

        // Assign name
        Entry->CustomName() = Name;

        // Record new name as taken in the current namespace
        auto [_, Inserted] = Namespace.insert(Name);
        revng_assert(Inserted);
      }
    }
  }
};

/// Promote OriginalNames to CustomNames
void model::promoteOriginalName(TupleTree<model::Binary> &Model) {
  auto AddressOf = [](auto &Entry) { return &Entry; };
  auto Unwrap = [](auto &UC) { return UC.get(); };

  SymbolPromoter Promoter;

  // Reserve symbols we can't use both for local symbols and global symbols
  Promoter.recordGlobalSymbols(Model->Functions(), AddressOf);
  Promoter.recordGlobalSymbols(Model->ImportedDynamicFunctions(), AddressOf);
  Promoter.recordGlobalSymbols(Model->TypeDefinitions(), Unwrap);
  for (auto &UP : Model->TypeDefinitions())
    if (auto *Enum = dyn_cast<model::EnumDefinition>(UP.get()))
      Promoter.recordGlobalSymbols(Enum->Entries(), AddressOf);

  Promoter.recordGlobalSymbols(Model->Segments(), AddressOf);

  // Reserve symbols we can't use for global symbols
  for (auto &UP : Model->TypeDefinitions()) {
    if (auto *Struct = dyn_cast<model::StructDefinition>(UP.get()))
      Promoter.recordLocalSymbols(Struct->Fields(), AddressOf);
    else if (auto *Union = dyn_cast<model::UnionDefinition>(UP.get()))
      Promoter.recordLocalSymbols(Union->Fields(), AddressOf);
    else if (auto *CFT = dyn_cast<model::CABIFunctionDefinition>(UP.get()))
      Promoter.recordLocalSymbols(CFT->Arguments(), AddressOf);
    else if (auto *RFT = dyn_cast<model::RawFunctionDefinition>(UP.get()))
      Promoter.recordLocalSymbols(RFT->Arguments(), AddressOf);
  }

  // Promote global symbols
  Promoter.promoteGlobalSymbols(Model->Functions(), AddressOf);
  Promoter.promoteGlobalSymbols(Model->ImportedDynamicFunctions(), AddressOf);
  Promoter.promoteGlobalSymbols(Model->TypeDefinitions(), Unwrap);
  for (auto &UP : Model->TypeDefinitions())
    if (auto *Enum = dyn_cast<model::EnumDefinition>(UP.get()))
      Promoter.promoteGlobalSymbols(Enum->Entries(), AddressOf);

  Promoter.promoteGlobalSymbols(Model->Segments(), AddressOf);

  // Promote local symbols
  for (auto &UP : Model->TypeDefinitions()) {
    if (auto *Struct = dyn_cast<model::StructDefinition>(UP.get()))
      Promoter.promoteLocalSymbols(Struct->Fields(), AddressOf);
    else if (auto *Union = dyn_cast<model::UnionDefinition>(UP.get()))
      Promoter.promoteLocalSymbols(Union->Fields(), AddressOf);
    else if (auto *CFT = dyn_cast<model::CABIFunctionDefinition>(UP.get()))
      Promoter.promoteLocalSymbols(CFT->Arguments(), AddressOf);
    else if (auto *RFT = dyn_cast<model::RawFunctionDefinition>(UP.get()))
      Promoter.promoteLocalSymbols(RFT->Arguments(), AddressOf);
  }
}
