// HashCon.cpp

#include "StdAfx.h"

#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"

#include "../../../Windows/ErrorMsg.h"

#include "ConsoleClose.h"
#include "HashCon.h"

static const wchar_t *kEmptyFileAlias = L"[Content]";

static const char *kScanningMessage = "Scanning";

HRESULT CHashCallbackConsole::CheckBreak()
{
  return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK;
}

HRESULT CHashCallbackConsole::StartScanning()
{
  (*OutStream) << kScanningMessage;
  return CheckBreak();
}

HRESULT CHashCallbackConsole::ScanProgress(UInt64 /* numFolders */, UInt64 /* numFiles */, UInt64 /* totalSize */, const wchar_t * /* path */, bool /* isDir */)
{
  return CheckBreak();
}

HRESULT CHashCallbackConsole::CanNotFindError(const wchar_t *name, DWORD systemError)
{
  return CanNotFindError_Base(name, systemError);
}

HRESULT CHashCallbackConsole::FinishScanning()
{
  (*OutStream) << endl << endl;
  return CheckBreak();
}

HRESULT CHashCallbackConsole::SetNumFiles(UInt64 /* numFiles */)
{
  return CheckBreak();
}

HRESULT CHashCallbackConsole::SetTotal(UInt64 size)
{
  if (EnablePercents)
    m_PercentPrinter.SetTotal(size);
  return CheckBreak();
}

HRESULT CHashCallbackConsole::SetCompleted(const UInt64 *completeValue)
{
  if (completeValue && EnablePercents)
  {
    m_PercentPrinter.SetRatio(*completeValue);
    m_PercentPrinter.PrintRatio();
  }
  return CheckBreak();
}

static void AddMinuses(AString &s, unsigned num)
{
  for (unsigned i = 0; i < num; i++)
    s += '-';
}

static void SetSpaces(char *s, int num)
{
  for (int i = 0; i < num; i++)
    s[i] = ' ';
}

static void SetSpacesAndNul(char *s, int num)
{
  SetSpaces(s, num);
  s[num] = 0;
}

static void AddSpaces(UString &s, int num)
{
  for (int i = 0; i < num; i++)
    s += ' ';
}

static const int kSizeField_Len = 13;
static const int kNameField_Len = 12;

static unsigned GetColumnWidth(unsigned digestSize)
{
  unsigned width = digestSize * 2;
  const unsigned kMinColumnWidth = 8;
  return width < kMinColumnWidth ? kMinColumnWidth: width;
}

void CHashCallbackConsole::PrintSeparatorLine(const CObjectVector<CHasherState> &hashers)
{
  AString s;
  for (unsigned i = 0; i < hashers.Size(); i++)
  {
    const CHasherState &h = hashers[i];
    AddMinuses(s, GetColumnWidth(h.DigestSize));
    s += ' ';
  }
  AddMinuses(s, kSizeField_Len);
  s += "  ";
  AddMinuses(s, kNameField_Len);
  m_PercentPrinter.PrintString(s);
  m_PercentPrinter.PrintNewLine();
}

HRESULT CHashCallbackConsole::BeforeFirstFile(const CHashBundle &hb)
{
  UString s;
  FOR_VECTOR (i, hb.Hashers)
  {
    const CHasherState &h = hb.Hashers[i];
    s += h.Name;
    AddSpaces(s, (int)GetColumnWidth(h.DigestSize) - h.Name.Len() + 1);
  }
  UString s2 = L"Size";
  AddSpaces(s, kSizeField_Len - s2.Len());
  s += s2;
  s += L"  ";
  s += L"Name";
  m_PercentPrinter.PrintString(s);
  m_PercentPrinter.PrintNewLine();
  PrintSeparatorLine(hb.Hashers);
  return CheckBreak();
}

HRESULT CHashCallbackConsole::OpenFileError(const wchar_t *name, DWORD systemError)
{
  FailedCodes.Add(systemError);
  FailedFiles.Add(name);
  // if (systemError == ERROR_SHARING_VIOLATION)
  {
    m_PercentPrinter.PrintString(name);
    m_PercentPrinter.PrintString(": WARNING: ");
    m_PercentPrinter.PrintString(NWindows::NError::MyFormatMessage(systemError));
    return S_FALSE;
  }
  // return systemError;
}

HRESULT CHashCallbackConsole::GetStream(const wchar_t *name, bool /* isFolder */)
{
  m_FileName = name;
  return CheckBreak();
}

void CHashCallbackConsole::PrintResultLine(UInt64 fileSize,
    const CObjectVector<CHasherState> &hashers, unsigned digestIndex, bool showHash)
{
  FOR_VECTOR (i, hashers)
  {
    const CHasherState &h = hashers[i];

    char s[k_HashCalc_DigestSize_Max * 2 + 64];
    s[0] = 0;
    if (showHash)
      AddHashHexToString(s, h.Digests[digestIndex], h.DigestSize);
    SetSpacesAndNul(s + strlen(s), (int)GetColumnWidth(h.DigestSize) - (int)strlen(s) + 1);
    m_PercentPrinter.PrintString(s);
  }
  char s[64];
  s[0] = 0;
  char *p = s;
  if (showHash && fileSize != 0)
  {
    p = s + 32;
    ConvertUInt64ToString(fileSize, p);
    int numSpaces = kSizeField_Len - (int)strlen(p);
    if (numSpaces > 0)
    {
      p -= numSpaces;
      SetSpaces(p, numSpaces);
    }
  }
  else
    SetSpacesAndNul(s, kSizeField_Len - (int)strlen(s));
  unsigned len = (unsigned)strlen(p);
  p[len] = ' ';
  p[len + 1] = ' ';
  p[len + 2] = 0;
  m_PercentPrinter.PrintString(p);
}

HRESULT CHashCallbackConsole::SetOperationResult(UInt64 fileSize, const CHashBundle &hb, bool showHash)
{
  PrintResultLine(fileSize, hb.Hashers, k_HashCalc_Index_Current, showHash);
  if (m_FileName.IsEmpty())
    m_PercentPrinter.PrintString(kEmptyFileAlias);
  else
    m_PercentPrinter.PrintString(m_FileName);
  m_PercentPrinter.PrintNewLine();
  return S_OK;
}

static const char *k_DigestTitles[] =
{
    " :"
  , " for data:              "
  , " for data and names:    "
  , " for streams and names: "
};

static void PrintSum(CStdOutStream &p, const CHasherState &h, unsigned digestIndex)
{
  char s[k_HashCalc_DigestSize_Max * 2 + 64];
  UString name = h.Name;
  AddSpaces(name, 6 - (int)name.Len());
  p << name;
  p << k_DigestTitles[digestIndex];
  s[0] = 0;
  AddHashHexToString(s, h.Digests[digestIndex], h.DigestSize);
  p << s;
  p << "\n";
}


void PrintHashStat(CStdOutStream &p, const CHashBundle &hb)
{
  FOR_VECTOR (i, hb.Hashers)
  {
    const CHasherState &h = hb.Hashers[i];
    p << "\n";
    PrintSum(p, h, k_HashCalc_Index_DataSum);
    if (hb.NumFiles != 1 || hb.NumDirs != 0)
      PrintSum(p, h, k_HashCalc_Index_NamesSum);
    if (hb.NumAltStreams != 0)
      PrintSum(p, h, k_HashCalc_Index_StreamsSum);
  }
}

void CHashCallbackConsole::PrintProperty(const char *name, UInt64 value)
{
  char s[32];
  s[0] = ':';
  s[1] = ' ';
  ConvertUInt64ToString(value, s + 2);
  m_PercentPrinter.PrintString(name);
  m_PercentPrinter.PrintString(s);
  m_PercentPrinter.PrintNewLine();
}

HRESULT CHashCallbackConsole::AfterLastFile(const CHashBundle &hb)
{
  PrintSeparatorLine(hb.Hashers);

  PrintResultLine(hb.FilesSize, hb.Hashers, k_HashCalc_Index_DataSum, true);
  m_PercentPrinter.PrintNewLine();
  m_PercentPrinter.PrintNewLine();
  
  if (hb.NumFiles != 1 || hb.NumDirs != 0)
  {
    if (hb.NumDirs != 0)
      PrintProperty("Folders", hb.NumDirs);
    PrintProperty("Files", hb.NumFiles);
  }
  PrintProperty("Size", hb.FilesSize);
  if (hb.NumAltStreams != 0)
  {
    PrintProperty("AltStreams", hb.NumAltStreams);
    PrintProperty("AltStreams size", hb.AltStreamsSize);
  }
  PrintHashStat(*m_PercentPrinter.OutStream, hb);
  m_PercentPrinter.PrintNewLine();
  return S_OK;
}