#ifdef WIN32
#include "javasigar.h"
#include "win32bindings.h"

#define MAX_INSERT_STRS  8
#define MAX_MSG_LENGTH   4096
#define MAX_ERROR_LENGTH 1024

#define REG_MSGFILE_ROOT "SYSTEM\\CurrentControlSet\\Services\\EventLog\\"

static void win32_set_pointer(JNIEnv *env, jobject obj, const void *ptr)
{
    jfieldID pointer_field;
    int pointer_int;
    jclass cls;

    cls = JENV->GetObjectClass(env, obj);
    
    pointer_field = JENV->GetFieldID(env, cls, "eventLogHandle", "I");
    pointer_int = (int)ptr;

    JENV->SetIntField(env, obj, pointer_field, pointer_int);
}

static HANDLE win32_get_pointer(JNIEnv *env, jobject obj)
{
    jfieldID pointer_field;
    HANDLE h;
    jclass cls;

    cls = JENV->GetObjectClass(env, obj);

    pointer_field = JENV->GetFieldID(env, cls, "eventLogHandle", "I");
    h = (HANDLE)JENV->GetIntField(env, obj, pointer_field);

    if (!h) {
        win32_throw_exception(env, "Event log not opened");
    }

    return h;
}

static int get_messagefile_dll(char *app, char *source, char *dllfile)
{
    HKEY hk;
    DWORD type, data;
    char buf[MAX_MSG_LENGTH];

    sprintf(buf, "%s%s\\%s", REG_MSGFILE_ROOT, app, source);
    
    if (RegOpenKey(HKEY_LOCAL_MACHINE, buf, &hk)) {
        return GetLastError();
    }

    if (RegQueryValueEx(hk, "EventMessageFile", NULL, &type,
                        (UCHAR *)buf, &data)) {
        RegCloseKey(hk);
        return GetLastError();
    }

    strncpy(dllfile, buf, sizeof(dllfile));

    RegCloseKey(hk);

    return 0;
}

static int get_formatted_message(EVENTLOGRECORD *pevlr, char *dllfile,
                                 char *msg)
{
    HINSTANCE hlib;
    LPTSTR msgbuf;
    char msgdll[MAX_MSG_LENGTH];
    char *insert_strs[MAX_INSERT_STRS], *ch;
    int i;

    if (!ExpandEnvironmentStrings(dllfile, msgdll, MAX_PATH))
        return GetLastError();

    if (!(hlib = LoadLibraryEx(msgdll, NULL,
                               LOAD_LIBRARY_AS_DATAFILE)))
        return GetLastError();

    ch = (char *)((LPBYTE)pevlr + pevlr->StringOffset);
    for (i = 0; i < pevlr->NumStrings && i < MAX_INSERT_STRS; i++) {
        insert_strs[i] = ch;
        ch += strlen(ch) + 1;
    }

    FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
                  FORMAT_MESSAGE_ALLOCATE_BUFFER |
                  FORMAT_MESSAGE_ARGUMENT_ARRAY,
                  hlib,
                  pevlr->EventID,
                  MAKELANGID(LANG_NEUTRAL, SUBLANG_ENGLISH_US),
                  (LPTSTR) &msgbuf,
                  MAX_MSG_LENGTH,
                  insert_strs);

    strncpy(msg, msgbuf, sizeof(msg));

    FreeLibrary(hlib);
    LocalFree((HLOCAL)msgbuf);

    return 0;
}

JNIEXPORT void SIGAR_JNI(win32_EventLog_open)
(JNIEnv *env, jobject obj, jstring lpSourceName)
{
    HANDLE h;
    const char *name;

    name = JENV->GetStringUTFChars(env, lpSourceName, 0);

    h = OpenEventLog(NULL, name);
    if (h == NULL) {
        char buf[MAX_ERROR_LENGTH];
        DWORD lastError = GetLastError();

        sprintf(buf, "Unable to open event log: %d", lastError);
        JENV->ReleaseStringUTFChars(env, lpSourceName, name);
        win32_throw_exception(env, buf);
        return;
    }

    JENV->ReleaseStringUTFChars(env, lpSourceName, name);

    /* Save the handle for later use */
    win32_set_pointer(env, obj, h);
}

JNIEXPORT void SIGAR_JNI(win32_EventLog_close)
(JNIEnv *env, jobject obj)
{
    HANDLE h = win32_get_pointer(env, obj);

    CloseEventLog(h);

    win32_set_pointer(env, obj, NULL);
}

JNIEXPORT jint SIGAR_JNI(win32_EventLog_getNumberOfRecords)
(JNIEnv *env, jobject obj)
{
    DWORD records;
    HANDLE h = win32_get_pointer(env, obj);

    GetNumberOfEventLogRecords(h, &records);

    return records;
}

JNIEXPORT jint SIGAR_JNI(win32_EventLog_getOldestRecord)
(JNIEnv *env, jobject obj)
{
    DWORD oldest;
    HANDLE h = win32_get_pointer(env, obj);

    GetOldestEventLogRecord(h, &oldest);

    return oldest;
}

JNIEXPORT jobject SIGAR_JNI(win32_EventLog_read)
(JNIEnv *env, jobject obj, jint recordOffset)
{
    EVENTLOGRECORD *pevlr;
    BYTE buffer[8192];
    char dllfile[1024];
    DWORD dwRead, dwNeeded;
    LPSTR source, machineName;
    HANDLE h;
    BOOL rv;
    jclass cls = WIN32_FIND_CLASS("EventLogRecord");
    jobject eventObj; /* Actual instance of the EventLogRecord */
    jfieldID id;

    h = win32_get_pointer(env, obj);

    pevlr = (EVENTLOGRECORD *)&buffer;
    rv = ReadEventLog(h,
                      EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ,
                      recordOffset,
                      pevlr,
                      sizeof(buffer),
                      &dwRead,
                      &dwNeeded);
    if (!rv) {
        char buf[MAX_ERROR_LENGTH];
        DWORD lastError = GetLastError();
        
        sprintf(buf, "Error reading from the event log: %d", lastError);
        win32_throw_exception(env, buf);
        return NULL;
    }

    eventObj = JENV->AllocObject(env, cls);

    id = JENV->GetFieldID(env, cls, "recordNumber", "J");
    JENV->SetLongField(env, eventObj, id, pevlr->RecordNumber);

    id = JENV->GetFieldID(env, cls, "timeGenerated", "J");
    JENV->SetLongField(env, eventObj, id, pevlr->TimeGenerated);

    id = JENV->GetFieldID(env, cls, "timeWritten", "J");
    JENV->SetLongField(env, eventObj, id, pevlr->TimeWritten);

    id = JENV->GetFieldID(env, cls, "eventId", "J");
    JENV->SetLongField(env, eventObj, id, pevlr->EventID);

    id = JENV->GetFieldID(env, cls, "eventType", "S");
    JENV->SetShortField(env, eventObj, id, pevlr->EventType);

    /* Extract string data from the end of the structure.  Lame. */

    id = JENV->GetFieldID(env, cls, "source", "Ljava/lang/String;");
    source = (LPSTR)((LPBYTE)pevlr + sizeof(EVENTLOGRECORD));
    SetStringField(env, eventObj, id, source);

    /* Get the formatted message */
    if (!get_messagefile_dll("Application", source, dllfile)) {
        char msg[MAX_MSG_LENGTH];
        if (!get_formatted_message(pevlr, dllfile, msg)) {
            
            id = JENV->GetFieldID(env, cls, "stringData", 
                                  "Ljava/lang/String;");
            SetStringField(env, eventObj, id, msg);
        }
    } else if (pevlr->StringOffset > 0) {
        /* Work around some applications not using a message file */
        char *tmp = (LPSTR)((LPBYTE)pevlr + pevlr->StringOffset);            
        id = JENV->GetFieldID(env, cls, "stringData", "Ljava/lang/String;");
        SetStringField(env, eventObj, id, tmp);
    }

    /* Increment up to the machine name. */
    id = JENV->GetFieldID(env, cls, "computerName", "Ljava/lang/String;");
    machineName = (LPSTR)((LPBYTE)pevlr + sizeof(EVENTLOGRECORD) +
                          strlen(source) + 1);
    SetStringField(env, eventObj, id, machineName);

    /* Get user id info */
    if (pevlr->UserSidLength > 0) {
        char name[256];
        char domain[256];
        DWORD namelen = sizeof(name);
        DWORD domainlen = sizeof(domain);
        DWORD len;
        SID_NAME_USE snu;
        PSID sid;
        
        sid = (PSID)((LPBYTE)pevlr + pevlr->UserSidOffset);
        if (LookupAccountSid(NULL, sid, name, &namelen, domain,
                             &domainlen, &snu)) {
            id = JENV->GetFieldID(env, cls, "user", "Ljava/lang/String;");
            SetStringField(env, eventObj, id, name);
        }
    }
    
    return eventObj;
}

JNIEXPORT void SIGAR_JNI(win32_EventLog_waitForChange)
(JNIEnv *env, jobject obj, jint timeout)
{
    HANDLE h, hEvent;
    DWORD millis;

    h = win32_get_pointer(env, obj);

    hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hEvent == NULL) {
        win32_throw_exception(env, "Unable to create event");
        return;
    }

    if (timeout == -1)
        millis = INFINITE;
    else
        millis = timeout;

    if(!(NotifyChangeEventLog(h, hEvent))) {
        char buf[MAX_ERROR_LENGTH];
        sprintf(buf, "Error registering for event log to change: %d",
                GetLastError());
        win32_throw_exception(env, buf);
        return;
    }

    if (WaitForSingleObject(hEvent, millis) == WAIT_FAILED)
    {
        char buf[MAX_ERROR_LENGTH];
        sprintf(buf, "Error waiting for event log change: %d",
                GetLastError());
        win32_throw_exception(env, buf);
    }

    return;
}
#endif /* WIN32 */
