#include "stdinc.h"
#include "win32file.h"
#include "atlbase.h"
#include "atlconv.h"

#define IS_NT() ((GetVersion() & 0x80000000) == 0)

using std::wstring;

void Win32File::snarfFullFile(wstring& output)
{
    output = L"";
    CByteVector bytes;
    DWORD dwReadSize = 0;

    bytes.resize(filesize() - filepointer());
    if (!ReadFile(_hFile, &bytes.front(), bytes.size(), &dwReadSize, NULL))
    {
        throw new ReadWriteError(false, ::GetLastError());
    }

    // Otherwise, bash the byte vector into a wstring for output
    output = ConvertToWstring(bytes, _type);
}




void Win32File::writeLine(const wstring& dump)
{
    CByteVector vb;
    DWORD dwWritten;

    vb = ConvertWstringToDestination(dump, _type);
    if (!WriteFile(_hFile, &vb.front(), vb.size(), &dwWritten, NULL))
        throw new ReadWriteError(true, ::GetLastError());
}



bool Win32File::eof() { return false; }

void
Win32File::openForRead(wstring wstname)
{
    USES_CONVERSION;

    if (_hFile != INVALID_HANDLE_VALUE)
    {
        throw new AlreadyOpened;
    }

    const PCWSTR pcwstr = wstname.c_str();
    const DWORD Flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN;

    _hFile =
        IS_NT() ? CreateFileW(pcwstr, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, Flags, NULL)
                : CreateFileA(W2A(pcwstr), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, Flags, NULL);

    if (_hFile == INVALID_HANDLE_VALUE || _hFile == NULL)
    {
        throw new OpeningError(GetLastError());
    }


    // If this was for reading, then sniff the first few bytes and see what kind
    // of file it is.
    DWORD dwReadBytes = 0;
    BYTE bBuffer[256];
    if (!ReadFile(_hFile, bBuffer, sizeof(bBuffer), &dwReadBytes, NULL))
    {
        throw new ReadWriteError(true, GetLastError());
    }

    // Now let's determine what kind of buffer we've got
    _type = DetermineFileTypeFromBuffer(bBuffer, dwReadBytes);
    _bOpenForRead = true;

    // Let's zip the file pointer past the first gunk
    SetFilePointer(_hFile, DetermineFileTypeSigSize(_type), NULL, FILE_BEGIN);

}




void
Win32File::openForWrite(wstring wstname, FileContentType fct, bool bOverwrite)
{
    USES_CONVERSION;

    if (_hFile != INVALID_HANDLE_VALUE)
    {
        throw new AlreadyOpened;
    }

    const DWORD OpenOrCreate = bOverwrite ? CREATE_ALWAYS : CREATE_NEW;
    const DWORD Flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN;
    PCWSTR pcwstr = wstname.c_str();

    _hFile =
        IS_NT() ? CreateFileW(pcwstr, GENERIC_WRITE, FILE_SHARE_READ, NULL, OpenOrCreate, Flags, NULL)
                : CreateFileA(W2A(pcwstr), GENERIC_WRITE, FILE_SHARE_READ, NULL, OpenOrCreate, Flags, NULL);

    if (_hFile == INVALID_HANDLE_VALUE || _hFile == NULL)
    {
        throw new OpeningError(GetLastError());
    }

    // Now that we have an open file, let's blop the signature bytes into it.
    DWORD dwWritten;
    DWORD dwToWrite;
    PCVOID pvWriteBuffer;
    switch (fct) {
    case FileContentsUnicode:
        pvWriteBuffer = UNICODE_SIGNATURE;
        dwToWrite = sizeof(UNICODE_SIGNATURE);
        break;
    case FileContentsUnicodeBigEndian:
        pvWriteBuffer = UNICODE_BIG_ENDIAN_SIGNATURE;
        dwToWrite = sizeof(UNICODE_BIG_ENDIAN_SIGNATURE);
        break;
    case FileContentsUTF8:
        pvWriteBuffer = NULL;
        dwToWrite = 0;
        break;
    default:
        pvWriteBuffer = UNICODE_SIGNATURE;
        dwToWrite = 0;
        break;
    }

    if (!WriteFile(_hFile, pvWriteBuffer, dwToWrite, &dwWritten, NULL) || (dwWritten != dwToWrite)) {
        throw new ReadWriteError(false, GetLastError());
    }

    _bOpenForRead = false;
    _type = fct;
}




Win32File::Win32File()
    : _hFile(INVALID_HANDLE_VALUE), _type(FileContentsUTF8),
      _bOpenForRead(false), _bEof(false)
{
}





Win32File::~Win32File() { if (_hFile != INVALID_HANDLE_VALUE) { CloseHandle(_hFile); } };


FileContentType DetermineFileTypeFromBuffer(unsigned char *data, int length)
{
    #define CHECKMARK(tp, ct) if ((length > sizeof(tp)) && (memcmp(data, tp, sizeof(tp)) == 0)) return ct;

    CHECKMARK(UNICODE_SIGNATURE, FileContentsUnicode);
    CHECKMARK(UNICODE_BIG_ENDIAN_SIGNATURE, FileContentsUnicodeBigEndian);
    CHECKMARK(UTF8_SIGNATURE, FileContentsUTF8);
    return FileContentsUTF8;
}

int DetermineFileTypeSigSize(FileContentType type)
{
    switch (type)
    {
        case FileContentsUnicode: return sizeof(UNICODE_SIGNATURE);
        case FileContentsUnicodeBigEndian: return sizeof(UNICODE_BIG_ENDIAN_SIGNATURE);
        case FileContentsUTF8:
        default: return 0;
    }
}
