from __future__ import annotations
from driver.base_tef import BaseTEF668X
from typing import overload, ParamSpec, TypeVar, Concatenate, Callable
from functools import wraps
import struct
from enum import IntEnum

P = ParamSpec("P")
T = TypeVar("T")

@overload
def _command_wrapper(func: Callable[Concatenate[TEF6686, P],tuple[bytes, int | None, None],],) -> Callable[Concatenate[TEF6686, P], bytes]: ...
@overload
def _command_wrapper(func: Callable[Concatenate[TEF6686, P],tuple[bytes, int | None, Callable[[bytes], T]],],) -> Callable[Concatenate[TEF6686, P], T]: ...

def _command_wrapper(func: Callable[Concatenate[TEF6686, P], tuple[bytes, int | None, Callable[[bytes], T] | None], ]) -> Callable[Concatenate[TEF6686, P], bytes | T]:
    @wraps(func)
    def inner(self: TEF6686, *args: P.args, **kwargs: P.kwargs) -> bytes | T:
        data, read_bytes, out_parser = func(self, *args, **kwargs)
        data, read_bytes, out_parser = self._pre_command_(func, data, read_bytes, out_parser)

        if read_bytes: 
            data = self.p.write_read_i2c(self.address, data, read_bytes)
        else: data = self.p.write_i2c(self.address, data)
        return out_parser(data) if out_parser else data
    return inner

class SignedInt(int): ...
def pack(*values: int | SignedInt):
    struct_string = ">"
    for value in values:
        if isinstance(value, SignedInt): struct_string += "h"
        else: struct_string += "H"
    return struct.pack(struct_string, *values)

class TEF6686(BaseTEF668X):
    def _pre_command_(self, wrap: Callable, data: bytes, read_bytes: int | None, out_parser: Callable | None): return data, read_bytes, out_parser

    class TuneTo_Mode(IntEnum):
        Preset = 1
        Search = 2
        FM_AF_Update = 3
        FM_Jump = 4
        FM_Check = 5
        End = 7
    @_command_wrapper
    def FM_Tune_To(self, mode: TuneTo_Mode, frequency: int | None): return b"\x20\x01\x01" + (pack(mode, frequency) if frequency is not None else pack(mode)), None, None
    @_command_wrapper
    def AM_Tune_To(self, mode: TuneTo_Mode, frequency: int | None): return b"\x21\x01\x01" + (pack(mode, frequency) if frequency is not None else pack(mode)), None, None

    @_command_wrapper
    def FM_Set_Tune_Options(self, afu_bw_mode: bool = False, afu_bandwidth: int = 2360, afu_mute_time: int = 1000, afu_sample_time: int = 2000):
        return b"\x20\x02\x01" + pack(afu_bw_mode, afu_bandwidth, afu_mute_time, afu_sample_time), None, None

    @_command_wrapper
    def FM_Set_Bandwidth(self, mode: bool = True, bandwidth: int = 2360, control_sensitivity: int = 1000, low_level_sensitivity: int = 1000, min_bandwidth: int = 560, nominal_bandwidth: int = 2360, control_attack: int = 300):
        """Minimal bandwidth requires p2.13, while nominal_bandwidth and control_attack require p2.17 (we have p2.24)"""
        return b"\x20\x0a\x01" + pack(mode, bandwidth, control_sensitivity, low_level_sensitivity, min_bandwidth, nominal_bandwidth, control_attack), None, None
    @_command_wrapper
    def AM_Set_Bandwidth(self, bandwidth: int = 40): return b"\x21\x0a\x01\x00\x00" + pack(bandwidth), None, None

    @_command_wrapper
    def FM_Set_RFAGC(self, start: int = 920, extension: bool = False): return b"\x20\x0b\x01" + pack(start, extension), None, None
    @_command_wrapper
    def AM_Set_RFAGC(self, start: int = 1000): return b"\x21\x0b\x01" + pack(start), None, None

    @_command_wrapper
    def AM_Set_Antenna(self, attenuation: int = 0): return b"\x21\x0c\x01" + pack(attenuation), None, None

    @_command_wrapper
    def AM_Set_CoChannelDet(self, mode: bool = True, restart: int = 2, sensitivity: int = 1000, count: int = 3): return b"\x21\x0e\x01" + pack(mode, restart, sensitivity, count), None, None

    @_command_wrapper
    def FM_Set_MphSuppression(self, mode: bool = False): return b"\x20\x14\x01" + pack(mode), None, None

    @_command_wrapper
    def FM_Set_ChannelEqualizer(self, mode: bool = False): return b"\x20\x16\x01" + pack(mode), None, None

    @_command_wrapper
    def FM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000, modulation: int = 900, offset: int = 1, attack: int = 140, decay: int = 2800):
        return b"\x20\x17\x01" + pack(mode, sensitivity, 0, modulation, offset, attack, decay), None, None

    @_command_wrapper
    def FM_Set_NoiseBlanker_Options(self, blank_time: int = 210, blank_time2: int = 210, blank_modulation: int = 250): return b"\x20\x18\x01" + pack(blank_time, blank_time2, blank_modulation), None, None

    @_command_wrapper
    @overload
    def AM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000, gain: int = 1000, blank_time: int = 56): return b"\x21\x17\x01" + pack(mode, sensitivity, gain, blank_time), None, None
    @_command_wrapper
    def AM_Set_NoiseBlanker(self, mode: bool = True, sensitivity: int = 1000): return b"\x21\x17\x01" + pack(mode, sensitivity), None, None

    @_command_wrapper
    @overload
    def AM_Set_NoiseBlanker_Audio(self, mode: bool = True, sensitivity: int = 1000, blank_time: int = 800): return b"\x21\x18\x01" + pack(mode, sensitivity, 0, blank_time), None, None
    @_command_wrapper
    def AM_Set_NoiseBlanker_Audio(self, mode: bool = True, sensitivity: int = 1000): return b"\x21\x18\x01" + pack(mode, sensitivity), None, None

    @_command_wrapper
    def FM_Set_Deemphasis(self, timeconstant: int = 500): return b"\x20\x1f\x01" + pack(timeconstant), None, None

    @_command_wrapper
    def FM_Set_LevelStep(self, step1: int = -20, step2: int = -30, step3: int = -40, step4: int = -50, step5: int = -60, step6: int = -60, step7: int = -60):
        return b"\x20\x26\x01" + pack(SignedInt(step1), SignedInt(step2), SignedInt(step3), SignedInt(step4), SignedInt(step5), SignedInt(step6), SignedInt(step7)), None, None
    @_command_wrapper
    def AM_Set_LevelStep(self, step1: int = -10, step2: int = -20, step3: int = -30, step4: int = -40, step5: int = -50, step6: int = -60, step7: int = -60):
        return b"\x21\x26\x01" + pack(SignedInt(step1), SignedInt(step2), SignedInt(step3), SignedInt(step4), SignedInt(step5), SignedInt(step6), SignedInt(step7)), None, None

    @_command_wrapper
    def FM_Set_LevelOffset(self, offset: int = 0): return b"\x20\x27\x01" + pack(SignedInt(offset)), None, None
    @_command_wrapper
    def AM_Set_LevelOffset(self, offset: int = 0): return b"\x21\x27\x01" + pack(SignedInt(offset)), None, None

    @_command_wrapper
    def FM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 20, fast_decay: int = 20):
        return b"\x20\x28\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
    @_command_wrapper
    def AM_Set_Softmute_Time(self, slow_attack: int = 120, slow_decay: int = 500, fast_attack: int = 120, fast_decay: int = 500):
        return b"\x21\x28\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
    @_command_wrapper
    def AM_Set_Softmute_Mod(self, mode: bool = False, start: int = 210, slope: int = 120, shift: int = 260): return b"\x21\x29\x01" + pack(mode, start, slope, shift), None, None
    @_command_wrapper
    def FM_Set_Softmute_Level(self, mode: int = 0, start: int = 150, slope: int = 220): return b"\x20\x2A\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def AM_Set_Softmute_Level(self, mode: int = 0, start: int = 280, slope: int = 250): return b"\x21\x2A\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Softmute_Noise(self, mode: int = 0, start: int = 500, slope: int = 1000): return b"\x20\x2b\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Softmute_Mph(self, mode: int = 0, start: int = 500, slope: int = 1000): return b"\x20\x2c\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Softmute_Max(self, mode: bool = True, start: int = 200): return b"\x20\x2c\x01" + pack(mode, start), None, None
    @_command_wrapper
    def AM_Set_Softmute_Max(self, mode: bool = True, start: int = 250): return b"\x21\x2c\x01" + pack(mode, start), None, None

    @_command_wrapper
    def FM_Set_Highcut_Time(self, slow_attack: int = 500, slow_decay: int = 2000, fast_attack: int = 20, fast_decay: int = 20):
        return b"\x20\x32\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
    @_command_wrapper
    def AM_Set_Highcut_Time(self, slow_attack: int = 500, slow_decay: int = 2000, fast_attack: int = 12, fast_decay: int = 50):
        return b"\x21\x32\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
    @_command_wrapper
    def FM_Set_Highcut_Mod(self, mode: bool = False, start: int = 250, slope: int = 130, shift: int = 500): return b"\x20\x33\x01" + pack(mode, start, slope, shift), None, None
    @_command_wrapper
    def AM_Set_Highcut_Mod(self, mode: bool = False, start: int = 250, slope: int = 130, shift: int = 500): return b"\x21\x33\x01" + pack(mode, start, slope, shift), None, None
    @_command_wrapper
    def FM_Set_Highcut_Level(self, mode: int = 3, start: int = 360, slope: int = 300): return b"\x20\x34\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def AM_Set_Highcut_Level(self, mode: int = 2, start: int = 400, slope: int = 200): return b"\x21\x34\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Highcut_Noise(self, mode: int = 2, start: int = 360, slope: int = 300): return b"\x20\x35\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Highcut_Mph(self, mode: int = 2, start: int = 360, slope: int = 300): return b"\x20\x36\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Highcut_Max(self, mode: bool = True, limit: int = 4000): return b"\x20\x37\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def AM_Set_Highcut_Max(self, mode: bool = True, limit: int = 1800): return b"\x21\x37\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def FM_Set_Highcut_Min(self, mode: bool = False, limit: int = 10000): return b"\x20\x38\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def AM_Set_Highcut_Min(self, mode: bool = False, limit: int = 6000): return b"\x21\x38\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def FM_Set_Lowcut_Max(self, mode: bool = True, limit: int = 120): return b"\x20\x39\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def AM_Set_Lowcut_Max(self, mode: bool = True, limit: int = 120): return b"\x21\x39\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def FM_Set_Lowcut_Min(self, mode: bool = False, limit: int = 20): return b"\x20\x3A\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def AM_Set_Lowcut_Min(self, mode: bool = True, limit: int = 20): return b"\x21\x3A\x01" + pack(mode, limit), None, None
    @_command_wrapper
    def FM_Set_Highcut_Options(self, mode: int = 1): return b"\x20\x3b\x01" + pack(mode), None, None

    @_command_wrapper
    def FM_Set_Stereo_Time(self, slow_attack: int = 1000, slow_decay: int = 4000, fast_attack: int = 80, fast_decay: int = 80):
        return b"\x20\x3c\x01" + pack(slow_attack, slow_decay, fast_attack, fast_decay), None, None
    @_command_wrapper
    def FM_Set_Stereo_Mod(self, mode: bool = False, start: int = 210, slope: int = 90, shift: int = 500):
        return b"\x20\x3d\x01" + pack(mode, start, slope, shift), None, None
    @_command_wrapper
    def FM_Set_Stereo_Level(self, mode: int = 3, start: int = 460, slope: int = 240): return b"\x20\x3e\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Stereo_Noise(self, mode: int = 3, start: int = 240, slope: int = 200): return b"\x20\x3f\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Stereo_Mph(self, mode: int = 3, start: int = 240, slope: int = 200): return b"\x20\x40\x01" + pack(mode, start, slope), None, None
    @_command_wrapper
    def FM_Set_Stereo_Max(self, mode: bool = True): return b"\x20\x41\x01" + pack(mode), None, None
    @_command_wrapper
    def FM_Set_Stereo_Min(self, mode: int = 0, limit: int = 400): return b"\x20\x42\x01" + pack(mode, limit), None, None

    @_command_wrapper
    def FM_Set_RDS(self, mode: int = 0, restart: int = 2, interface: int = 0): return b"\x20\x51\x01" + pack(mode, restart, interface), None, None

    @_command_wrapper
    def FM_Set_Specials(self, mode: int = 0): return b"\x20\x55\x01" + pack(mode), None, None

    @_command_wrapper
    def FM_Set_Bandwidth_Options(self, modulation: int = 950): return b"\x20\x56\x01" + pack(modulation), None, None

    @_command_wrapper
    def AUDIO_Set_Volume(self, volume: int = 0): return b"\x30\x0a\x01" + pack(SignedInt(volume)), None, None

    @_command_wrapper
    def AUDIO_Set_Mute(self, mute: bool = True): return b"\x30\x0b\x01" + pack(mute), None, None

    @_command_wrapper
    def AUDIO_Set_Input(self, source: int = 0): return b"\x30\x0c\x01" + pack(source), None, None

    @_command_wrapper
    def AUDIO_Set_Output_Source(self, signal: int, source: int = 224): return b"\x30\x0d\x01" + pack(signal, source), None, None

    @_command_wrapper
    def AUDIO_Set_Ana_Out(self, signal: int, mode: bool = True): return b"\x30\x15\x01" + pack(signal, mode), None, None

    @_command_wrapper
    def AUDIO_Set_Dig_IO(self, signal: int, mode: int = 0, format: int = 32, operation: int = 0, sample_rate: int = 4410): return b"\x30\x16\x01" + pack(signal, mode, format, operation, sample_rate), None, None

    @_command_wrapper
    def AUDIO_Set_Input_Scaler(self, source: int, gain: int = 0): return b"\x30\x17\x01" + pack(source, SignedInt(gain)), None, None

    @_command_wrapper
    def AUDIO_Set_WaveGen(self, mode: int = 0, offset: int = 0, amplitude1: int = -200, frequency1: int = 400, amplitude2: int = -200, frequency2: int = 1000):
        return b"\x30\x18\x01" + pack(mode, SignedInt(offset), SignedInt(amplitude1), frequency1, SignedInt(amplitude2), frequency2), None, None

    @_command_wrapper
    def APPL_Set_OperationMode(self, mode: bool = True): return b"\x40\x01\x01" + pack(mode), None, None

    @_command_wrapper
    def APPL_Set_GPIO(self, pin: int, module: int, feature: int): return b"\x40\x03\x01" + pack(pin, module, feature), None, None

    # Activate and refrence clock are in the base

    @staticmethod
    def _get_quality_data(data) -> None | tuple[int, int, int, int, int, int, int]:
            if data[0] != 0: return None
            return struct.unpack_from(">HhHHhHH", data, 1)
    @_command_wrapper
    def FM_Get_Quality_Status(self):
        return b"\x20\x80\x01", 7*2, self._get_quality_data
    @_command_wrapper
    def FM_Get_Quality_Data(self):
        return b"\x20\x81\x01", 7*2, self._get_quality_data
    @_command_wrapper
    def AM_Get_Quality_Status(self):
        return b"\x21\x80\x01", 7*2, self._get_quality_data
    @_command_wrapper
    def AM_Get_Quality_Data(self):
        return b"\x21\x81\x01", 7*2, self._get_quality_data

    @staticmethod
    def _get_rds_data_proc_decoder(data) -> None | tuple[int, int, int, int, int, int]:
        if data[0] != 0: return None
        return struct.unpack_from(">HHHHHH", data, 1)
    @staticmethod
    def _get_rds_data_proc_demodulator(data):
        if data[0] != 0: return None
        return struct.unpack_from(">HI", data, 1)
    @_command_wrapper
    def FM_Get_RDS_Status__decoder(self):
        return b"\x20\x82\x01", 2*6, self._get_rds_data_proc_decoder
    @_command_wrapper
    def FM_Get_RDS_Data__decoder(self):
        return b"\x20\x83\x01", 2*6, self._get_rds_data_proc_decoder
    @_command_wrapper
    def FM_Get_RDS_Status__demodulator(self):
        return b"\x20\x82\x01", 2*3, self._get_rds_data_proc_demodulator
    @_command_wrapper
    def FM_Get_RDS_Data__demodulator(self):
        return b"\x20\x83\x01", 2*3, self._get_rds_data_proc_demodulator

    @_command_wrapper
    def FM_Get_AGC(self):
        """
        Returns None if there was a I2C error, or a tuple of booleans (FM Stereo/Mono and Analog/Digital radio [DR models])
        """
        def proc(data):
            if data[0] != 0: return None
            return struct.unpack_from(">HH", data, 1)
        return b"\x20\x84\x01", 2*2, proc

    @_command_wrapper
    def FM_Get_Signal_Status(self):
        """
        Returns None if there was a I2C error, or a tuple of booleans (FM Stereo/Mono and Analog/Digital radio [DR models])
        """
        def proc(data):
            if data[0] != 0: return None
            value, *_ = struct.unpack_from(">H", data, 1)
            return (value & (1 << 15)) != 0, (value & (1 << 14)) != 0
        return b"\x20\x85\x01", 2, proc

    @_command_wrapper
    def FM_Get_Processing_Status(self):
        def proc(data):
            if data[0] != 0: return None
            return struct.unpack_from(">HHHH", data, 1)
        return b"\x20\x86\x01", 2*4, proc

    # Get_Operation_Status is defined in the base

    @_command_wrapper
    def APPL_Get_GPIO_Status(self):
        def proc(data):
            if data[0] != 0: return None
            return bool(data[1] & 1), bool((data[1] >> 1) & 1), bool((data[1] >> 2) & 1)
        return b"\x40\x81\x01", 2, proc

    @_command_wrapper
    def APPL_Get_Identification(self):
        def proc(data):
            if data[0] != 0: return None
            return struct.unpack_from(">HHH", data, 1)
        return b"\x40\x82\x01", 2*3, proc

    @_command_wrapper
    def APPL_Get_LastWrite(self):
        def proc(data):
            if data[0] != 0: return None
            return struct.unpack_from(">HHHHHHH", data, 1)
        return b"\x40\x83\x01", 2*7, proc

@overload
def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P],tuple[bytes, int | None, None],],) -> Callable[Concatenate[TEF6687, P], bytes]: ...
@overload
def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P],tuple[bytes, int | None, Callable[[bytes], T]],],) -> Callable[Concatenate[TEF6687, P], T]: ...

def _7_command_wrapper(func: Callable[Concatenate[TEF6687, P], tuple[bytes, int | None, Callable[[bytes], T] | None], ]) -> Callable[Concatenate[TEF6687, P], bytes | T]:
    @wraps(func)
    def inner(self: TEF6687, *args: P.args, **kwargs: P.kwargs) -> bytes | T:
        data, read_bytes, out_parser = func(self, *args, **kwargs)
        data, read_bytes, out_parser = self._pre_command_(func, data, read_bytes, out_parser)

        if read_bytes: data = self.p.write_read_i2c(self.address, data, read_bytes)
        else: data = self.p.write_i2c(self.address, data)
        if out_parser: return out_parser(data)
        return data
    return inner

class TEF6687(TEF6686):
    @_7_command_wrapper
    def FM_Set_StereoImprovement(self, mode: bool = False):
        return b"\x20\x20\x01" + pack(mode), None, None

    @_7_command_wrapper
    def FM_Set_StBandBlend_Time(self, attack: int = 50, decay: int = 50):
        return b"\x20\x5a\x01" + pack(attack, decay), None, None
    @_7_command_wrapper
    def FM_Set_StBandBlend_Gain(self, band1: int = 1000, band2: int = 1000, band3: int = 1000, band4: int = 1000):
        return b"\x20\x5b\x01" + pack(band1, band2, band3, band4), None, None
    @_7_command_wrapper
    def FM_Set_StBandBlend_Bias(self, band1: int = -75, band2: int = -35, band3: int = -25, band4: int = -25):
        return b"\x20\x5c\x01" + pack(SignedInt(band1), SignedInt(band2), SignedInt(band3), SignedInt(band4)), None, None

@overload
def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P],tuple[bytes, int | None, None],],) -> Callable[Concatenate[TEF6688, P], bytes]: ...
@overload
def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P],tuple[bytes, int | None, Callable[[bytes], T]],],) -> Callable[Concatenate[TEF6688, P], T]: ...

def _8_command_wrapper(func: Callable[Concatenate[TEF6688, P], tuple[bytes, int | None, Callable[[bytes], T] | None], ]) -> Callable[Concatenate[TEF6688, P], bytes | T]:
    @wraps(func)
    def inner(self: TEF6688, *args: P.args, **kwargs: P.kwargs) -> bytes | T:
        data, read_bytes, out_parser = func(self, *args, **kwargs)
        data, read_bytes, out_parser = self._pre_command_(func, data, read_bytes, out_parser)

        if read_bytes: data = self.p.write_read_i2c(self.address, data, read_bytes)
        else: data = self.p.write_i2c(self.address, data)
        if out_parser: return out_parser(data)
        return data
    return inner

class TEF6688(TEF6686):
    @_8_command_wrapper
    def FM_Set_DigitalRadio(self, mode: bool = False):
        return b"\x20\x1e\x01" + pack(mode), None, None
    @_8_command_wrapper
    def AM_Set_DigitalRadio(self, mode: bool = False):
        return b"\x21\x1e\x01" + pack(mode), None, None
    
    @_8_command_wrapper
    def FM_Set_DR_Blend(self, mode: int = 0, in_time: int = 50, out_time: int = 50, gain: int = 0):
        return b"\x20\x53\x01" + pack(mode, in_time, out_time, SignedInt(gain)), None, None
    @_8_command_wrapper
    def AM_Set_DR_Blend(self, mode: int = 0, in_time: int = 50, out_time: int = 50, gain: int = 0):
        return b"\x21\x53\x01" + pack(mode, in_time, out_time, SignedInt(gain)), None, None

    @_8_command_wrapper
    def FM_Set_DR_Options(self, samplerate: int = 0, mode: bool = False, format: int = 4112):
        """Mode = false, means open drain data pin mode"""
        actual_mode = (34 << 8) | (2 if mode else 4)
        return b"\x20\x54\x01" + pack(samplerate, actual_mode, format), None, None
    
    @_8_command_wrapper
    def FM_Get_Interface_Status(self):
        def parse(data): 
            if data[0] != 0: return None
            return struct.unpack_from(">H", data, 1)
        return b"\x20\x87\x01", 2, parse
    @_8_command_wrapper
    def AM_Get_Interface_Status(self):
        def parse(data): 
            if data[0] != 0: return None
            return struct.unpack_from(">H", data, 1)
        return b"\x21\x87\x01", 2, parse

# This is a combination of all of the features of those all
class TEF6689(TEF6687, TEF6688): ...