# Source code for canlib.canlib.busparams

    """A set of helper functions to the new busparam API added in CANlib v5.34.

        .. versionadded :: v1.16

    """

    import collections

    SI_PREFIXES = {
        24: 'Y',
        21: 'Z',
        18: 'E',
        15: 'P',
        12: 'T',
        9: 'G',
        6: 'M',
        3: 'k',
        0: '',
        -3: 'm',
        -6: 'u',
        -9: 'n',
        -12: 'p',
        -15: 'f',
        -18: 'a',
        -21: 'z',
        -24: 'y',
    }

    def calc_bitrate(target_bitrate, clk_freq):
        """Calculate nearest available bitrate

        Args:
            target_bitrate (`int`): Wanted bitrate (bit/s)
            clk_freq (`int`): Device clock frequency (Hz)

        Returns:
            The returned tuple is a ``(bitrate, tq)`` named tuple of:

            #. ``bitrate`` (`int`): Available bitrate, could be a rounded value (bit/s)
            #. ``tq`` (`int`): Number of time quanta in one bit

        .. versionadded :: v1.16

        """
        tq = round(clk_freq / target_bitrate)
        # Minimum tq is 3 (sync_seg + phase1 + phase2)
        if tq < 3:
            tq = 3
        bitrate = clk_freq / tq

        Bitrate = collections.namedtuple('Bitrate', 'bitrate tq')
        return Bitrate(bitrate=bitrate, tq=tq)

    def calc_busparamstq(target_bitrate,
                         target_sample_point,
                         target_sync_jump_width,
                         clk_freq,
                         target_prop_tq=None):
        """Calculate closest matching busparameters.

        The device clock frequency, `clk_freq`, can be obtained via
        `ChannelData.clock_info.frequency()`:

            >>> chd = canlib.ChannelData(channel_number=0)
            >>> clock_info = chd.clock_info
            >>> clock_info.frequency()
            80000000.0

        Now call `calc_busparamstq` with target values, and a `BusParamsTq`
        object will be returned:

            >>> params = calc_busparamstq(
            ... target_bitrate=470_000,
            ... target_sample_point=82,
            ... target_sync_jump_width=33.5,
            ... clk_freq=clock_info.frequency())
            >>> params
            BusParamsTq(tq=170, prop=107, phase1=31, phase2=31, sjw=57, prescaler=1)

        Note: Paramters are calculated with prescaler = 1, minimum sjw returned is 1.

        Args:
            target_bitrate (`float`): Wanted bitrate (bit/s)
            target_sample_point (`float`): Wanted sample point in percentage (0-100)
            target_sync_jump_width (`float`): Wanted sync jump width, 0-100 (%)
            clk_freq (`float`): Device clock frequency (Hz)
            target_prop_tq(`int`, Optional): Wanted propagation segment (time quanta)

        Returns:
            `BusParamsTq`: Calculated bus parameters

        .. versionadded :: v1.16

        """
        bitrate, tq = calc_bitrate(target_bitrate, clk_freq)
        prescaler = 1
        sjw, sync_jump_width = calc_sjw(tq, target_sync_jump_width)
        sample_point, phase2 = calc_sample_point(tq, target_sample_point)
        sync_tq = 1

        if target_prop_tq is None:
            phase1 = phase2
        else:
            phase1 = calc_phase_seg1(tq, target_prop_tq, phase2, sync_tq)
            if phase1 < sjw:
                phase1 = sjw

        prop = calc_prop_seg(tq, phase1, phase2, sync_tq)
        return BusParamsTq(
            tq=tq,
            prop=prop,
            phase1=phase1,
            phase2=phase2,
            sjw=sjw,
            prescaler=prescaler,
        )

    def calc_phase_seg1(tq, prop, phase2, sync_tq=1):
        """Calculate phase segment 1"""
        return tq - prop - phase2 - sync_tq

    def calc_prop_seg(tq, phase1, phase2, sync_tq=1):
        """Calculate propagation segment"""
        return tq - phase2 - phase1 - sync_tq

    def calc_sample_point(tq, target_sample_point):
        """Calculate actual sample_point and phase segment 2

        tq (`int`): Total number of quanta in one bit
        target_sample_point (`float`): Wanted sample point in percentage (0-100)

        Returns:
            named tuple(sample_point, phase2)

        phase2: Number of time quanta in phase segment 2
        sample_point: Sample point in percentage

        .. versionadded :: v1.16

        """
        phase2 = tq - (round((target_sample_point / 100) * tq))
        sample_point = ((tq - phase2) / tq) * 100

        SamplePoint = collections.namedtuple('SamplePoint', 'sample_point phase2')
        return SamplePoint(sample_point=sample_point, phase2=phase2)

    def calc_sjw(tq, target_sync_jump_width):
        """Calculate sync jump width

        tq: Number of time quanta in one bit
        target_sync_jump_width: Wanted sync jump width, 0-100 (%)

        Note: Minimum sjw_tq returned is 1.

        Returns:
            `namedtuple`(sjw_tq, sync_jump_width)

        sjw_tq: Size of sync jump width in number of time quanta
        sync_jump_width: Size of sync jump width in percentage (%)

        .. versionadded :: v1.16

        """
        sjw_tq = round((target_sync_jump_width / 100) * tq)
        if sjw_tq < 1:
            sjw_tq = 1
        sync_jump_width = round(sjw_tq / tq * 100)

        Sjw = collections.namedtuple('Sjw', 'sjw_tq sync_jump_width')
        return Sjw(sjw_tq=sjw_tq, sync_jump_width=sync_jump_width)

    class ClockInfo:
        """Information about clock a oscillator

        The clock frequency is set in the form:

            frequency = numerator / denominator * 10 ** power_of_ten +/- accuracy

        .. versionadded :: v1.16

        """
        @classmethod
        def from_list(cls, args):
            """Create a `ClockInfo` object from a versioned list of values.

            The first number in the list is the version number for the list and
            must be 1. See the `kvClockInfo` structure in CANlib for more
            information.

            Args:
                args (`list` (version (`int`), ...)): where version must be 1.

            Returns:
                `ClockInfo`

            """
            version = args[0]
            if version == 1:
                return ClockInfo(
                    numerator=args[1],
                    denominator=args[2],
                    power_of_ten=args[3],
                    accuracy=args[4],
                )
            else:
                raise ValueError(
                    "Clock info version '{v}' is unknown, perhaps update canlib?".format(v=version)
                )

        def __init__(self, numerator, denominator, power_of_ten, accuracy):
            self.numerator = numerator
            self.denominator = denominator
            self.power_of_ten = power_of_ten
            self.accuracy = accuracy

        def __eq__(self, other):
            if not isinstance(other, ClockInfo):
                return NotImplemented
            if self.accuracy != other.accuracy:
                return False
            if self.frequency() != other.frequency():
                return False
            return True

        def __repr__(self):
            txt = (
                f"ClockInfo(numerator={self.numerator}, denominator={self.denominator}, "
                f"power_of_ten={self.power_of_ten}, accuracy={self.accuracy})"
            )
            return txt

        def __str__(self):
            frequency = self.frequency()
            if self.power_of_ten in SI_PREFIXES:
                frequency = frequency / 10 ** self.power_of_ten
                si_prefix = SI_PREFIXES[self.power_of_ten]
            else:
                si_prefix = ""
            txt = self.__repr__() + f", (frequency: {frequency} {si_prefix}Hz)"
            return txt

        def frequency(self):
            """Returns an approximation of the clock frequency as a float."""
            return self.numerator / self.denominator * 10 ** self.power_of_ten

    class BusParamsTq:
        """Holds parameters for busparameters in number of time quanta.

        If you don't want to specify the busparameters in time quanta directly,
        you may use `calc_busparamstq` which returns an object of this class.

            >>> params = calc_busparamstq(
            ... target_bitrate=470_000,
            ... target_sample_point=82,
            ... target_sync_jump_width=33.5,
            ... clk_freq=clk_freq)
            >>> params
            BusParamsTq(tq=170, prop=107, phase1=31, phase2=31, sjw=57, prescaler=1)

        You may now query for the actual Sample Point and Sync Jump Width expressed
        as percentages of total bit time quanta:

            >>> params.sample_point()
            81.76470588235294

            >>> params.sync_jump_width()
            33.52941176470588

        If you supply the clock frequency, you may also calculate the corresponding bitrate:

            >>> params.bitrate(clk_freq=80_000_000)
            470588.23529411765

        Args:
            tq (`int`): Number of time quanta in one bit.
            phase1 (`int`): Number of time quanta in Phase Segment 1.
            phase2 (`int`): Number of time quanta in Phase Segment 2.
            sjw (`int`): Number of time quanta in Sync Jump Width.
            prescaler (`int`): Prescaler value (1-2 to enable auto in CAN FD)
            prop (`int`, optional): Number of time quanta in Propagation Segment.

        .. versionadded :: v1.16

        """
        def __init__(self, tq, phase1, phase2, sjw, prescaler=1, prop=None):
            self.sync = 1
            if prop is None:
                self.prop = tq - phase1 - phase2 - self.sync
            else:
                self.prop = prop
            if tq != self.sync + self.prop + phase1 + phase2:
                raise ValueError(
                    f"Total number of time quanta does not match: {tq}(tq) !="
                    f" {self.sync}(sync) + {self.prop}(prop) + {phase1}(phase1) + {phase2}(phase2)"
                )
            self.tq = tq
            self.phase1 = phase1
            self.phase2 = phase2
            self.sjw = sjw
            self.prescaler = prescaler

        def __eq__(self, other):
            if not isinstance(other, BusParamsTq):
                return NotImplemented
            if self.tq != other.tq:
                return False
            if self.phase1 != other.phase1:
                return False
            if self.phase2 != other.phase2:
                return False
            if self.sjw != other.sjw:
                return False
            if self.prescaler != other.prescaler:
                return False
            if self.prop != other.prop:
                return False
            return True

        def __repr__(self):
            txt = (f"BusParamsTq(tq={self.tq}, prop={self.prop}, phase1={self.phase1},"
                   f" phase2={self.phase2}, sjw={self.sjw}, prescaler={self.prescaler})")
            return txt

        def bitrate(self, clk_freq):
            """Return bitrate assuming the given clock frequency

            Args:
                clk_freq (`int`): Clock frequency (in Hz)
            """
            return clk_freq / self.tq

        def sample_point(self):
            """Return sample point in percentage."""
            return ((self.tq - self.phase2) / self.tq) * 100

        def sync_jump_width(self):
            """Return sync jump width (SJW) in percentage."""
            return self.sjw / self.tq * 100

    def calc_tolerance(nominal, data=None):
        """Calculate tolerance with the given `BusParamsTq`.

        Args:
            nominal (`BusParamsTq`): Nominal bus parameters
            data (`BusParamsTq`, optional): Bus parameters for data phase (in CAN FD)

        Returns:
            `namedtuple`(df1, df2, df3, df4, df5)

        .. versionadded :: v1.16

        """
        df1 = nominal.sjw / (2 * 10 * nominal.tq)
        df1 = round((df1 * 2) * 1000000)

        min_Tphase_seg = min(nominal.phase1, nominal.phase2)
        df2 = min_Tphase_seg / ((13 * nominal.tq) - nominal.phase2)
        df2 = round(df2 * 1000000)

        if data is None:
            df3 = None
            df4 = None
            df5 = None
        else:
            df3 = data.sjw / (10 * data.tq)
            df3 = round(df3 * 1000000)

            d_n_ratio = data.prescaler / nominal.prescaler
            df4 = (min_Tphase_seg / (6 * data.tq - data.phase2
                                     * d_n_ratio + 7 * nominal.tq))
            df4 = round(df4 * 1000000)

            n_d_ratio = nominal.prescaler / data.prescaler
            df5 = ((data.sjw - max(0, n_d_ratio - 1)) /
                   ((2 * nominal.tq - nominal.phase2) *
                    n_d_ratio + data.phase2 + 4 * data.tq))
            df5 = round(df5 * 1000000)

        Tolerance = collections.namedtuple('Tolerance', 'df1 df2 df3 df4 df5')
        return Tolerance(df1=df1, df2=df2, df3=df3, df4=df4, df5=df5)