// Copyright (c) 2004 by Digital Mars
// All Rights Reserved
// written by Walter Bright and Matthew Wilson (Sysesis Software Pty Ltd.)
// www.digitalmars.com
// www.synesis.com.au/software

/* NOTE: This file has been patched from the original DMD distribution to
   work with the GDC compiler.

   Modified by David Friedman, September 2004
*/

/*
 * Memory mapped files.
 */

module mmfile;

private import std.file;
private import std.c.stdio;
private import std.c.stdlib;
private import std.path;
private import std.string;

//debug = MMFILE;

version (Win32)
{
    private import std.c.windows.windows;
    private import std.utf;

    private int useWfuncs = 1;
    private uint dwVersion;

    static this()
    {	// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/getversion.asp
	dwVersion = GetVersion();

	// Win 95, 98, ME do not implement the W functions
	useWfuncs = (dwVersion < 0x80000000);
    }
}
else version (Unix)
{
    private import std.c.unix;
}
else
{
    static assert(0);
}


auto class MmFile
{
    enum Mode
    {	Read,		// read existing file
	ReadWriteNew,	// delete existing file, write new file
	ReadWrite,	// read/write existing file, create if not existing
	ReadCopyOnWrite, // read/write existing file, copy on write
    }

    /* Open for reading
     */
    this(char[] filename)
    {
	this(filename, Mode.Read, 0, null);
    }

    /* Open
     */
    this(char[] filename, Mode mode, size_t size, void* address)
    {
	this.filename = filename;

	version (Win32)
	{
	    void* p;
	    uint dwDesiredAccess2;
	    uint dwShareMode;
	    uint dwCreationDisposition;
	    uint dwDesiredAccess;
	    uint flProtect;

	    if (dwVersion & 0x80000000 && (dwVersion & 0xFF) == 3)
	    {
		throw new FileException(filename,
		    "Win32s does not implement mm files");
	    }

	    switch (mode)
	    {
		case Mode.Read:
		    dwDesiredAccess2 = GENERIC_READ;
		    dwShareMode = FILE_SHARE_READ;
		    dwCreationDisposition = OPEN_EXISTING;
		    flProtect = PAGE_READONLY;
		    dwDesiredAccess = FILE_MAP_READ;
		    break;

		case Mode.ReadWriteNew:
		    assert(size != 0);
		    dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
		    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
		    dwCreationDisposition = CREATE_ALWAYS;
		    flProtect = PAGE_READWRITE;
		    dwDesiredAccess = FILE_MAP_WRITE;
		    break;

		case Mode.ReadWrite:
		    dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
		    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
		    dwCreationDisposition = OPEN_ALWAYS;
		    flProtect = PAGE_READWRITE;
		    dwDesiredAccess = FILE_MAP_WRITE;
		    break;

		case Mode.ReadCopyOnWrite:
		    if (dwVersion & 0x80000000)
		    {
			throw new FileException(filename,
			    "Win9x does not implement copy on write");
		    }
		    dwDesiredAccess2 = GENERIC_READ | GENERIC_WRITE;
		    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
		    dwCreationDisposition = OPEN_EXISTING;
		    flProtect = PAGE_WRITECOPY;
		    dwDesiredAccess = FILE_MAP_COPY;
		    break;
	    }

	    if (useWfuncs)
	    {
		wchar* namez = std.utf.toUTF16z(filename);
		hFile = CreateFileW(namez,
			dwDesiredAccess2,
			dwShareMode,
			null,
			dwCreationDisposition,
			FILE_ATTRIBUTE_NORMAL,
			cast(HANDLE)null);
	    }
	    else
	    {
		char* namez = std.file.toMBSz(filename);
		hFile = CreateFileA(namez,
			dwDesiredAccess2,
			dwShareMode,
			null,
			dwCreationDisposition,
			FILE_ATTRIBUTE_NORMAL,
			cast(HANDLE)null);
	    }
	    if (hFile == INVALID_HANDLE_VALUE)
		goto err1;

	    hFileMap = CreateFileMappingA(hFile, null, flProtect, 0, size, null);
	    if (hFileMap == null)               // mapping failed
		goto err1;

	    p = MapViewOfFileEx(hFileMap, dwDesiredAccess, 0, 0, size, address);
	    if (p == null)                      // mapping view failed
	    {
		goto err1;
	    }
	    if (size == 0)
		size = GetFileSize(hFile, null);

	    debug (MMFILE) printf("MmFile.this(): p = %p, size = %d\n", p, size);
	    data = p[0 .. size];
	    return;

	  err1:
	    if (hFileMap != null)
		CloseHandle(hFileMap);
	    hFileMap = null;

	    if (hFile != INVALID_HANDLE_VALUE)
		CloseHandle(hFile);
	    hFile = INVALID_HANDLE_VALUE;

	    errNo();
	}
	else version (Unix)
	{
	    char* namez = toStringz(filename);
	    void* p;
	    int fd;
	    int prot;
	    int flags;
	    int oflag;
	    int fmode;

	    switch (mode)
	    {
		case Mode.Read:
		    flags = MAP_SHARED;
		    prot = PROT_READ;
		    oflag = O_RDONLY;
		    fmode = 0;
		    break;

		case Mode.ReadWriteNew:
		    assert(size != 0);
		    flags = MAP_SHARED;
		    prot = PROT_READ | PROT_WRITE;
		    oflag = O_CREAT | O_RDWR | O_TRUNC;
		    fmode = 0660;
		    break;

		case Mode.ReadWrite:
		    flags = MAP_SHARED;
		    prot = PROT_READ | PROT_WRITE;
		    oflag = O_CREAT | O_RDWR;
		    fmode = 0660;
		    break;

		case Mode.ReadCopyOnWrite:
		    flags = MAP_PRIVATE;
		    prot = PROT_READ | PROT_WRITE;
		    oflag = O_RDWR;
		    fmode = 0;
		    break;
	    }

	    if (filename.length)
	    {
		struct_stat statbuf;

		fd = std.c.unix.open(namez, oflag, fmode);
		if (fd == -1)
		{
		    printf("\topen error, errno = %d\n",getErrno());
		    errNo();
		}

		if (std.c.unix.fstat(fd, &statbuf))
		{
		    //printf("\tfstat error, errno = %d\n",getErrno());
		    std.c.unix.close(fd);
		    errNo();
		}

		if (prot & PROT_WRITE && size > statbuf.st_size)
		{
		    // Need to make the file size bytes big
		    std.c.unix.lseek(fd, size - 1, SEEK_SET);
		    char c = 0;
		    std.c.unix.write(fd, &c, 1);
		}
		else if (prot & PROT_READ && size == 0)
		    size = statbuf.st_size;
	    }
	    else
	    {
		fd = -1;
		flags |= MAP_ANONYMOUS;
	    }

	    p = mmap(address, size, prot, flags, fd, 0);
	    //printf(" p = %x, size = %d\n", p, size);

	    /* Memory mapping stays active even if we close the handle.
	     * Closing it now avoids worrys about closing it during error
	     * recovery.
	     */
	    if (fd != -1 && std.c.unix.close(fd) == -1)
		errNo();

	    if (p == MAP_FAILED)		// in sys/mman.h
		errNo();
	    data = p[0 .. size];
	}
	else
	{
	    static assert(0);
	}
    }

    ~this()
    {
	debug (MMFILE) printf("MmFile.~this()\n");
	version (Win32)
	{
	    /* Note that under Windows 95, UnmapViewOfFile() seems to return
	     * random values, not TRUE or FALSE.
	     */
	    if (data && UnmapViewOfFile(data) == FALSE &&
		(dwVersion & 0x80000000) == 0)
		errNo();
	    data = null;

	    if (hFileMap != null && CloseHandle(hFileMap) != TRUE)
		errNo();
	    hFileMap = null;

	    if (hFile != INVALID_HANDLE_VALUE && CloseHandle(hFile) != TRUE)
		errNo();
	    hFile = INVALID_HANDLE_VALUE;
	}
	else version (Unix)
	{
	    int i;

	    i = munmap(cast(void*)data, data.length);
	    if (i != 0)
		errNo();
	}
	else
	{
	    static assert(0);
	}
	data = null;
    }

    /* Flush any pending output.
     */
    void flush()
    {
	debug (MMFILE) printf("MmFile.flush()\n");
	version (Win32)
	{
	    FlushViewOfFile(data, data.length);
	}
	else version (Unix)
	{
	    int i;

	    i = msync(cast(void*)data, data.length, MS_SYNC);	// sys/mman.h
	    if (i != 0)
		errNo();
	}
	else
	{
	    static assert(0);
	}
    }

    size_t length()
    {
	debug (MMFILE) printf("MmFile.length()\n");
	return data.length;
    }

    void[] opSlice()
    {
	debug (MMFILE) printf("MmFile.opSlice()\n");
	return data;
    }

    void[] opSlice(size_t i1, size_t i2)
    {
	debug (MMFILE) printf("MmFile.opSlice(%d, %d)\n", i1, i2);
	return data[i1 .. i2];
    }

    ubyte opIndex(size_t i)
    {
	debug (MMFILE) printf("MmFile.opIndex(%d)\n", i);
	return (cast(ubyte[])data)[i];
    }

    ubyte opIndex(size_t i, ubyte value)
    {
	debug (MMFILE) printf("MmFile.opIndex(%d, %d)\n", i, value);
	return (cast(ubyte[])data)[i] = value;
    }


  private:
    char[] filename;
    void[] data;

    version (Win32)
    {
	HANDLE hFile = INVALID_HANDLE_VALUE;
	HANDLE hFileMap = null;
    }
    else version (Unix)
    {
    }
    else
    {
	static assert(0);
    }

    // Report error, where errno gives the error number
    void errNo()
    {
	version (Win32)
	{
	    throw new FileException(filename, GetLastError());
	}
	else version (Unix)
	{
	    throw new FileException(filename, getErrno());
	}
	else
	{
	    static assert(0);
	}
    }
}

/*
	version (Win32)
	{
	}
	else version (linux)
	{
	}
	else
	{
	    static assert(0);
	}
*/

