//
// Created by pengx on 2024/11/7.
//

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <jni.h>

#include "android/log.h"
#include "serial_port.h"

static const char *jni_tag = "JNI_SerialPort";

static speed_t get_baud_rate(jint j_baud_rate) {
    switch (j_baud_rate) {
        case 0:
            return B0;
        case 50:
            return B50;
        case 75:
            return B75;
        case 110:
            return B110;
        case 134:
            return B134;
        case 150:
            return B150;
        case 200:
            return B200;
        case 300:
            return B300;
        case 600:
            return B600;
        case 1200:
            return B1200;
        case 1800:
            return B1800;
        case 2400:
            return B2400;
        case 4800:
            return B4800;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
        case 115200:
            return B115200;
        case 230400:
            return B230400;
        case 460800:
            return B460800;
        case 500000:
            return B500000;
        case 576000:
            return B576000;
        case 921600:
            return B921600;
        case 1000000:
            return B1000000;
        case 1152000:
            return B1152000;
        case 1500000:
            return B1500000;
        case 2000000:
            return B2000000;
        case 2500000:
            return B2500000;
        case 3000000:
            return B3000000;
        case 3500000:
            return B3500000;
        case 4000000:
            return B4000000;
        default:
            return -1;
    }
}

static jobject j_fd_object = nullptr;

extern "C"
JNIEXPORT jobject JNICALL
Java_com_casic_common_detector_gd_uart_SerialPort_open(JNIEnv *env, jobject thiz, jstring path,
                                                       jint baud_rate, jint flags) {
    int fd;
    speed_t speed;

    /* Check arguments */
    {
        speed = get_baud_rate(baud_rate);
        if (speed == -1) {
            __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "invalid baud_rate");
            return nullptr;
        }
    }

    /* Opening device */
    {
        jboolean is_copy;
        const char *path_utf = env->GetStringUTFChars(path, &is_copy);
        __android_log_print(
                ANDROID_LOG_DEBUG, jni_tag,
                "opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags
        );
        fd = open(path_utf, O_RDWR | flags);
        __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "open() fd = %d", fd);
        env->ReleaseStringUTFChars(path, path_utf);
        if (fd == -1) {
            __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "open port failed");
            return nullptr;
        }
    }

    /* Configure device */
    {
        struct termios cfg{};
        __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "configuring serial port");
        if (tcgetattr(fd, &cfg)) {
            __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "tcgetattr() failed");
            close(fd);
            return nullptr;
        }

        cfmakeraw(&cfg);
        cfsetispeed(&cfg, speed);
        cfsetospeed(&cfg, speed);

        if (tcsetattr(fd, TCSANOW, &cfg)) {
            __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "tcsetattr() failed");
            close(fd);
            return nullptr;
        }
    }

    /* Create a corresponding file descriptor */
    {
        jclass j_fd_class = env->FindClass("java/io/FileDescriptor");
        jmethodID j_fd_methodID = env->GetMethodID(j_fd_class, "<init>", "()V");

        char descriptor_buffer[16] = {0};
        strcat(descriptor_buffer, "descriptor");
        jfieldID j_desc_fieldID = env->GetFieldID(j_fd_class, descriptor_buffer, "I");

        j_fd_object = env->NewObject(j_fd_class, j_fd_methodID);
        env->SetIntField(j_fd_object, j_desc_fieldID, (jint) fd);
    }

    return j_fd_object;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_casic_common_detector_gd_uart_SerialPort_close(JNIEnv *env, jobject thiz) {
    jclass serial_port_class = env->GetObjectClass(thiz);
    jclass file_descriptor_class = env->FindClass("java/io/FileDescriptor");

    // fd 对应SerialPort里面的 fd
    char fd_buffer[4] = {0};
    strcat(fd_buffer, "fd");
    jfieldID fd_field_id = env->GetFieldID(serial_port_class, fd_buffer,
                                           "Ljava/io/FileDescriptor;");

    char descriptor_buffer[16] = {0};
    strcat(descriptor_buffer, "descriptor");
    jfieldID descriptor_field_id = env->GetFieldID(file_descriptor_class, descriptor_buffer, "I");

    jobject fd_obj = env->GetObjectField(thiz, fd_field_id);
    jint descriptor = env->GetIntField(fd_obj, descriptor_field_id);

    __android_log_print(ANDROID_LOG_DEBUG, jni_tag, "close()");
    close(descriptor);
}