#include "stdafx.h"
#include <nwdsdefs.h>
#include <algorithm>
#include "Log.h"

namespace
{

const DWORD MAX_REC_LEN = 4096;

const TCHAR FIELD_SEPARATOR = _T('|');

CString FormatOwner( const CString& sOwnerName )
{
  TCHAR buf[MAX_DN_CHARS + 1];
  memset(buf, _T(' '), sizeof(buf));
  buf[MAX_DN_CHARS] = _T('\0');
  _tcsncpy(buf, sOwnerName, sOwnerName.GetLength());
  return CString(buf);
}

} // namespace

CLog::CLog( void ) :
  m_hFile(INVALID_HANDLE_VALUE),
  m_bAutoDelete(FALSE)
{
}

CLog::~CLog( void )
{
  Close();
}

void CLog::Open( LPCTSTR path )
{
  // Открываем файл.
  m_hFile = CreateFile(
    path,
    GENERIC_WRITE | GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
  if (INVALID_HANDLE_VALUE == m_hFile)
  {
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, path);
  }
  m_fileName = path;

  // Заполняем массив смещений:
  
  m_aOffsets.RemoveAll();
  
  DWORD recLen = 0;
  DWORD read;

  LONG off = 0;
  while (ReadFile(m_hFile, &recLen, sizeof(recLen), &read, NULL))
  {
    if (!read)
      break;

    if (read != sizeof(recLen) || recLen > MAX_REC_LEN)
    {
      // Ошибка формата файла!
      int cause = CFileException::generic;
      AfxThrowFileException(cause, 0, m_fileName);
    }

    m_aOffsets.Add(off);
    off += (recLen + sizeof(recLen));

    SetFilePointer(m_hFile, off, NULL, FILE_BEGIN);
  }

  m_bAutoDelete = FALSE;
}

void CLog::Create( LPCTSTR path, BOOL bAutoDelete )
{
  m_hFile = CreateFile(
    path,
    GENERIC_WRITE | GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL);
  if (INVALID_HANDLE_VALUE == m_hFile)
  {
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, path);
  }
  m_fileName = path;
  m_bAutoDelete = bAutoDelete;
  m_aOffsets.RemoveAll();
}

void CLog::Close( void )
{
  if (m_hFile != INVALID_HANDLE_VALUE)
  {
    CloseHandle(m_hFile);
    m_hFile = INVALID_HANDLE_VALUE;
    m_aOffsets.RemoveAll();
    if (m_bAutoDelete)
      DeleteFile(m_fileName);
    m_fileName.Empty();
  }
}

const CString& CLog::FileName( void ) const
{
  return m_fileName;
}

void CLog::Append( const CFileInfo& entry )
{
  // Перейти к концу файла:
  LONG off = SetFilePointer(m_hFile, 0, NULL, FILE_END);

  // Сформировать запись:
  CString s;
  s.Format(
    _T("%s%c%s%c%u\r\n"),
    (LPCTSTR)entry.filePath,
    FIELD_SEPARATOR,
    (LPCTSTR)FormatOwner(entry.ownerName),
    FIELD_SEPARATOR,
    entry.fileSize);
  s.AnsiToOem();

  DWORD written = 0;

  // 1) Сохранить длину записи:
  DWORD recLen = s.GetLength();
  if (!WriteFile(m_hFile, &recLen, sizeof(recLen), &written, NULL))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }
  
  // 2) Сохранить запись:
  if (!WriteFile(m_hFile, (LPCTSTR)s, recLen, &written, NULL))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }

  // Запоминаем смещение:
  m_aOffsets.Add(off);
}

int CLog::Count( void ) const
{
  return m_aOffsets.GetSize();
}

void CLog::GetEntry( int n, CFileInfo* pEntry ) const
{
  // Читаем запись:
  char buf[MAX_REC_LEN];
  DWORD recLen = ReadRecord(n, buf, sizeof(buf));
  
  const char* pBegin = buf;
  const char* pEnd = buf + recLen;
  const char* pSep = std::find(pBegin, pEnd, FIELD_SEPARATOR);
  if (pSep == pEnd)
  {
    // Ошибка формата файла!
    int cause = CFileException::generic;
    AfxThrowFileException(cause, 0, m_fileName);
  }

  pEntry->filePath = CString(pBegin, pSep - pBegin);

  const char* END_OF_LINE = "\r\n";

  const char* pOwner = pSep + 1;
  pSep = std::find(pOwner, pEnd, FIELD_SEPARATOR);
  if (pSep == pEnd)
  {
    // Старый формат (без размера)
    
    // Найти конец строки:
    const char* pEol = std::find_first_of(
      pOwner, pEnd,
      END_OF_LINE, END_OF_LINE + sizeof(END_OF_LINE) - 1);
    if (pEol == pEnd)
    {
      // Ошибка формата файла!
      int cause = CFileException::generic;
      AfxThrowFileException(cause, 0, m_fileName);
    }

    // Имя владельца:
    pEntry->ownerName = CString(pOwner, pEol - pOwner);

    // Размер файла отсутствует.
    pEntry->fileSize = 0;
  }
  else
  {
    // Новый формат (с размером)

    // Имя владельца:
    pEntry->ownerName = CString(pOwner, pSep - pOwner);
    pEntry->ownerName.TrimRight(_T(' '));

    const char* pSize = pSep + 1;

    // Найти конец строки:
    const char* pEol = std::find_first_of(
      pSize, pEnd,
      END_OF_LINE, END_OF_LINE + sizeof(END_OF_LINE) - 1);
    if (pEol == pEnd)
    {
      // Ошибка формата файла!
      int cause = CFileException::generic;
      AfxThrowFileException(cause, 0, m_fileName);
    }
    
    // Размер файла:
    pEntry->fileSize = atoi(CString(pSize, pEol - pSize));
  }

  pEntry->filePath.OemToAnsi();
  pEntry->ownerName.OemToAnsi();
}

void CLog::SetEntry( int n, const CFileInfo& entry )
{
  // Читаем запись:
  const DWORD MAX_REC_LEN = 4096;
  char buf[MAX_REC_LEN];
  DWORD recLen = ReadRecord(n, buf, sizeof(buf));

  // Ищем позицию владельца:
  char* pBegin = buf;
  char* pEnd = buf + recLen;
  char* pOwner = std::find(pBegin, pEnd, FIELD_SEPARATOR);
  if (pOwner == pEnd)
  {
    // Ошибка формата файла!
    int cause = CFileException::generic;
    AfxThrowFileException(cause, 0, m_fileName);
  }
  ++pOwner;

  // Обновляем владельца:
  CString s = FormatOwner(entry.ownerName);
  s.AnsiToOem();
  _tcsncpy(pOwner, s, s.GetLength());

  // Сохраняем результат:
  
  // Определяем смещение в файле до нужной записи
  // (уситываем, что перед записью записана ее длина):
  LONG off = m_aOffsets[n] + sizeof(DWORD);

  // Переходим к необходимой записи:
  if (off != SetFilePointer(m_hFile, off, NULL, FILE_BEGIN))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }

  // Записываем буфер:
  DWORD written = 0;
  if (!WriteFile(m_hFile, buf, recLen, &written, NULL))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }
}

DWORD CLog::ReadRecord(int n, void* pBuf, size_t sz) const
{
  // Определяем смещение в файле до нужной записи:
  LONG off = m_aOffsets[n];

  // Переходим к необходимой записи:
  if (off != SetFilePointer(m_hFile, off, NULL, FILE_BEGIN))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }

  DWORD read = 0;

  // Читаем длину записи:
  DWORD recLen = 0;
  if (!ReadFile(m_hFile, &recLen, sizeof(recLen), &read, NULL))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }

  if (read != sizeof(recLen) || recLen > sz || recLen == 0)
  {
    // Ошибка формата файла!
    int cause = CFileException::generic;
    AfxThrowFileException(cause, 0, m_fileName);
  }

  if (!ReadFile(m_hFile, pBuf, recLen, &read, NULL))
  {
    // Ошибка!
    DWORD err = GetLastError();
    int cause = CFileException::OsErrorToException(err);
    AfxThrowFileException(cause, err, m_fileName);
  }

  if (read != recLen)
  {
    // Ошибка формата файла!
    int cause = CFileException::generic;
    AfxThrowFileException(cause, 0, m_fileName);
  }

  return read;
}
