// input_reader.cpp
#include <android/log.h>
#include <fcntl.h>
#include <jni.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/select.h>

// Helper macros for bit manipulation
#define NBITS(x) ((((x)-1)/NLONG)+1)
#define NLONG (sizeof(long) * 8)
#define NLONGS(x) (((x) + NLONG - 1) / NLONG)

// Custom log macro
#define LOG_TAG "RockChipInterface"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// Custom input event structure to match the 24-byte frame
typedef struct {
    // First 16 bytes appear to be timestamp data
    uint8_t timestamp[16];

    // Updated to match the actual event structure
    uint16_t type;    // Observed as 0x0001
    uint16_t code;    // Observed as 0x0040 (CW), 0x0041 (CCW), 0x003f (button)
    int32_t value;    // 0 or 1 for events
} custom_input_event;

// Function declarations
int findInputDevice();
void* inputMonitorThread(void* arg);
JNIEnv* getJNIEnv();
void notifyInputEvent(int eventType, int eventCode, int eventValue);

// Event type constants
#define EVENT_TYPE_ROTATION 1
#define EVENT_TYPE_BUTTON 2

// Global variables
int inputFd = -1;
bool isRunning = false;
pthread_t inputThread;
int rotaryPosition = 0;
pthread_mutex_t positionMutex = PTHREAD_MUTEX_INITIALIZER;

// Global reference to JNI environment and Java object for callbacks
static JavaVM* g_JavaVM = nullptr;
static jobject g_InterfaceObj = nullptr;
static jmethodID g_NotifyEventMethod = nullptr;

// Helper function to get JNI environment
JNIEnv* getJNIEnv() {
    JNIEnv* env = nullptr;
    if (g_JavaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        int status = g_JavaVM->AttachCurrentThread(&env, nullptr);
        if (status < 0 || !env) {
            LOGE("Failed to attach thread to JavaVM");
            return nullptr;
        }
    }
    return env;
}

// Call Java method to notify of input events
void notifyInputEvent(int eventType, int eventCode, int eventValue) {
    JNIEnv* env = getJNIEnv();
    if (!env || !g_InterfaceObj || !g_NotifyEventMethod) {
        return;
    }

    env->CallVoidMethod(g_InterfaceObj, g_NotifyEventMethod, eventType, eventCode, eventValue);
}

// Function to find the correct input device
int findInputDevice() {
    char devicePath[64];
    char deviceName[256];
    
    // Try several event devices
    for (int i = 0; i < 32; i++) {
        snprintf(devicePath, sizeof(devicePath), "/dev/input/event%d", i);
        int fd = open(devicePath, O_RDONLY);

        if (fd >= 0) {
            // Get device name
            if (ioctl(fd, EVIOCGNAME(sizeof(deviceName)), deviceName) >= 0) {
                LOGD("Checking device %s: %s", devicePath, deviceName);
                
                // Check if this device supports the key events we need
                unsigned long keyBits[NLONGS(KEY_MAX)] = {0};
                if (ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), keyBits) >= 0) {
                    // Check for our specific key codes (0x003F=63, 0x0040=64, 0x0041=65, 0x0044=68)
                    bool hasRotationKeys = (keyBits[63/NLONG] & (1UL << (63%NLONG))) &&
                                          (keyBits[64/NLONG] & (1UL << (64%NLONG))) &&
                                          (keyBits[65/NLONG] & (1UL << (65%NLONG)));
                    
                    bool hasButtonKey = (keyBits[63/NLONG] & (1UL << (63%NLONG))) ||
                                       (keyBits[68/NLONG] & (1UL << (68%NLONG)));
                    
                    if (hasRotationKeys || hasButtonKey) {
                        LOGD("Found matching device: %s (%s)", devicePath, deviceName);
                        return fd;
                    }
                }
                
                // Alternative: Check for specific device name patterns
                if (strstr(deviceName, "rotary") != NULL ||
                    strstr(deviceName, "potentiometer") != NULL ||
                    strstr(deviceName, "encoder") != NULL ||
                    strstr(deviceName, "gpio") != NULL ||
                    strstr(deviceName, "rockchip") != NULL) {
                    LOGD("Found device by name pattern: %s (%s)", devicePath, deviceName);
                    return fd;
                }
            }
            
            // Test by reading a small event to see if it matches our format
            fd_set readfds;
            struct timeval timeout;
            FD_ZERO(&readfds);
            FD_SET(fd, &readfds);
            timeout.tv_sec = 0;
            timeout.tv_usec = 100000; // 100ms timeout
            
            if (select(fd + 1, &readfds, NULL, NULL, &timeout) > 0) {
                custom_input_event testEvent;
                ssize_t bytesRead = read(fd, &testEvent, sizeof(testEvent));
                
                if (bytesRead == sizeof(testEvent) && testEvent.type == 0x0001) {
                    // Check if the event code matches our expected codes
                    if (testEvent.code == 0x0040 || testEvent.code == 0x0041 || 
                        testEvent.code == 0x003F || testEvent.code == 0x0044) {
                        LOGD("Found device by test event: %s", devicePath);
                        // Reset file position
                        lseek(fd, 0, SEEK_SET);
                        return fd;
                    }
                }
                // Reset file position if test failed
                lseek(fd, 0, SEEK_SET);
            }
            
            close(fd);
        }
    }

    LOGE("Failed to find input device with rotation/button capabilities");
    return -1;
}

// Input monitoring thread
void* inputMonitorThread(void* arg) {
    custom_input_event event;

    LOGD("Input monitor thread started");

    while (isRunning) {
        // Read exactly 24 bytes at a time (as seen in your dumps)
        ssize_t bytesRead = read(inputFd, &event, sizeof(event));

        if (bytesRead == sizeof(event)) {
            LOGD("Raw event: type=0x%04x, code=0x%04x, value=%d",
                 (unsigned int)event.type, (unsigned int)event.code, event.value);

            if (event.type == 0x0001) {
                // Handle all input events with their codes
                if (event.code == 0x0040 || event.code == 0x0041) {
                    // This is a rotation event
                    if (event.value == 1) {  // Only handle "press" events, not "release"
                        pthread_mutex_lock(&positionMutex);
                        if (event.code == 0x0040) {
                            // Clockwise
                            rotaryPosition++;
                        } else {
                            // Counter-clockwise
                            rotaryPosition--;
                        }
                        int currentPos = rotaryPosition;
                        pthread_mutex_unlock(&positionMutex);

                        // Notify with rotation event type, code as ID, and position as value
                        notifyInputEvent(EVENT_TYPE_ROTATION, event.code, currentPos);
                    }
                } else if (event.code == 0x003F || event.code == 0x0044) {
                    // This is a button event
                    // Notify with button event type, code as ID, and press/release as value
                    notifyInputEvent(EVENT_TYPE_BUTTON, event.code, event.value);
                }
            }
        } else if (bytesRead < 0) {
            if (errno != EAGAIN) {
                LOGE("Error reading from input device: %s", strerror(errno));
            }
            usleep(100000);  // 100ms
        } else if (bytesRead == 0) {
            LOGE("End of file reached - device disconnected?");
            break;
        }

        usleep(5000);  // 5ms
    }

    LOGD("Input monitor thread exiting");
    return NULL;
}

// JNI Functions for interacting with Java code

extern "C" JNIEXPORT jboolean JNICALL
Java_io_kiotplugins_rockchipinterface_RockChipInterface_initializeNative(JNIEnv* env, jobject thiz) {
    // Store JavaVM and create global reference to the Java object
    env->GetJavaVM(&g_JavaVM);
    g_InterfaceObj = env->NewGlobalRef(thiz);

    // Get method ID for callback method
    jclass clazz = env->GetObjectClass(thiz);
    g_NotifyEventMethod = env->GetMethodID(clazz, "notifyInputEvent", "(III)V");

    // Check that we found the method
    if (!g_NotifyEventMethod) {
        LOGE("Failed to find callback method");
        env->DeleteGlobalRef(g_InterfaceObj);
        g_InterfaceObj = nullptr;
        return JNI_FALSE;
    }

    // Close any previously opened file
    if (inputFd >= 0) {
        close(inputFd);
    }

    // Find the input device
    inputFd = findInputDevice();
    if (inputFd < 0) {
        LOGE("Failed to open input device");
        env->DeleteGlobalRef(g_InterfaceObj);
        g_InterfaceObj = nullptr;
        return JNI_FALSE;
    }

    // Start the input monitoring thread
    isRunning = true;
    if (pthread_create(&inputThread, NULL, inputMonitorThread, NULL) != 0) {
        LOGE("Failed to create input monitor thread");
        close(inputFd);
        inputFd = -1;
        isRunning = false;
        env->DeleteGlobalRef(g_InterfaceObj);
        g_InterfaceObj = nullptr;
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

extern "C" JNIEXPORT void JNICALL
Java_io_kiotplugins_rockchipinterface_RockChipInterface_shutdownNative(JNIEnv* env, jobject thiz) {
    // Stop the thread and close the device
    if (isRunning) {
        isRunning = false;
        pthread_join(inputThread, NULL);
    }

    if (inputFd >= 0) {
        close(inputFd);
        inputFd = -1;
    }

    // Clean up global references
    if (g_InterfaceObj) {
        env->DeleteGlobalRef(g_InterfaceObj);
        g_InterfaceObj = nullptr;
    }
}

extern "C" JNIEXPORT jint JNICALL
Java_io_kiotplugins_rockchipinterface_RockChipInterface_getRotaryPositionNative(JNIEnv* env, jobject thiz) {
    pthread_mutex_lock(&positionMutex);
    int position = rotaryPosition;
    pthread_mutex_unlock(&positionMutex);
    return position;
}

extern "C" JNIEXPORT void JNICALL
Java_io_kiotplugins_rockchipinterface_RockChipInterface_setRotaryPositionNative(JNIEnv* env, jobject thiz, jint position) {
    pthread_mutex_lock(&positionMutex);
    rotaryPosition = position;
    pthread_mutex_unlock(&positionMutex);
}