// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <string>

#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Core/PowerPC/PPCAnalyst.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/SignatureDB.h"

namespace
{
// On-disk format for SignatureDB entries.
struct FuncDesc
{
  u32 checkSum;
  u32 size;
  char name[128];
};

}  // namespace

bool SignatureDB::Load(const std::string& filename)
{
  File::IOFile f(filename, "rb");
  if (!f)
    return false;
  u32 fcount = 0;
  f.ReadArray(&fcount, 1);
  for (size_t i = 0; i < fcount; i++)
  {
    FuncDesc temp;
    memset(&temp, 0, sizeof(temp));

    f.ReadArray(&temp, 1);
    temp.name[sizeof(temp.name) - 1] = 0;

    DBFunc dbf;
    dbf.name = temp.name;
    dbf.size = temp.size;
    database[temp.checkSum] = dbf;
  }

  return true;
}

bool SignatureDB::Save(const std::string& filename)
{
  File::IOFile f(filename, "wb");
  if (!f)
  {
    ERROR_LOG(OSHLE, "Database save failed");
    return false;
  }
  u32 fcount = (u32)database.size();
  f.WriteArray(&fcount, 1);
  for (const auto& entry : database)
  {
    FuncDesc temp;
    memset(&temp, 0, sizeof(temp));
    temp.checkSum = entry.first;
    temp.size = entry.second.size;
    strncpy(temp.name, entry.second.name.c_str(), 127);
    f.WriteArray(&temp, 1);
  }

  INFO_LOG(OSHLE, "Database save successful");
  return true;
}

// Adds a known function to the hash database
u32 SignatureDB::Add(u32 startAddr, u32 size, const std::string& name)
{
  u32 hash = ComputeCodeChecksum(startAddr, startAddr + size - 4);

  DBFunc temp_dbfunc;
  temp_dbfunc.size = size;
  temp_dbfunc.name = name;

  FuncDB::iterator iter = database.find(hash);
  if (iter == database.end())
    database[hash] = temp_dbfunc;

  return hash;
}

void SignatureDB::List()
{
  for (const auto& entry : database)
  {
    INFO_LOG(OSHLE, "%s : %i bytes, hash = %08x", entry.second.name.c_str(), entry.second.size,
             entry.first);
  }
  INFO_LOG(OSHLE, "%zu functions known in current database.", database.size());
}

void SignatureDB::Clear()
{
  database.clear();
}

void SignatureDB::Apply(PPCSymbolDB* symbol_db)
{
  for (const auto& entry : database)
  {
    u32 hash = entry.first;
    Symbol* function = symbol_db->GetSymbolFromHash(hash);
    if (function)
    {
      // Found the function. Let's rename it according to the symbol file.
      if (entry.second.size == (unsigned int)function->size)
      {
        function->name = entry.second.name;
        INFO_LOG(OSHLE, "Found %s at %08x (size: %08x)!", entry.second.name.c_str(),
                 function->address, function->size);
      }
      else
      {
        function->name = entry.second.name;
        ERROR_LOG(OSHLE, "Wrong size! Found %s at %08x (size: %08x instead of %08x)!",
                  entry.second.name.c_str(), function->address, function->size, entry.second.size);
      }
    }
  }
  symbol_db->Index();
}

void SignatureDB::Initialize(PPCSymbolDB* symbol_db, const std::string& prefix)
{
  for (const auto& symbol : symbol_db->Symbols())
  {
    if ((prefix.empty() && (!symbol.second.name.empty()) &&
         symbol.second.name.substr(0, 3) != "zz_" && symbol.second.name.substr(0, 1) != ".") ||
        ((!prefix.empty()) && symbol.second.name.substr(0, prefix.size()) == prefix))
    {
      DBFunc temp_dbfunc;
      temp_dbfunc.name = symbol.second.name;
      temp_dbfunc.size = symbol.second.size;
      database[symbol.second.hash] = temp_dbfunc;
    }
  }
}

/*static*/ u32 SignatureDB::ComputeCodeChecksum(u32 offsetStart, u32 offsetEnd)
{
  u32 sum = 0;
  for (u32 offset = offsetStart; offset <= offsetEnd; offset += 4)
  {
    u32 opcode = PowerPC::HostRead_Instruction(offset);
    u32 op = opcode & 0xFC000000;
    u32 op2 = 0;
    u32 op3 = 0;
    u32 auxop = op >> 26;
    switch (auxop)
    {
    case 4:  // PS instructions
      op2 = opcode & 0x0000003F;
      switch (op2)
      {
      case 0:
      case 8:
      case 16:
      case 21:
      case 22:
        op3 = opcode & 0x000007C0;
      }
      break;

    case 7:  // addi muli etc
    case 8:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
      op2 = opcode & 0x03FF0000;
      break;

    case 19:  // MCRF??
    case 31:  // integer
    case 63:  // fpu
      op2 = opcode & 0x000007FF;
      break;
    case 59:  // fpu
      op2 = opcode & 0x0000003F;
      if (op2 < 16)
        op3 = opcode & 0x000007C0;
      break;
    default:
      if (auxop >= 32 && auxop < 56)
        op2 = opcode & 0x03FF0000;
      break;
    }
    // Checksum only uses opcode, not opcode data, because opcode data changes
    // in all compilations, but opcodes don't!
    sum = (((sum << 17) & 0xFFFE0000) | ((sum >> 15) & 0x0001FFFF));
    sum = sum ^ (op | op2 | op3);
  }
  return sum;
}
