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

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


class GroupGenerator:
    def basic(pi:int, tp:bool=False, pty:int=0):
        """
        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), see Standart EN50067 page 70 (first byte, so 3 in 0x3555), also see page 72 for coverage code
        :param tp: Traffic Program
        :param pty: Program Type, see EN50067 page 77-80
        """
        return Group(
            pi & 0xFFFF, #A
            (int(tp) << 10 | pty << 5) & 0xFFFF, #B
            0,0, #C
            None #is_b
        )
    def ps(blocks:Group, ps_text:str, segment:int, ms:bool=True, ta:bool=False, di:int=1, block2:int=0):
        """
        This function will generate a 0A group which includes TA MS DI AF and 2 characters of text
        
        :param blocks: 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 ms: Music/Speech (music is True)
        :param ta: Trafic Annoucement
        :param di: Decoder Identificaton
        :param block2: Block C, you can use this to send AF

        Decoder status: Decoded
        """
        if segment > 3: raise SegmentError("Segment limit")
        return Group(
            blocks.a & 0xFFFF,
            ( blocks.b | int(ta) << 4 | int(ms) << 3 | ( ( di >> (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
        )
    def ps_b(blocks:Group,ps_text:str,segment:int,ms:bool=True,ta:bool=False,di:int=1):
        """This function will generate a 0B group which is very similiar to a 0A group but no AF
        
        :param blocks: 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 ms: Music/Speech (music is True)
        :param ta: Trafic Annoucement
        :param di: Decoder Identificaton

        Decoder status: Decoded
        """
        if segment > 3: raise SegmentError("Segment limit")
        return Group(
            blocks.a & 0xFFFF,
            ( blocks.b | get_group(0,True) | int(ta) << 4 | int(ms) << 3 | ( ( di >> (3-segment) ) << 2) | segment ) & 0xFFFF,
            blocks.a & 0xFFFF,
            (RDSCharset.translate(get_from_list(ps_text,segment*2," "))<<8 | RDSCharset.translate(get_from_list(ps_text,segment*2+1," "))) & 0xFFFF,
            True
        )
    def ecc(blocks:Group, ecc: int):
        """This function will generate a 1A group, with the ECC value
        
        :param blocks: Basic block structure, which is generated by basic()
        :param ecc: The ECC code itself, see EN50067 page 71

        Decoder status: Decoded
        """
        return Group(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(1)) & 0xFFFF,
            (blocks.c | ecc) & 0xFFFF,
            blocks.d & 0xFFFF,
            False
        )
    def lic(blocks:Group, lic: int):
        """This function will generate a 1A group, very similiar to ECC
        
        :param blocks: Basic block structure, which is generated by basic()
        :param lic: The LIC code itself, see EN50067 page 84-85

        Decoder status: Decoded
        """
        return Group(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(1)) & 0xFFFF,
            (blocks.c | (lic | 0x3000)) & 0xFFFF,
            blocks.d & 0xFFFF,
            False
        )
    def rt(blocks:Group,rt_text:str,segment:int,ab:bool=False):
        """This function will generate a 2A group, which includes AB the segment and 4 characters of text
        
        :param blocks: 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(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(2) | 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
        )
    def rt_b(blocks:Group,rt_text:str,segment:int,ab:bool=False):
        """This function will generate a 2B group, very similiar to 2A but this include only 2 characters of text
        
        :param blocks: 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(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(2,True) | int(ab) << 4 | segment) & 0xFFFF,
            blocks.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
        )
    def ptyn(blocks:Group, ptyn_text:str, segment:int,ab:bool=False):
        """This function will generate a 10A group, which includes 4 characters of text
        
        :param blocks: 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(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(10) | (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
        )
    def ct(basic:Group, mjd: int, hour: int, minute: int, local_hour:int=None,local_minute:int=None):
        """This function generates a 4A group
        
        :param blocks: 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) | (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
    def tdc(blocks:Group, channel:int,data:list[int],segment:int):
        """This function will generate a 5A group
        
        :param blocks: Basic block structure, which is generated by basic()
        :param channnel: TDC channel< this should stay static, shouldn't be 31 ot more (32 but it starts from 0 so 31)
        :param data: TDC Data, should be a list of ints
        :param segment: Segment of the TDC message

        Decoder status: Decoded
        """
        if channel > 31: raise GroupGeneratorError("Channel bigger than 31")
        return Group(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(5) | (channel & 0x1F)) & 0xFFFF,
            (get_from_list(data,segment+0,0)<<8 | get_from_list(data,segment+1,0)) & 0xFFFF,
            (get_from_list(data,segment+2,0)<<8 | get_from_list(data,segment+3,0)) & 0xFFFF,
            False
        )
    def tdc_b(blocks:Group, channel:int,data:list[int],segment:int):
        """This function will generate a 5B group
        
        :param blocks: Basic block structure, which is generated by basic()
        :param channnel: TDC channel< this should stay static, shouldn't be 31 ot more (32 but it starts from 0 so 31)
        :param data: TDC Data, should be a list of ints
        :param segment: Segment of the TDC message

        Decoder status: Decoded
        """
        if channel > 31: raise GroupGeneratorError("Channel bigger than 31")
        return Group(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(5,True) | (channel & 0x1F)) & 0xFFFF,
            blocks.a & 0xFFFF,
            (get_from_list(data,segment+0,0)<<8 | get_from_list(data,segment+1,0)) & 0xFFFF,
            True
        )
    def in_house(blocks:Group, data:list[int],segment:int=0):
        """This function will generate a 6A group
        
        :param blocks: Basic block structure, which is generated by basic()
        :data data: 3 byte long list of ints, but the 1st byte is not guarteed to be decoded so maybe but some unnessacy info here

        Decoder status: Decoded
        """
        return Group(
            blocks.a & 0xFFFF,
            (((blocks.b | get_group(6)) & ~0x1F) | (get_from_list(data,segment+0,0) & 0x1F)) & 0xFFFF,
            get_from_list(data,segment+1,0) & 0xFFFF,
            get_from_list(data,segment+2,0) & 0xFFFF,
            False
        )
    def in_house_b(blocks:Group, data:list[int],segment:int=0):
        """This function will generate a 6B group
        
        :param blocks: Basic block structure, which is generated by basic()
        :data data: 2 byte long list of ints

        Decoder status: Decoded
        """
        return Group(
            blocks.a & 0xFFFF,
            (((blocks.b | get_group(6,True)) & ~0x1F) | (get_from_list(data,segment+0,0) & 0x1F)) & 0xFFFF,
            blocks.a & 0xFFFF,
            get_from_list(data,segment+1,0) & 0xFFFF,
            True
        )
    def oda_aid(blocks:Group, group: GroupIdentifier, aid:int, scb:int=0):
        """This function will generate a 3A group
        
        :param blocks: Basic block structure, which is generated by basic()
        :data group: The type of group this AID is for
        :data aid: AID
        :data scb: '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(
            blocks.a & 0xFFFF,
            (blocks.b | 3 << 12 | (group_type << 1) | group_ver) & 0xFFFF,
            scb & 0xFFFF,
            aid & 0xFFFF,
            False
        )
    def oda(blocks:Group, group: GroupIdentifier, data:list[int,int,int]):
        """This function will generate a xY group
        
        :param blocks: Basic block structure, which is generated by basic()
        :data group: The type of group this ODA is
        :data data: Raw 16 bit data blocks (first is 5 bits)

        Decoder status: Won't decode
        """
        if not isinstance(group, GroupIdentifier): raise GroupGeneratorError("group should be a GroupIdentifier")
        return Group(
            blocks.a & 0xFFFF,
            (((blocks.b | get_group(group.group_number,group.group_version)) & ~0x1F) | get_from_list(data,0,0) & 0x1F) & 0xFFFF,
            get_from_list(data,1,0) & 0xFFFF,
            get_from_list(data,2,0) & 0xFFFF,
            False
        )
    def eon(blocks: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):
        """This function will generate a 14A group (UNTESTED)
        
        :param blocks: 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
        Decoder status: Decoded
        """
        group = Group(
            blocks.a & 0xFFFF,
            (((blocks.b | get_group(14,False) | 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_tp) << 10)
        return group
    def eon_b(blocks:Group, on_pi: int, on_tp: bool, on_ta:bool):
        """This function will generate a 14B group (UNTESTED)
        
        :param blocks: 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(
            blocks.a & 0xFFFF,
            (blocks.b | get_group(14,True) | BitManipulator.set_bit(0,11,on_tp) | BitManipulator.set_bit(0,12,on_ta)) & 0xFFFF,
            blocks.a & 0xFFFF,
            on_pi & 0xFFFF,
            True
        )