from xmlrpc.server import SimpleXMLRPCServer
import smbus
import yaml
from typing import Optional

BUS: int = 1
REG_00: int = 0X00
REG_01: int = 0X01
REG_02: int = 0X02
REG_04: int = 0X04
REG_13: int = 0X13
FREQ_SCALE_FACTOR: int = 20
KT0803_ADDRESS: int = 0x3E
MAX_FREQ: int = 108
MAX_CHANNEL: int = 2160
MIN_FREQ: int = 86
MIN_CHANNEL: int = 1720

BUS = smbus.SMBus(BUS)


with open("/etc/rpi-fm-transmitter/config.yaml") as f:
    config: dict[str,str] = yaml.safe_load(f)

SERVER = SimpleXMLRPCServer((config['address'], int(config['port'])))

def check_device_connection() -> bool:
    try:
        # Quick write operation to the KT0803 device at the specified I2C address.
        BUS.write_quick(KT0803_ADDRESS)
        return True
    except IOError:
        return False
    
def _write_data(reg: int, data: int) -> bool:
    try:
        # Writes data to a specified register on the KT0803 device via I2C.
        BUS.write_byte_data(KT0803_ADDRESS, reg, data)
        return True
    except IOError:
        return False
 
def _read_data(reg: int) -> Optional[int]:
    try:
        # Reads data from a specified register on the KT0803 device via I2C.
        return BUS.read_byte_data(KT0803_ADDRESS, reg)
    except IOError:
        return None

def set_frequency(frequency: float) -> bool:
    if frequency < MIN_FREQ or frequency > MAX_FREQ:
        return False
    channel = round(frequency * FREQ_SCALE_FACTOR)
    return _set_channel(channel)

def _set_channel(channel: int) -> bool:
    if channel < MIN_CHANNEL or channel > MAX_CHANNEL:
        return False
    
    #CHSEL[11:0] = Reg0x1[2:0]:Reg0x0[7:0]:Reg0x2[7]
    register0: int = (channel >> 1) & 0xFF   #Reg0x0[7:0]
    if not _write_data(REG_00, register0):
        return False

    register1: int = (channel >> 9) & 0x07   # Reg0x1[2:0]
    if not _write_data(REG_01, register1):
        return False

    register2: int = (channel & 0x01) << 7  # Reg0x2[7]
    if not _write_data(REG_02, register2):
        return False

    return True

def set_mute(mute: bool) -> bool:
    # Read register value
    data: Optional[int] = _read_data(REG_02)
    if data is None:
        return False
    if mute and (data & 0x08) == 0x08:
        return True
    if not mute and (data & 0x08) == 0x00:
        return True
    data ^= 0x08 # Toggle the bit to REG_02[3]
    return _write_data(REG_02, data)

def set_bass_control(bass_level: int) -> bool:
    # Read register value
    current_reg04: Optional[int] = _read_data(REG_04)
    if current_reg04 is None:
        print("Error: Failed to read REG_04 for bass control")
        return False
            
    # Set bass level based on input
    bass_levels: dict[int, int] = {
            0: 0x00,  # Disabled
            1: 0x01,  # 5dB
            2: 0x02,  # 11dB
            3: 0x03   # 17dB
        }
        
    get_level: int = bass_levels[bass_level]
    register0: int = (current_reg04 & 0xFC) | get_level
    return _write_data(REG_04, register0)
        
def set_power_level(RF_level: int) -> bool:
    if not isinstance(RF_level, int) or not 0 <= RF_level <= 15:
        print(f"Invalid RF_level: {RF_level}, must be an integer between 0 and 15")
        return False
    # Read register value
    current_reg01: Optional[int] = _read_data(REG_01)
    current_reg02: Optional[int] = _read_data(REG_02)
    current_reg13: Optional[int] = _read_data(REG_13)

    if current_reg01 is None or current_reg02 is None or current_reg13 is None:
        print("Error: Failed to read registers for power level")
        return False

    register0: int = (current_reg01 & 0x3F) | (RF_level & 0x03) << 6  # Bits 0-1 to REG_01[7:6]
    register1: int = (current_reg13 & 0x7f) | (((RF_level >> 2) & 0x01) << 7)  # Bit 2 to REG_13[7]
    register2: int = (current_reg02 & 0xbf) | (((RF_level >> 3) & 0x01) << 6)  # Bit 3 to REG_02[6]

    
    if not _write_data(REG_01, register0):
        return False

    if not _write_data(REG_02, register2):
        return False

    if not _write_data(REG_13, register1):
        return False

    return True

def set_pga_gain(gain_level: int) -> bool:
    if not isinstance(gain_level, int):
        print(f"Invalid gain_level: {gain_level}, must be an integer between 9 and 12")
        return False
    # Set PGA gain level based on input
    gain_levels: dict[int,int] = {
        12:  0x03,  # PGA_LSB1=1, PGA_LSB0=1
        11:  0x02,  # PGA_LSB1=1, PGA_LSB0=0
        10:  0x01,  # PGA_LSB1=0, PGA_LSB0=1
        9 :  0x00   # PGA_LSB1=0, PGA_LSB0=0
    }
    
    # Read register value
    current_reg01: Optional[int] = _read_data(REG_01)
    current_reg02: Optional[int] = _read_data(REG_04)
    if current_reg01 is None or current_reg02 is None:
        print("Error: Failed to read registers for PGA gain")
        return False

    gain_level: int = gain_levels[gain_level]

    register0: int = (current_reg01 & 0xC7) | (0b111 << 3) # Bits PGA[2-0] to REG_01[5:3]
    register1: int = (current_reg02 & 0xCf) | ((gain_level & 0x03) << 4) # Bits PGA_LSB[1-0] to REG_04[5:4]

    
    if not _write_data(REG_01, register0):
        return False

    if not _write_data(REG_04, register1):
        return False

    return True

def set_mono_control(mono: bool) -> bool:
    # Read register value
    current_reg04: Optional[int] = _read_data(REG_04)
    if current_reg04 is None:
        print("Error: Failed to read REG_04 for mono control")
        return False
    
    if mono and (current_reg04 & 0x40) == 0x40: # Set 1 to REG_04[6]
        return True
    if not mono and (current_reg04 & 0x40) == 0x00: # Set 0 to REG_04[6]
        return True
    current_reg04 ^= 0x40 # Toggle the bit in REG_04[6]
    return _write_data(REG_04, current_reg04)

if __name__ == "__main__":
    SERVER.register_function(check_device_connection, 'check_device_connection')
    SERVER.register_function(set_frequency, 'set_frequency')
    SERVER.register_function(set_mute, 'set_mute')
    SERVER.register_function(set_bass_control, 'set_bass_control')
    SERVER.register_function(set_power_level, 'set_power_level')
    SERVER.register_function(set_pga_gain, 'set_pga_gain')
    SERVER.register_function(set_mono_control, 'set_mono_control')
    SERVER.serve_forever()
