import ctypes
import struct
import platform
import os
import sys
import time
from ctypes import *
from enum import Enum
from functools import wraps
from threading import Event
import time
import json


def pause():
    input("Press Enter to continue ...")


def get_lib_path():
    lib_base = "../../SDK"
    if platform.system() == 'Windows':
        if struct.calcsize("P") == 4:
            lib_name = "windows/WECG400x86.dll"
        else:
            lib_name = "windows/WECG400x64.dll"
    elif platform.system() == 'Linux':
        if platform.machine() == 'aarch64':
            lib_name = "linux/libwecgrpix64.so"
        elif platform.machine() == 'armv7l':
            lib_name = "linux/libwecgrpix86.so"
        elif platform.machine() == 'x86_64':
            lib_name = "linux/libwecgx64.so"
        else:
            lib_name = "linux/libwecgx86.so"
    elif platform.system() == 'Darwin':
        lib_name = "macos/libwecg.a"            
    return os.path.join(lib_base, lib_name)



sdk = cdll.LoadLibrary(get_lib_path())

ConnectedCallback = CFUNCTYPE(None, c_bool)
SignalOutputCallback = CFUNCTYPE(None, c_double, c_double)
FrequencyCallback = CFUNCTYPE(None, c_double)


class OutputFunction(Enum):
    Output_Off = 0
    Output_Sine = 1
    Output_Triangle = 2
    Output_Square = 3
    Output_RectanglePulse = 4
    Output_TrianglePulse = 5
    Output_Exponential = 6
    Output_ECG2_27 = 7
    Output_Custom_ECG = 8
    Output_RawData = 18


class OutputDut(Enum):
    DUT1 = 0
    DUT2 = 1
    DUT3 = 2
    DUT4 = 3


class OutputLead(Enum):
    Lead_RA = 0
    Lead_LA = 1


class PacingPulse(Enum):
    SinglePulse = 0
    DoublePulse_150ms = 1
    DoublePulse_250ms = 2


class HW_INFORMATION(Structure):
    _fields_ = [
        ('FWMajorVersion', c_int),
        ('FWMinorVersion', c_int),
        ('FWBuildVersion', c_int),
        ('HWMajorVersion', c_int),
        ('HWMinorVersion', c_int)
    ]


class MODEL_INFORMATION(Structure):
    _fields_ = [
        ('ProductName', c_char * 2),
        ('GenerationNumber', c_char),
        ('ModelNumber', c_char),
        ('SerialNumber', c_int),
        ('Year', c_int)
    ]

class PLAY_RAW_DATA(Structure):
    _fields_ = [
        ('SampleRate', c_double),
        ('Size', c_int),
        ('AC', c_void_p),
        ('DC', c_void_p)
    ]


def sdk_fn(cfn_name, args_type=None, return_type=None):
    def decorator_function(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            cfn = getattr(self.sdk, cfn_name)
            if args_type:
                cfn.argtypes = args_type
            if return_type:
                cfn.restype = return_type
            return cfn(*args, **kwargs)

        return wrapper

    return decorator_function

class WECG400:
    def __init__(self, lib_file_path):        
        self.sdk = cdll.LoadLibrary(lib_file_path)   

    #
    # Initialization & Cleanup
    #
    @sdk_fn("WECGInit", [ConnectedCallback], c_bool)
    def init(self, cb) -> c_bool:
        pass

    @sdk_fn("WECGConnect", [c_uint, c_uint], c_bool)
    def connect(self, port_number, milliseconds_timeout) -> c_bool:
        pass

    @sdk_fn("WECGFree", [], None)
    def free(self) -> None:
        pass

    @sdk_fn("WECGGetVersion", [], c_uint32)
    def get_version(self) -> c_uint32:
        pass

    @sdk_fn("WECGReset", [], None)
    def reset(self) -> None:
        pass

    @sdk_fn("WECGIsConnected", [], c_bool)
    def is_connected(self) -> c_bool:
        pass

    #
    # Device Configurations
    #
    @sdk_fn("WECGGetDeviceInformation", [c_void_p], c_bool)   # other
    def get_device_information(self) -> c_bool:
        pass

    @sdk_fn("WECGGetHWInformation", [c_void_p], c_bool)
    def get_hw_information(self) -> c_bool:
        pass

    def get_serial_number(self):
        sdk.WECGGetSerialNumber.restype = c_char_p
        return sdk.WECGGetSerialNumber().decode('ascii')

    #
    # Register callback functions
    #
    @sdk_fn("WECGRegisterSignalOutputCallback", [SignalOutputCallback], None)
    def register_signal_output_callback(cb) -> None:
        pass

    @sdk_fn("WECGRegisterFrequencyCallback", [FrequencyCallback], None)
    def register_frequency_callback(cb) -> None:
        pass

    #
    # waveform
    #
    @sdk_fn("WECGSetOutputLead", [c_int, c_int], c_int)
    def set_output_lead(self, channel, lead) -> c_int:
        pass

    @sdk_fn("WECGSetOutputFunction", [c_int], c_int)
    def set_output_function(self, func) -> c_int:
        pass

    @sdk_fn("WECGOutputSignal", [c_int], c_int)
    def output_signal(self, func) -> c_int:
        pass

    @sdk_fn("WECGSetFrequency", [c_double], c_int)
    def set_frequency(self, freq) -> c_int:
        pass

    @sdk_fn("WECGSetAmplitude", [c_double], c_int)
    def set_amplitude(self, amp) -> c_int:
        pass    

    @sdk_fn("WECGSetPulseWidth", [c_double], c_int)
    def set_pulse_width(self, width) -> c_int:
        pass

    @sdk_fn("WECGSetDCOffset", [c_int, c_int], c_int)
    def set_dc_offset(self, channel, setting) -> c_int:
        pass

    @sdk_fn("WECGSetDCOffsetVariableMode", [c_int, c_bool], c_int)
    def set_dc_offset_variable_mode(self, channel, on_off) -> c_int:
        pass

    #
    # customized ECG waveform
    #

    @sdk_fn("WECGSetPRInterval", [c_double], c_int)
    def set_pr_interval(self, time) -> c_int:
        pass

    @sdk_fn("WECGSetQTInterval", [c_double], c_int)
    def set_qt_interval(self, time) -> c_int:
        pass

    @sdk_fn("WECGSetPDuration", [c_double], c_int)
    def set_p_duration(self, time) -> c_int:
        pass

    @sdk_fn("WECGSetPWave", [c_double], c_int)
    def set_pwave(self, amp) -> c_int:
        pass

    @sdk_fn("WECGSetQRSDuration", [c_double], c_int)
    def set_qrs_duration(self, time) -> c_int:
        pass

    @sdk_fn("WECGSetSTDeviation", [c_double], c_int)
    def set_st_deviation(self, amp) -> c_int:
        pass

    @sdk_fn("WECGSetTWave", [c_double], c_int)
    def set_twave(self, amp) -> c_int:
        pass

    #
    # pacing pulse
    #
    @sdk_fn("WECGSetPacingPulseMode", [c_int], c_int)
    def set_pacing_pulse_mode(self, mode) -> c_int:
        pass

    @sdk_fn("WECGSetPacingAmplitude", [c_double], c_int)
    def set_pacing_amplitude(self, amp) -> c_int:
        pass

    @sdk_fn("WECGSetPacingDuration", [c_double], c_int)
    def set_pacing_duration(self, time) -> time:
        pass

    @sdk_fn("WECGSetPacingRate", [c_uint], c_int)
    def set_pacing_rate(self, bpm) -> c_int:
        pass

    @sdk_fn("WECGSetFrequencyScan", [c_bool, c_double, c_double, c_int], c_int)
    def set_frequency_scan(self, on_off, start_freq, stop_freq, duration) -> c_int:
        pass

    @sdk_fn("WECGSetInputImpedanceMode", [c_int, c_int], c_int)
    def set_input_impedance(self, channel, mode) -> c_int:
        pass
    
    @sdk_fn("WECGGetInputImpedanceMode", [c_int], c_int)
    def get_input_impedance(self, channel, mode) -> c_int:
        pass    
        
    @sdk_fn("WECGSetSignalGroundToGNDGround", [c_int, c_bool], c_int)
    def set_signal_ground_to_gnd_ground(self, channel, connect) -> c_int:
        pass    
        

    @sdk_fn("WECGSaveToStandaloneMode", [c_int], c_bool)
    def save_to_standalone_mode(self, func) -> c_bool:
        pass

    #
    # load raw data
    #
    @sdk_fn("WECGLoadRawData", [c_void_p], c_int)
    def load_raw_data(self) -> c_int:
        pass
