/*
 * AUTHOR
 *
 * Ash Berlin <ash@cpan.org>
 *
 * Based on code by
 * Rob Mueller <cpan@robm.fastmail.fm>
 *
 * COPYRIGHT AND LICENSE
 *
 * Copyright (C) 2007 by Ash Berlin
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the same terms as Perl itself. 
 * 
*/

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>


#include "mmap_cache.h"
#include "mmap_cache_internals.h"

#ifdef _MSC_VER
#if _MSC_VER <= 1310
#define vsnprintf _vsnprintf
#endif
#endif

char* _mmc_get_def_share_filename(mmap_cache * cache)
{
    int ret;
    static char buf[MAX_PATH];

    ret = GetTempPath(MAX_PATH, buf);
    if (ret > MAX_PATH)
    {
        _mmc_set_error(cache, GetLastError(), "Unable to get temp path");
        return NULL;
    }    
    return strcat(buf, "sharefile");    
}

int mmc_open_cache_file(mmap_cache* cache, int* do_init) {
  int i;
  void *tmp;
    HANDLE fh, fileMap, findHandle;
    WIN32_FIND_DATA statbuf;

    findHandle = FindFirstFile(cache->share_file, &statbuf);
        
    /* Create file if it doesn't exist */    
    if (findHandle == INVALID_HANDLE_VALUE) {
        fh = CreateFile(cache->share_file, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
                
        if (fh == INVALID_HANDLE_VALUE) {
            _mmc_set_error(cache, GetLastError(), "Create of share file %s failed", cache->share_file);
            return -1;
        }
        
        /* Fill file with 0's */
        tmp = calloc(1, cache->c_page_size);
        if (!tmp) {
            _mmc_set_error(cache, GetLastError(), "Calloc of tmp space failed");
            return -1;
        }
        
        for (i = 0; i < cache->c_num_pages; i++) {
            DWORD tmpOut;
            WriteFile(fh, tmp, cache->c_page_size, &tmpOut, NULL);
        }
        free(tmp);
        
        /* Later on initialise page structures */
        *do_init = 1;
        
        CloseHandle(fh);
        
    } else {
        FindClose(findHandle);
    
        if (cache->init_file || (statbuf.nFileSizeLow != cache->c_size)) {
            *do_init = 1;
    
            fh = CreateFile(cache->share_file, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
			    CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
                            
            if (fh == INVALID_HANDLE_VALUE) {
                _mmc_set_error(cache, GetLastError(), "Truncate of existing share file %s failed", cache->share_file);
                return -1;
            }
            CloseHandle(fh);
        }
    }
    
    fh = CreateFile(cache->share_file,         // File Name 
             GENERIC_READ|GENERIC_WRITE,       // Desired Access
             FILE_SHARE_READ|FILE_SHARE_WRITE, // Share mode
             NULL,                             // Security Rights
             OPEN_EXISTING,                    // Creation Mode
             FILE_ATTRIBUTE_TEMPORARY,         // File Attribs
             NULL);                            // Template File    
    
    if (fh == INVALID_HANDLE_VALUE) {
        _mmc_set_error(cache, GetLastError(), "Open of share file \"%s\" failed", cache->share_file);
        return -1;  
    }

    cache->fh = fh;
    return 0;
}

int mmc_map_memory(mmap_cache * cache) {
    HANDLE fileMap = CreateFileMapping(cache->fh, NULL, PAGE_READWRITE, 0, cache->c_size, NULL);
    if (fileMap == NULL) {
        _mmc_set_error(cache, GetLastError(), "CreateFileMapping of %s failed", cache->share_file);
        CloseHandle(cache->fh);
        return -1;
    }
    
    cache->mm_var = MapViewOfFile(fileMap, FILE_MAP_WRITE|FILE_MAP_READ, 0,0,0);
    if (cache->mm_var == NULL) {
        _mmc_set_error(cache, GetLastError(), "Mmap of shared file %s failed", cache->share_file);
        CloseHandle(fileMap);
        CloseHandle(cache->fh);
        return -1;
        
    }
    /* If I read the docs right, this will do nothing untill the mm_var is unmapped */
    if (CloseHandle(fileMap) == FALSE) {
        _mmc_set_error(cache, GetLastError(), "CloseHandle(fileMap) on shared file %s failed", cache->share_file);
        UnmapViewOfFile(cache->mm_var);
        CloseHandle(fileMap);
        CloseHandle(cache->fh);
        return -1;
    }
  return 0;
}

int mmc_check_fh(mmap_cache* cache) {
  return 1;
}

int mmc_close_fh(mmap_cache* cache) {
  int ret = CloseHandle(cache->fh);
  cache->fh = NULL;
  return ret;
}

int mmc_unmap_memory(mmap_cache* cache) {
  int res = UnmapViewOfFile(cache->mm_var);
  if (res == -1) {
    _mmc_set_error(cache, GetLastError(), "Unmmap of shared file %s failed", cache->share_file);
  }
  return res;
}

int mmc_lock_page(mmap_cache* cache, MU64 p_offset) {
    OVERLAPPED lock;
    DWORD lock_res, bytesTransfered;
    memset(&lock, 0, sizeof(lock));
    lock.Offset = (DWORD)(p_offset & 0xffffffff);
    lock.OffsetHigh = (DWORD)((p_offset >> 32) & 0xffffffff);
    lock.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (LockFileEx(cache->fh, 0, 0, cache->c_page_size, 0, &lock) == 0) {
        DWORD err = GetLastError();
        CloseHandle(lock.hEvent);
        _mmc_set_error(cache, err, "LockFileEx failed");
        return -1;
    }

    lock_res = WaitForSingleObjectEx(lock.hEvent, 10000, FALSE);

    if (lock_res != WAIT_OBJECT_0 || GetOverlappedResult(cache->fh, &lock, &bytesTransfered, FALSE) == FALSE) {
        DWORD err = GetLastError();
        CloseHandle(lock.hEvent);
        _mmc_set_error(cache, err, "Overlapped Lock failed");
        return -1;
    }
    /* Always close the event handle once the lock has been acquired,
     * otherwise long-running processes leak one HANDLE per lock. */
    CloseHandle(lock.hEvent);
    return 0;
}

int mmc_unlock_page(mmap_cache* cache, MU64 p_offset) {
    OVERLAPPED lock;
    memset(&lock, 0, sizeof(lock));
    /* Offset is a DWORD so we must split p_offset across Offset/OffsetHigh,
     * otherwise we'd unlock at a different offset than we locked at for
     * any cache file larger than 4GB. */
    lock.Offset = (DWORD)(p_offset & 0xffffffff);
    lock.OffsetHigh = (DWORD)((p_offset >> 32) & 0xffffffff);
    lock.hEvent = 0;

    UnlockFileEx(cache->fh, 0, cache->c_page_size, 0, &lock);
    return 0;
}

/*
 * int _mmc_set_error(mmap_cache *cache, int err, char * error_string, ...)
 *
 * Set internal error string/state
 *
*/
int _mmc_set_error(mmap_cache *cache, int err, char * error_string, ...) {
  va_list ap;
  static char errbuf[1024];
  char *msgBuff;

  va_start(ap, error_string);

  /* Make sure it's terminated */
  errbuf[1023] = '\0';

  /* Start with error string passed */
  vsnprintf(errbuf, 1023, error_string, ap);

  /* Add system error code if passed. */
  if (err) {
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        err,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &msgBuff,
        0, NULL );
    if (msgBuff) {
      size_t used = strlen(errbuf);
      if (used < sizeof(errbuf) - 1) {
        snprintf(errbuf + used, sizeof(errbuf) - used, ": %s", msgBuff);
      }
      LocalFree(msgBuff);
    }
  }

  /* Save in cache object */
  cache->last_error = errbuf;

  va_end(ap);

  return -1;
}

