/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#include "cmFilePathChecksum.h"

#include <vector>

#include "cmBase32.h"
#include "cmCryptoHash.h"
#include "cmMakefile.h"
#include "cmSystemTools.h"

cmFilePathChecksum::cmFilePathChecksum() = default;

cmFilePathChecksum::cmFilePathChecksum(std::string const& currentSrcDir,
                                       std::string const& currentBinDir,
                                       std::string const& projectSrcDir,
                                       std::string const& projectBinDir)
{
  this->setupParentDirs(currentSrcDir, currentBinDir, projectSrcDir,
                        projectBinDir);
}

cmFilePathChecksum::cmFilePathChecksum(cmMakefile* makefile)
{
  this->setupParentDirs(makefile->GetCurrentSourceDirectory(),
                        makefile->GetCurrentBinaryDirectory(),
                        makefile->GetHomeDirectory(),
                        makefile->GetHomeOutputDirectory());
}

void cmFilePathChecksum::setupParentDirs(std::string const& currentSrcDir,
                                         std::string const& currentBinDir,
                                         std::string const& projectSrcDir,
                                         std::string const& projectBinDir)
{
  this->parentDirs[0].first = cmSystemTools::GetRealPath(currentSrcDir);
  this->parentDirs[1].first = cmSystemTools::GetRealPath(currentBinDir);
  this->parentDirs[2].first = cmSystemTools::GetRealPath(projectSrcDir);
  this->parentDirs[3].first = cmSystemTools::GetRealPath(projectBinDir);

  this->parentDirs[0].second = "CurrentSource";
  this->parentDirs[1].second = "CurrentBinary";
  this->parentDirs[2].second = "ProjectSource";
  this->parentDirs[3].second = "ProjectBinary";
}

std::string cmFilePathChecksum::get(std::string const& filePath) const
{
  std::string relPath;
  std::string relSeed;
  {
    std::string const fileReal = cmSystemTools::GetRealPath(filePath);
    std::string parentDir;
    // Find closest project parent directory
    for (auto const& pDir : this->parentDirs) {
      if (!pDir.first.empty() &&
          cmsys::SystemTools::IsSubDirectory(fileReal, pDir.first)) {
        parentDir = pDir.first;
        relSeed = pDir.second;
        break;
      }
    }
    // Use file system root as fallback parent directory
    if (parentDir.empty()) {
      relSeed = "FileSystemRoot";
      cmsys::SystemTools::SplitPathRootComponent(fileReal, &parentDir);
    }
    // Calculate relative path from project parent directory
    relPath = cmsys::SystemTools::RelativePath(
      parentDir, cmsys::SystemTools::GetParentDirectory(fileReal));
  }

  // Calculate the file ( seed + relative path ) binary checksum
  std::vector<unsigned char> hashBytes =
    cmCryptoHash(cmCryptoHash::AlgoSHA256).ByteHashString(relSeed + relPath);

  // Convert binary checksum to string
  return cmBase32Encoder().encodeString(hashBytes.data(), hashBytes.size(),
                                        false);
}

std::string cmFilePathChecksum::getPart(std::string const& filePath,
                                        size_t length) const
{
  return this->get(filePath).substr(0, length);
}
