#ifdef WIN32
#include <pdh.h>
#include <pdhmsg.h>

#include "win32bindings.h"
#include "javasigar.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifndef PDH_ACCESS_DENIED
#define PDH_ACCESS_DENIED ((DWORD)0xC0000BDBL)
#endif

/**
 * Hack around not being able to format error codes using
 * FORMAT_MESSAGE_FROM_HMODULE.  We only define the error
 * codes that could be possibly returned.
 */
static char *get_error_message(PDH_STATUS status) {
    switch (status) {
    case PDH_CSTATUS_NO_MACHINE:
        return "The computer is unavailable";
    case PDH_CSTATUS_NO_OBJECT:
        return "The specified object could not be found on the computer";
    case PDH_INVALID_ARGUMENT:
        return "A required argument is invalid";
    case PDH_MEMORY_ALLOCATION_FAILURE:
        return "A required temporary buffer could not be allocated";
    case PDH_INVALID_HANDLE:
        return "The query handle is not valid";
    case PDH_NO_DATA:
        return "The query does not currently have any counters";
    case PDH_CSTATUS_BAD_COUNTERNAME:
        return "The counter name path string could not be parsed or "
            "interpreted";
    case PDH_CSTATUS_NO_COUNTER:
        return "The specified counter was not found";
    case PDH_CSTATUS_NO_COUNTERNAME:
        return "An empty counter name path string was passed in";
    case PDH_FUNCTION_NOT_FOUND:
        return "The calculation function for this counter could not "
            "be determined";
    case PDH_ACCESS_DENIED:
        return "Access denied";
    default:
        return "Unknown error";
    }
}

JNIEXPORT void SIGAR_JNI(win32_Pdh_pdhConnectMachine)
(JNIEnv *env, jobject cur, jstring jhost)
{
    PDH_STATUS status;
    LPCTSTR host = JENV->GetStringUTFChars(env, jhost, NULL);

    status = PdhConnectMachine(host);
    JENV->ReleaseStringUTFChars(env, jhost, host);

    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
    }
}

JNIEXPORT jlong JNICALL SIGAR_JNI(win32_Pdh_pdhOpenQuery)
(JNIEnv *env, jobject cur)
{
    HQUERY     h_query;
    PDH_STATUS status;

    status = PdhOpenQuery(NULL, 0, &h_query);
    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return 0;
    }
    return (jlong)h_query;
}

JNIEXPORT void SIGAR_JNI(win32_Pdh_pdhCloseQuery)
(JNIEnv *env, jclass cur, jlong query)
{
    HQUERY     h_query    = (HQUERY)query;
    PDH_STATUS status;

    // Close the query and the log file.
    status = PdhCloseQuery(h_query);

    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return;
    }
}

JNIEXPORT jlong SIGAR_JNI(win32_Pdh_pdhAddCounter)
(JNIEnv *env, jclass cur, jlong query, jstring cp)
{
    HCOUNTER   h_counter;
    HQUERY     h_query = (HQUERY)query;
    PDH_STATUS status;
    LPCTSTR    counter_path = JENV->GetStringUTFChars(env, cp, NULL);

    /* Add the counter that created the data in the log file. */
    status = PdhAddCounter(h_query, counter_path, 0, &h_counter);
    JENV->ReleaseStringUTFChars(env, cp, counter_path);

    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return 0;
    }

    return (jlong)h_counter;
}

JNIEXPORT void SIGAR_JNI(win32_Pdh_pdhRemoveCounter)
(JNIEnv *env, jclass cur, jlong counter)
{
    HCOUNTER   h_counter = (HCOUNTER)counter;
    PDH_STATUS status;
    
    status = PdhRemoveCounter(h_counter);

    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return;
    }
}

JNIEXPORT jdouble SIGAR_JNI(win32_Pdh_pdhGetValue)
(JNIEnv *env, jclass cur, jlong query, jlong counter, jboolean fmt)
{
    HCOUNTER              h_counter      = (HCOUNTER)counter;
    HQUERY                h_query        = (HQUERY)query;
    PDH_STATUS            status;
    PDH_RAW_COUNTER raw_value;
    PDH_FMT_COUNTERVALUE fmt_value;
    DWORD type;

    status = PdhCollectQueryData(h_query);
   
    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return 0;
    }

    if (fmt) {
        status = PdhGetFormattedCounterValue(h_counter,
                                             PDH_FMT_DOUBLE,
                                             (LPDWORD)NULL,
                                             &fmt_value);
    }
    else {
        status = PdhGetRawCounterValue(h_counter, &type, &raw_value);
    }

    if (status != ERROR_SUCCESS) {
        win32_throw_exception(env, get_error_message(status));
        return 0;
    }

    if (fmt) {
        return fmt_value.doubleValue;
    }
    else {
        return (jdouble)raw_value.FirstValue;
    }
}

JNIEXPORT jobjectArray SIGAR_JNI(win32_Pdh_pdhGetInstances)
(JNIEnv *env, jclass cur, jstring cp)
{
    PDH_STATUS   status              = ERROR_SUCCESS;
    DWORD        counter_list_size   = 0;
    DWORD        instance_list_size  = 8096;
    LPTSTR       instance_list_buf   = 
        (LPTSTR)malloc ((instance_list_size * sizeof (TCHAR)));
    LPTSTR       cur_object          = NULL;
    LPCTSTR      counter_path        = 
        (LPCTSTR)JENV->GetStringUTFChars(env, cp, 0);
    jobjectArray array = NULL;

    status = PdhEnumObjectItems(NULL, NULL, counter_path, NULL,
                                &counter_list_size, instance_list_buf,
                                &instance_list_size, PERF_DETAIL_WIZARD,
                                FALSE);
    
    if (status == PDH_MORE_DATA && instance_list_size > 0) {
        // Allocate the buffers and try the call again.
        if (instance_list_buf != NULL) 
            free(instance_list_buf);
        
        instance_list_buf = (LPTSTR)malloc((instance_list_size * 
                                            sizeof (TCHAR)));
        counter_list_size = 0;
        status  = PdhEnumObjectItems (NULL, NULL, counter_path,
                                      NULL, &counter_list_size,
                                      instance_list_buf,
                                      &instance_list_size,
                                      PERF_DETAIL_WIZARD, FALSE);
    }

    JENV->ReleaseStringUTFChars(env, cp, counter_path);

    // Still may get PDH_ERROR_MORE data after the first reallocation,
    // but that is OK for just browsing the instances
    if (status == ERROR_SUCCESS || status == PDH_MORE_DATA) {
        int i, count;
        
        for (cur_object = instance_list_buf, count = 0;
             *cur_object != 0;
             cur_object += lstrlen(cur_object) + 1, count++);
            
        array = JENV->NewObjectArray(env, count,
                                     JENV->FindClass(env, 
                                                     "java/lang/String"),
                                     JENV->NewStringUTF(env, ""));

        /* Walk the return instance list, creating an array */
        for (cur_object = instance_list_buf, i = 0;
             *cur_object != 0;
             cur_object += lstrlen(cur_object) + 1, i++) 
        {
            jstring s = JENV->NewStringUTF(env, cur_object);
            JENV->SetObjectArrayElement(env, array, i, s);
        }
    } else {
        if (instance_list_buf != NULL) 
            free(instance_list_buf);
        
        // An error occured
        win32_throw_exception(env, get_error_message(status));
        return NULL;
    }

    if (instance_list_buf != NULL) 
        free(instance_list_buf);

    return array;
}

JNIEXPORT jobjectArray SIGAR_JNI(win32_Pdh_pdhGetKeys)
(JNIEnv *env, jclass cur, jstring cp)
{
    PDH_STATUS  status              = ERROR_SUCCESS;
    DWORD       counter_list_size   = 8096;
    DWORD       instance_list_size  = 0;
    LPTSTR      instance_list_buf   = 
        (LPTSTR)malloc (counter_list_size * sizeof(TCHAR));
    LPTSTR      cur_object          = NULL;
    LPCTSTR     counter_path        = JENV->GetStringUTFChars(env, cp, 0);
    jobjectArray array              = NULL;

    status = PdhEnumObjectItems(NULL, NULL, counter_path,
                                instance_list_buf, &counter_list_size,
                                NULL, &instance_list_size,
                                PERF_DETAIL_WIZARD, FALSE); 
        
    if (status == PDH_MORE_DATA) {
        /* Allocate the buffers and try the call again. */
        if (instance_list_buf != NULL)
            free(instance_list_buf);

        instance_list_buf = (LPTSTR)malloc(counter_list_size *
                                           sizeof(TCHAR));
        instance_list_size = 0;
        status = PdhEnumObjectItems (NULL, NULL, counter_path,
                                     instance_list_buf,
                                     &counter_list_size, NULL,
                                     &instance_list_size,
                                     PERF_DETAIL_WIZARD, 0);
    }

    JENV->ReleaseStringUTFChars(env, cp, counter_path);

    if (status == ERROR_SUCCESS || status == PDH_MORE_DATA) {
        int i, count;

        for (cur_object = instance_list_buf, count = 0;
             *cur_object != 0;
             cur_object += lstrlen(cur_object) + 1, count++);

        array = JENV->NewObjectArray(env, count,
                                     JENV->FindClass(env, 
                                                     "java/lang/String"),
                                     JENV->NewStringUTF(env, ""));

        /* Walk the return instance list, creating an array */
        for (cur_object = instance_list_buf, i = 0;
             *cur_object != 0;
             cur_object += lstrlen(cur_object) + 1, i++) 
        {
            jstring s = JENV->NewStringUTF(env, cur_object);
            JENV->SetObjectArrayElement(env, array, i, s);
        }
    } else {
        // An error occured
        if (instance_list_buf != NULL) 
            free(instance_list_buf);
        
        // An error occured
        win32_throw_exception(env, get_error_message(status));
        return NULL;
    }
    
    if (instance_list_buf != NULL) 
            free(instance_list_buf);

    return array;
}

JNIEXPORT jobjectArray SIGAR_JNI(win32_Pdh_pdhGetObjects)
(JNIEnv *env, jclass cur)
{
    PDH_STATUS   status;
    DWORD        list_size       = 8096;
    LPTSTR       list_buf        = (LPTSTR)malloc(list_size * sizeof(TCHAR));
    LPTSTR       cur_object;
    DWORD        i, num_objects  = 0;
    jobjectArray array           = NULL;

    status = PdhEnumObjects(NULL, NULL, list_buf, &list_size,
                            PERF_DETAIL_WIZARD, FALSE);

    if (status == PDH_MORE_DATA) {
        // Re-try call with a larger buffer
        if (list_buf != NULL)
            free(list_buf);

        list_buf = (LPTSTR)malloc(list_size * sizeof(TCHAR));
        status = PdhEnumObjects(NULL, NULL, list_buf, &list_size,
                                PERF_DETAIL_WIZARD, FALSE);
    }

    if (status != ERROR_SUCCESS) {
        if (list_buf != NULL)
            free(list_buf);

        win32_throw_exception(env, get_error_message(status));
        return NULL;
    }

    // Walk the return buffer counting the number of objects
    for (cur_object = list_buf, num_objects = 0;
         *cur_object != 0;
         cur_object += lstrlen(cur_object) + 1, num_objects++);

    array = JENV->NewObjectArray(env, num_objects,
                                 JENV->FindClass(env, 
                                                 "java/lang/String"),
                                 JENV->NewStringUTF(env, ""));

    for (cur_object = list_buf, i = 0;
         *cur_object != 0;
         cur_object += lstrlen(cur_object) + 1, i++) 
    {
        jstring s = JENV->NewStringUTF(env, cur_object);
        JENV->SetObjectArrayElement(env, array, i, s);
    }

    if (list_buf != NULL)
        free(list_buf);

    return array;
}

#ifdef __cplusplus
}
#endif
#endif /* WIN32 */
