from .datatypes import Group, GroupIdentifier
from .comfort import get_from_list, BitManipulator
from .af import AlternativeFrequency
from .charset import RDSCharset
class GroupGeneratorError(Exception): pass
class SegmentError(GroupGeneratorError): pass

def get_group(group:int, b:bool=False,details:bool=False) -> tuple[int, int]:
    group = ((group << 4) | int(b))
    if not details:
        return (((group >> 4) & 15) << 12) | (0x800 if b else 0), 0
    else:
        return ((group >> 4) & 15), int(b)

class GroupGenerator:
    @staticmethod
    def basic(pi:int, tp:bool=False, pty:int=0) -> Group:
        """
        This function will generate a basic block structure which includes the PI, TP and PTY, this shouldn't be sent by itself to a decoder

        :param pi: The PI code, should be a 16 bit integer (for example 0x3000), pi codes are country dependent (especially for ECC)
        :param tp: Traffic Program, do you broadcast traffic information
        :param pty: Program Type, see EN50067 page 77-80
        """
        return Group(
            int(pi) & 0xFFFF,
            (int(tp) << 10 | pty << 5) & 0xFFFF,
            0,0,
            False
        )
    @staticmethod
    def ps(basic:Group, ps_text:str, segment:int, ta:bool=False, dpty:bool=False, block2:int=0) -> Group:
        """
        This function will generate a 0A group which includes TA DI AF and 2 characters of text

        :param basic: Basic block structure, which is generated by basic()
        :param ps_text: PS Text
        :param segment: Current segment to generate (ps is segmented into four 2 character segments)
        :param ta: Trafic Annoucement, is traffic information transmitted right now
        :param dpty: Dynamic PTY, do we change PTY?
        :param block2: Block C, you can use this to send AF

        Decoder status: Decoded
        """
        if segment > 3: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            ( basic.b | int(ta) << 4 | ( ( (dpty << 3) >> (3-segment) ) << 2) | segment ) & 0xFFFF,
            (int(block2) & 0xFFFF) or AlternativeFrequency.get_no_af(),
            (RDSCharset.translate(get_from_list(ps_text,segment*2," "))<<8 | RDSCharset.translate(get_from_list(ps_text,segment*2+1," "))) & 0xFFFF, #low byte + high byte as we're using utf-8, and we conver 2 8 bit numbers to a single 16 bit one
            False
        )
    @staticmethod
    def ps_b(basic:Group,ps_text:str,segment:int,ta:bool=False,dpty:bool=False) -> Group:
        """This function will generate a 0B group which is very similiar to a 0A group but no AF

        :param basic: Basic block structure, which is generated by basic()
        :param ps_text: PS Text
        :param segment: Current segment to generate (ps is segmented into four 2 character segments)
        :param ta: Trafic Annoucement, is traffic information transmitted right now
        :param dpty: Dynamic PTY, do we change PTY?

        Decoder status: Decoded
        """
        if segment > 3: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            ( basic.b | get_group(0,True)[0] | int(ta) << 4| ( ( (dpty << 3) >> (3-segment) ) << 2) | segment ) & 0xFFFF,
            basic.a & 0xFFFF,
            (RDSCharset.translate(get_from_list(ps_text,segment*2," "))<<8 | RDSCharset.translate(get_from_list(ps_text,segment*2+1," "))) & 0xFFFF,
            True
        )
    @staticmethod
    def ecc(basic:Group, ecc: int) -> Group:
        """This function will generate a 1A group, with the ECC value

        :param basic: Basic block structure, which is generated by basic()
        :param ecc: The ECC code itself, see EN50067 page 71

        Decoder status: Decoded
        """
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(1)[0]) & 0xFFFF,
            ecc,
            0,
            False
        )
    @staticmethod
    def broadcasterdata(basic:Group, data: int) -> Group:
        """This function will generate a 1A group

        :param basic: Basic block structure, which is generated by basic()

        Decoder status: Decoded
        """
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(1)[0]) & 0xFFFF,
            (basic.c | ((data & 0b1111111111111) | 0b01100000000000000)) & 0xFFFF,
            0,
            True
        )
    @staticmethod
    def rt(basic:Group,rt_text:str,segment:int,ab:bool=False) -> Group:
        """This function will generate a 2A group, which includes AB the segment and 4 characters of text

        :param basic: Basic block structure, which is generated by basic()
        :param rt_text: RT Text
        :param segment: Current segment to generate (2a is segmented into 16 4 character segments)
        :param ab: Switch this if RT changes

        Decoder status: Decoded
        """
        if segment > 15: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(2)[0] | int(ab) << 4 | segment) & 0xFFFF,
            (RDSCharset.translate(get_from_list(rt_text,segment*4+0," "))<<8 | RDSCharset.translate(get_from_list(rt_text,segment*4+1," "))) & 0xFFFF,
            (RDSCharset.translate(get_from_list(rt_text,segment*4+2," "))<<8 | RDSCharset.translate(get_from_list(rt_text,segment*4+3," "))) & 0xFFFF,
            False
        )
    @staticmethod
    def rt_b(basic:Group,rt_text:str,segment:int,ab:bool=False) -> Group:
        """This function will generate a 2B group, very similiar to 2A but this include only 2 characters of text

        :param basic: Basic block structure, which is generated by basic()
        :param rt_text: RT Text
        :param segment: Current segment to generate (2b is segmented into 16 2 character segments)
        :param ab: Switch this if RT changes

        Decoder status: Decoded
        """
        if segment > 15: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(2,True)[0] | int(ab) << 4 | segment) & 0xFFFF,
            basic.a & 0xFFFF,
            (RDSCharset.translate(get_from_list(rt_text,segment*4+0," "))<<8 | RDSCharset.translate(get_from_list(rt_text,segment*4+1," "))) & 0xFFFF,
            True
        )
    @staticmethod
    def ptyn(basic:Group, ptyn_text:str, segment:int,ab:bool=False) -> Group:
        """This function will generate a 10A group, which includes 4 characters of text

        :param basic: Basic block structure, which is generated by basic()
        :param ptyn_text: PTYN Text
        :param segment: Current segment to generate (2b is segmented into 2 2 character segments)
        :param ab: Switch this if PTYN changes

        Decoder status: Decoded
        """
        if segment > 1: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(10)[0] | (int(ab) << 4) | segment) & 0xFFFF,
            (RDSCharset.translate(get_from_list(ptyn_text,segment*4+0," "))<<8 | RDSCharset.translate(get_from_list(ptyn_text,segment*4+1," "))) & 0xFFFF,
            (RDSCharset.translate(get_from_list(ptyn_text,segment*4+2," "))<<8 | RDSCharset.translate(get_from_list(ptyn_text,segment*4+3," "))) & 0xFFFF,
            False
        )
    @staticmethod
    def ct(basic:Group, mjd: int, hour: int, minute: int, local_hour:int|None=None,local_minute:int|None=None) -> Group:
        """This function generates a 4A group

        :param basic: Basic block structure, which is generated by basic()
        :param mjd: The Modified Julian Day value, calculated with calculate_mjd
        :param hour: UTC Hour
        :param minute: UTC Minute
        :param local_hour: Local Hour
        :param local_minute: Local Minute

        Decoder status: Decoded
        """
        group = Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(4)[0] | (mjd>>15)) & 0xffff,
            ((mjd<<1) | (hour >> 4)) & 0xffff,
            ((hour & 0xF)<<12 | minute << 6) & 0xffff,
            False
        )
        if local_hour and local_minute:
            offset_h = local_hour - hour
            if offset_h < 0: group.d |= (1 << 5)
            if local_minute:
                offset_m = local_minute - minute
            else: offset_m = 0
            offset = int((offset_m + (offset_h * 60)) / 30)
            group.d |= abs(offset)
        elif local_hour:
            offset_h = local_hour - hour
            if offset_h < 0: group.d |= (1 << 5)
            offset = int((0 + (offset_h * 60)) / 30)
            group.d |= abs(offset)
        elif local_minute:
            offset_m = local_minute - minute
            if offset_m < 0: group.d |= (1 << 5)
            offset = int((offset_m + (0 * 60)) / 30)
            group.d |= abs(offset)
        return group
    @staticmethod
    def oda_aid(basic:Group, group: GroupIdentifier, aid:int, data:int=0) -> Group:
        """This function will generate a 3A group

        :param basic: Basic block structure, which is generated by basic()
        :data group: The type of group this AID is for
        :data aid: AID
        :data data: 'Message bits'

        Decoder status: Decoded
        """
        if not isinstance(group, GroupIdentifier): raise GroupGeneratorError("group should be a GroupIdentifier")
        group_type, group_ver = get_group(group.group_number,group.group_version,True)
        return Group(
            basic.a & 0xFFFF,
            (basic.b | 3 << 12 | (group_type << 1) | group_ver) & 0xFFFF,
            data & 0xFFFF,
            aid & 0xFFFF,
            False
        )
    @staticmethod
    def custom(basic:Group, group: GroupIdentifier, data:list) -> Group:
        """This function will generate a xY group

        :param basic: Basic block structure, which is generated by basic()
        :data group: The type of group this is
        :data data: Raw 16 bit data basic (first int must be 5 bits or max 31)

        Decoder status: (ODA Decoded as Raw)
        """
        if not isinstance(group, GroupIdentifier): raise GroupGeneratorError("group should be a GroupIdentifier")
        return Group(
            basic.a & 0xFFFF,
            (((basic.b | get_group(group.group_number,group.group_version)[0]) & ~0x1F) | get_from_list(data,0,0) & 0x1F) & 0xFFFF,
            ((get_from_list(data,1,0) & 0xFFFF) if group.group_version == False else (basic.a & 0xFFFF)),
            get_from_list(data,2,0) & 0xFFFF,
            group.group_version
        )
    @staticmethod
    def eon(basic:Group, on_pi: int, on_tp: bool, variant_code: int, on_ps:str="", on_af:int=0, on_pty: int=0, on_ta: bool=False, on_broadcaster_data:int=0) -> Group:
        """This function will generate a 14A group

        :param basic: Basic block structure, which is generated by basic()
        :param on_pi: The other station's pi
        :param on_tp: The other station's tp
        :param variant_code: The variant code, see http://www.interactive-radio-system.com/docs/EN50067_RDS_Standard.pdf page 38
        :param on_ps: Other station's data
        :param on_af: Other station's data
        :param on_pty: Other station's data
        :param on_ta: Other station's data
        :param on_broadcaster_data: Other station's data
        Decoder status: Decoded
        """
        group = Group(
            basic.a & 0xFFFF,
            (((basic.b | get_group(14,False)[0] | BitManipulator.set_bit(0,11,on_tp)) & ~0xF) | (variant_code & 0xF)) & 0xFFFF,
            0,
            on_pi,
            False
        )
        if variant_code <= 3:
            group.c = (RDSCharset.translate(get_from_list(on_ps,variant_code*2+0," ")) << 8) | RDSCharset.translate(get_from_list(on_ps,variant_code*2+1," "))
        match variant_code:
            case 4:
                group.c = on_af
            case 13:
                group.c = ((on_pty & 0x1f) << 11) | (int(on_ta) << 10)
            case 15:
                group.c = on_broadcaster_data
        return group
    @staticmethod
    def eon_b(basic:Group, on_pi: int, on_tp: bool, on_ta:bool) -> Group:
        """This function will generate a 14B group

        :param basic: Basic block structure, which is generated by basic()
        :param on_pi: The other station's pi
        :param on_tp: The other station's tp
        :param on_tp: The other station's ta
        Decoder status: Decoded
        """
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(14,True)[0] | BitManipulator.set_bit(0,11,on_tp) | BitManipulator.set_bit(0,12,on_ta)) & 0xFFFF,
            basic.a & 0xFFFF,
            on_pi & 0xFFFF,
            True
        )
    @staticmethod
    def long_ps(basic:Group,ps_text:str,segment:int) -> Group:
        """This function will generate a 15A group

        :param basic: Basic block structure, which is generated by basic()
        :param ps_text: PS Text

        See https://github.com/windytan/redsea/discussions/104

        Decoder status: Decoded
        """
        if segment > 7: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            (basic.b | get_group(15)[0] | segment) & 0xFFFF,
            (ord(get_from_list(ps_text,segment*4+0," "))<<8 | ord(get_from_list(ps_text,segment*4+1," "))) & 0xFFFF,
            (ord(get_from_list(ps_text,segment*4+2," "))<<8 | ord(get_from_list(ps_text,segment*4+3," "))) & 0xFFFF,
            False
        )
    @staticmethod
    def fast_switching(basic:Group, segment:int, ta:bool=False, dpty:int=1) -> Group:
        """
        This function will generate a 15B group which includes TA MS DI AF

        :param basic: Basic block structure, which is generated by basic()
        :param segment: Current segment to generate (ps is segmented into four 2 character segments)
        :param ta: Trafic Annoucement
        :param dpty: Dynamic PTY

        Decoder status: Decoded
        """
        if segment > 3: raise SegmentError("Segment limit")
        return Group(
            basic.a & 0xFFFF,
            ( basic.b | get_group(15,True)[0] | int(ta) << 4 | ( ( (dpty << 3) >> (3-segment) ) << 2) | segment ) & 0xFFFF,
            basic.a & 0xFFFF,
            ( basic.b | get_group(15,True)[0] | int(ta) << 4 | ( ( (dpty << 3) >> (3-segment) ) << 2) | segment ) & 0xFFFF,
            False
        )