# RFA.py
# This file is a module that allows a user to more easily manipulate output data from SXXI. This module contains a class declaration
#     for an RFA object, useful methods for manipulating RFA objects, a function to import records in either GMF or SFAF format,
#     functions for exporting the data in particular formats, and functions for converting SXXI values into more readable formats.
# The current version of this module should be considered an 'alpha' version as it contains minimal documentation and error handling.

import os
import re
from datetime import date
import sys
from .utils import formatLatLong, extractBandwidthFromEMS, formatFrequency, formatFrequencyBand, formatGMFDate, formatSFAFDate, formatPower, unquote, detectSuffix, convertPrefix


# An RFA object that represents a Radio Frequency Assignment from SXXI using fields derived from both GMF and SFAF format
# Some SXXI columns can be repeated to represent multiple values for a particular column which are represented as lists
#     in this object declaration. Columns that can only ever have one value are represented as strings.
class RFA:
    def __init__(self):
        self.serial_number = None
        self.agency_action_number = None
        self.classification = None
        self.bureau = None
        self.agency = None
        self.record_type = None
        self.service = None
        self.main_function_id = None
        self.intermediate_function_id = None
        self.detailed_function_id = None
        self.irac_docket_number = []
        self.docket_number_old = None
        self.sxxi_frequency = None
        self.sxxi_frequency_upper_limit = None
        self.sxxi_excluded_frequency_bands = []
        self.sxxi_paired_frequency = []
        self.paired_serial_number = []
        self.gmf_time = None
        self.irac_notes = []
        self.free_text = []
        self.misc_agency_data = []
        self.fas_agenda = []
        self.supplementary_details = ''
        self.point_of_contact = None
        self.poc_name = None
        self.poc_phone_number = None
        self.poc_verification_date = None
        self.joint_agency_names = []
        self.international_coordination_id = None
        self.canadian_coordination_comments = []
        self.mexican_coordination_comments = []
        self.user_net_code = []
        # Emission group
        self.station_class = []
        self.emission_designator = []
        self.sxxi_power = []
        # These two are not used in DoC records
        self.effective_radiated_power = []
        self.power_augmentation = []
        # Transmitter group
        self.tx_state_country_code = None
        self.tx_antenna_location = None
        self.tx_station_control = None
        self.tx_station_call_sign = None
        self.tx_antenna_latitude = None
        self.tx_antenna_longitude = None
        self.tx_authorized_radius = None
        self.tx_inclination_angle = None
        self.tx_apogee = None
        self.tx_perigee = None
        self.tx_period_of_orbit = None
        self.tx_number_of_satellites = None
        self.tx_power_density = None
        self.tx_equipment_nomenclature = []
        self.tx_system_name = None
        self.tx_number_of_stations = None
        self.tx_ots_equipment = None
        self.tx_radar_tunability = None
        self.tx_pulse_duration = []
        self.tx_pulse_repetition_rate = []
        self.tx_antenna_name = None
        self.tx_antenna_nomenclature = None
        self.tx_antenna_gain = []
        self.tx_antenna_elevation = []
        self.tx_antenna_feed_point_height = []
        self.tx_antenna_horizontal_beamwidth = []
        self.tx_antenna_azimuth = None
        self.tx_antenna_orientation = None
        self.tx_antenna_polarization = None
        self.tx_jsc_area_code = None
        # Receiver group
        self.rx_state_country_code = []
        self.rx_antenna_location = []
        self.rx_control_id_and_server_system_id = []
        self.rx_antenna_latitude = []
        self.rx_antenna_longitude = []
        self.rx_station_call_sign = []
        self.rx_authorized_radius = []
        self.rx_repeater_indicator = []
        self.rx_inclination_angle = []
        self.rx_apogee = []
        self.rx_perigee = []
        self.rx_period_of_orbit = []
        self.rx_number_of_satellites = []
        self.rx_equipment_nomenclature = []
        self.rx_antenna_name = []
        self.rx_antenna_nomenclature = []
        self.rx_antenna_gain = []
        self.rx_antenna_elevation = []
        self.rx_antenna_feed_point_height = []
        self.rx_antenna_horizontal_beamwidth = []
        self.rx_antenna_azimuth = []
        self.rx_antenna_orientation = []
        self.rx_antenna_polarization = []
        self.rx_jsc_area_code = []
        # Area authorization
        self.authorized_area_both = []
        self.rx_authorized_area = []
        self.tx_authorized_area = []
        self.excepted_states_both = []
        self.rx_excepted_states = []
        self.tx_excepted_states = []
        self.authorized_states_both = []
        self.rx_authorized_states = []
        self.tx_authorized_states = []
        # Dates
        self.last_transaction_date = None
        self.revision_date = None
        self.authorization_date = None
        self.expiration_date = None
        self.review_date = None
        self.entry_date = None
        self.receipt_date = None
        # Likely not entered into database for now
        # self.foi_exempt = None
        # self.approval_authority = None
        # self.data_source = None
        # self.routine_agenda_item = None
        # custom columns
        self.power_w = []
        self.center_frequency = None
        self.max_power = None
        self.frequency_band = None
        self.bandwidth = None
        self.tx_lat_long = None
        self.rx_lat_long = []

    # This method overrides the default str() function to print the RFA's serial number.
    def __str__(self):
        return f'<RFA: {self.serial_number}>'
    
    def __getitem__(self, item):
        return getattr(self, item)

    # This method is used to convert an RFA object into a string in .csv format.
    # Any fields that contain commas will have all commas converted into semicolons to be compatible with .csv format.
    # Any list-type fields will be joined into a single field seperated by '|' characters.
    # Any date-type fields will be output in mm/dd/yyyy format
    def toCSVRow(self):
        row = []
        for value in self.__dict__.values():
            if isinstance(value, list):
                value = '|'.join(map(str, value))
            elif isinstance(value, date):
                value = value.strftime('%m/%d/%Y')
            elif value is None:
                value = ''
            row.append(value.replace(',',';'))
        return ','.join(row)
    
    def toGMFViewerRow(self):
        return f'{self.serial_number}\t{self.classification}\t{self.sxxi_frequency}\t{self.sxxi_frequency_upper_limit}\t{self.gmf_time}\tFOIA\t{self.service}\t{self.last_transaction_date}\t{self.tx_antenna_location}\t{self.tx_state_country_code}\t{self.tx_antenna_gain}\t{self.tx_antenna_feed_point_height}\t{self.station_class}\t{self.emission_designator}\t{self.sxxi_power}\t{self.rx_antenna_gain}\t{self.rx_antenna_feed_point_height}\t{self.rx_antenna_location}\t{self.rx_state_country_code}\t{self.tx_equipment_nomenclature}\t{self.rx_equipment_nomenclature}\t{self.tx_antenna_latitude}\t{self.tx_antenna_longitude}\tTx Antenna LatDD\tTx Antenna LongDD\t{self.rx_antenna_latitude}\t{self.rx_antenna_longitude}\t{self.tx_authorized_radius}\t{self.rx_authorized_radius}\t{self.paired_serial_number}\t{self.sxxi_paired_frequency}\t{self.free_text}\t{self.irac_notes}\t{self.misc_agency_data}\t{self.supplementary_details}\t{self.agency}\t{self.main_function_id}'
    
    # def toCSVRow_formatted(self):
    #     row = []
    #     for key, value in self.__dict__.items():
    #         if key in ['frequency', 'frequency_upper_limit', 'excluded_frequency_bands', 'paired_frequency']:
    #             if isinstance(value, list):
    #                 value = '|'.join(map(formatFrequency, value))
    #             else:
    #                 value = formatFrequency(value)
    #         elif key == 'power':
    #             value = '|'.join(map(formatPower, value))
    #         elif isinstance(value, list):
    #             value = '|'.join(value)
    #         elif isinstance(value, date):
    #             value = value.strftime('%m/%d/%Y')
    #         elif value is None:
    #             value = ''
    #         row.append(value.replace(',',';'))
    #     return ','.join(row)

    # This method is the same as toCSVRow(), but it only outputs specified fields.
    def toCSVRow_NWSFormat(self):
        main_function_id = self.main_function_id.replace(',', ';')
        intermediate_function_id = self.intermediate_function_id.replace(',', ';')
        detailed_function_id = self.detailed_function_id.replace(',', ';')
        point_of_contact = self.point_of_contact.replace(',', ';')
        revision_date = self.revision_date.strftime('%m/%d/%Y')
        station_classes = '|'.join(self.station_class)
        emission_designators = '|'.join(self.emission_designator)
        powers = '|'.join(self.sxxi_power)
        tx_antenna_name = '|'.join(self.tx_antenna_name)
        tx_antenna_polarization = '|'.join(self.tx_antenna_polarization)
        tx_antenna_orientation = '|'.join(self.tx_antenna_orientation)
        return f'{self.serial_number},{self.agency_action_number},{main_function_id},{intermediate_function_id},{detailed_function_id},{self.sxxi_frequency},{point_of_contact},{revision_date},{self.tx_state_country_code},{self.tx_antenna_location},{self.tx_antenna_latitude},{self.tx_antenna_longitude},{station_classes},{emission_designators},{powers},{self.last_transaction_date},{self.record_type},{tx_antenna_name},{tx_antenna_polarization},{tx_antenna_orientation},{self.tx_station_call_sign}'
    
    def toCSVRow_TrackerFormat(self):
        point_of_contact = self.point_of_contact.replace(',', ';')
        return f'{self.serial_number},{self.agency_action_number},{self.revision_date},{point_of_contact}'


# This function expects a .txt file which was generated by SXXI using any of the SFAF 1 Column or GMF 1 Column output options
#     and returns a list of RFA objects that correspond one-to-one with records found in the file. This function converts
#     records from either format into a joined format. It should be noted that there are some columns in SFAF format that are
#     not present in GMF format, so the resulting list of RFAs may have slightly different values depending on if an SFAF
#     1 Column or GMF 1 Column file was used.
# All column tags from GMF and SFAF format should be included, but this module was developed from an unclassifed release of SXXI
#     with unclassified data, so classified fields will not be handled. Any unrecognized tags will be printed to the console
#     along with the serial number of the record in which they were found.
# NOTE: If using an SFAF 1 Column output that only contains a subset of columns and not the full record, the '005' column which
#     contains the record classification is required to detect the beginning of an new record. This will be fixed in later versions.
def importRFAsFrom1ColFile(filename):
    with open(filename, 'r') as iFile:
        lines = iFile.readlines()
    mode = 'assignment'
    if '_p_' in filename:
        mode = 'proposal'
    return importRFAs(lines, mode)

def importRFAs(lines, mode='assignment'):
    # regular expression strings
    receiver_group_tag = r'(.*),R\d{2}$'

    serial_suffix = ''
    if mode == 'proposal':
        serial_suffix = 'p'

    RFAs = []
    rfa = None
    for line_no, line in enumerate(lines, start=1):
        if line == '':
            continue
        # This is some error handling in the rare case that a record has a row with a tag but no value.
        try:
            tag, value = line.strip().split(maxsplit=1)
        except ValueError as err:
            # print(f'{type(err)}: {err}')
            tag = line.strip()
            value = ''
            # if tag != '115.':
            #     if rfa is not None and rfa.serial_number is not None:
            #         raise Exception(f'RFA with serial number {rfa.serial_number} has no value for tag {tag}') from None
            #     else:
            #         raise Exception(f'Line number {line_no} has no value for tag {tag}') from None
            # else:
            #     continue
        except Exception as err:
            if rfa is not None and rfa.serial_number is not None:
                print(f'Unexpected Error: {type(err)} on RFA with serial number {rfa.serial_number}', file=sys.stderr)
            else:
                print(f'Unexpected Error: {type(err)} on line number {line_no}', file=sys.stderr)
            raise err
        # Remove the ',R##' tags from values in receiver groups past the first. This only applies to files in SFAF format.
        if matches := re.findall(r'(.*),R\d{2}$', value):
            # print(f'{value} becomes {matches[0]}')
            value = matches[0]

        # If a value is '$', the loop will skip to the next iteration and not record the value. May need to be handled different at client request.
        if value == '$':
            continue
        try:
            # In these formats, there is no delimination between different records, so the end of a record is determined by the start
            #     of a new one. In GMF format, the first tag of a record is 'SER' which is the serial number, but in SFAF format, the
            #     first tag is '005' which is the security classification.
            if tag == 'SER01' or tag == '005.': #SFAF uses 005 for FOI and CDD in addition to CLA. May need to be handled differently
                if rfa != None:
                    addPowerWAndMaxPower(rfa)
                    addBandwidthAndFrequencyBand(rfa)
                    addLatLong(rfa)
                    RFAs.append(rfa)
                rfa = RFA()
                if tag == 'SER01':
                    rfa.serial_number = value + serial_suffix
                else:
                    rfa.classification = value
            elif tag == '102.':
                # print(value)
                rfa.serial_number = value + serial_suffix
            elif tag == 'TYP01' or tag == '010.':
                rfa.record_type = value
            elif tag == 'DAT01' or tag == '911.':
                rfa.last_transaction_date = formatGMFDate(value) if tag == 'DAT01' else formatSFAFDate(value)
            elif tag == 'CLA01': 
                rfa.classification = value
            elif tag == 'FOI01':
                # rfa.foi_exempt = value
                continue
            elif tag == 'ACN01' or tag == '956.':
                rfa.agency_action_number = value
            elif tag == 'EXD01' or tag == '141.':
                rfa.expiration_date = formatGMFDate(value) if tag == 'EXD01' else formatSFAFDate(value)
            elif tag == '142.':
                rfa.review_date = formatSFAFDate(value)
            elif tag == '144.':
                # rfa.approval_authority = value
                continue
            elif tag == 'BUR01' or tag == '203.':
                rfa.bureau = value
            elif tag == '200.':
                rfa.agency = value
            elif tag[:3] == 'NET' or tag[:3] == '208':
                rfa.user_net_code.append(value)
            elif tag == 'FRQ01':
                rfa.sxxi_frequency = value
            elif tag == '110.':
                if '-' in value:
                    unit = value[0]
                    lower, upper = value[1:].split('-')
                    rfa.sxxi_frequency = unit + lower
                    rfa.sxxi_frequency_upper_limit = unit + upper
                else:
                    rfa.sxxi_frequency = value
            elif tag[:3] == 'PRD' or tag[:3] == '506':
                rfa.sxxi_paired_frequency.append(value)
            elif tag == 'FRU01':
                rfa.sxxi_frequency_upper_limit = value
            elif tag[:3] == 'FBE' or tag[:3] == '111':
                rfa.sxxi_excluded_frequency_bands.append(value)
            elif tag[:3] == 'STC' or tag[:3] == '113':
                rfa.station_class.append(value)
            elif tag[:3] == 'EMS' or tag[:3] == '114':
                if value == '':
                    continue
                rfa.emission_designator.append(value)
            elif tag[:3] == 'PWR' or tag[:3] == '115':
                rfa.sxxi_power.append(value)
            elif tag[:3] == '117':
                rfa.effective_radiated_power.append(value)
            elif tag[:3] == '118':
                rfa.power_augmentation.append(value)
            elif tag == 'TME01' or tag == '130.':
                rfa.gmf_time = value
            elif tag == 'XSC01' or tag == '300.':
                rfa.tx_state_country_code = value
            elif tag == 'XAL01' or tag == '301.':
                rfa.tx_antenna_location = value
            elif tag == 'XLA01':
                rfa.tx_antenna_latitude = value
            elif tag == 'XLG01':
                rfa.tx_antenna_longitude = value
            elif tag == 'XRD01' or tag == '306.':
                rfa.tx_authorized_radius = value
            elif tag[:3] == 'ARB':
                rfa.authorized_area_both.append(value)
            elif tag == 'XAR01':
                rfa.tx_authorized_area.append(value)
            elif tag[:3] == '530':
                if value[:3] == 'ART':
                    rfa.tx_authorized_area.append(value[4:])
                elif value[:3] == 'ARR':
                    rfa.rx_authorized_area.append(value[4:])
                elif value[:3] == 'ARB':
                    rfa.authorized_area_both.append(value[4:])
                else:
                    print(f'Unknown tag in SFAF 530 of record{rfa.serial_number}')
            elif tag[:3] == 'EQS' or tag[:3] == '344':
                rfa.tx_ots_equipment = value
            elif tag == '303.':
                rfa.tx_antenna_latitude = value[:7] #double check if char count is sufficient condition
                rfa.tx_antenna_longitude = value[7:]
            elif tag[:3] == 'XSE' or tag[:3] == '358':
                rfa.tx_antenna_elevation.append(value)
            elif tag[:3] == 'XAH' or tag[:3] == '359':
                rfa.tx_antenna_feed_point_height.append(value)
            elif tag == 'XRC01' or tag == '302.':
                rfa.tx_station_control = value
            elif tag == 'XCL01' or tag == '304.':
                rfa.tx_station_call_sign = value
            elif tag[:3] == 'XAG' or tag[:3] == '357':
                rfa.tx_antenna_gain.append(value)
            elif tag[:3] == 'XAT' or tag[:3] == '354':
                rfa.tx_antenna_name = value
            elif tag[:3] == 'XAK' or tag[:3] == '355':
                rfa.tx_antenna_nomenclature = value
            elif tag[:3] == 'XAZ':
                rfa.tx_antenna_orientation = value
            elif tag[:3] == '362': #Will need additional processing of tag 362
                if ',' in value:
                    rfa.tx_antenna_orientation, rfa.tx_antenna_azimuth = value.split(',')
                else:
                    rfa.tx_antenna_orientation = value
            elif tag[:3] == 'XAA':
                rfa.tx_antenna_azimuth = value
            elif tag[:3] == 'XAP' or tag[:3] == '363':
                rfa.tx_antenna_polarization = value
            elif tag == 'TUN01' or tag == '345.':
                rfa.tx_radar_tunability = value
            elif tag[:3] == 'PDD' or tag[:3] == '346':
                rfa.tx_pulse_duration.append(value)
            elif tag[:3] == 'PRR' or tag[:3] == '347':
                rfa.tx_pulse_repetition_rate.append(value)
            elif tag[:3] == 'XEQ' or tag[:3] == '340':
                rfa.tx_equipment_nomenclature.append(value)
            elif tag[:3] == 'NTT':
                rfa.tx_number_of_stations = value
            elif tag[:3] == 'NAM':
                rfa.tx_system_name = value
            elif tag[:3] == '341':
                rfa.tx_number_of_stations, rfa.tx_system_name = value.split(',')
                if rfa.tx_number_of_stations[0] == 'X':
                    rfa.tx_number_of_stations = None
            elif tag[:3] == '373':
                rfa.tx_jsc_area_code == value
            elif tag[:3] == 'RSC' or tag[:3] == '400':
                rfa.rx_state_country_code.append(value)
            elif tag[:3] == 'RAL' or tag[:3] == '401':
                rfa.rx_antenna_location.append(value)
            elif tag[:3] == 'RLA':
                rfa.rx_antenna_latitude.append(value)
            elif tag[:3] == 'RLG':
                rfa.rx_antenna_longitude.append(value)
            elif tag[:3] == '403':
                rfa.rx_antenna_latitude.append(value[:7])
                rfa.rx_antenna_longitude.append(value[7:])
            elif tag[:3] == 'RSE' or tag[:3] == '458':
                rfa.rx_antenna_elevation.append(value)
            elif tag[:3] == 'RRD' or tag[:3] == '406':
                rfa.rx_authorized_radius.append(value)
            elif tag[:3] == 'RRC' or tag[:3] == '402':
                rfa.rx_control_id_and_server_system_id.append(value)
            elif tag[:3] == 'RCL' or tag[:3] == '404':
                rfa.rx_station_call_sign.append(value)
            elif tag[:3] == 'RAG' or tag[:3] == '457':
                rfa.rx_antenna_gain.append(value)
            elif tag[:3] == 'RAT' or tag[:3] == '454':
                rfa.rx_antenna_name.append(value)
            elif tag[:3] == 'RAK' or tag[:3] == '455':
                rfa.rx_antenna_nomenclature.append(value)
            elif tag[:3] == 'RAH' or tag[:3] == '459':
                rfa.rx_antenna_feed_point_height.append(value)
            elif tag[:3] == 'RAZ' or tag[:3] == '462':
                rfa.rx_antenna_orientation.append(value)
            elif tag[:3] == 'RAA':
                rfa.rx_antenna_azimuth.append(value)
            elif tag[:3] == 'RAZ':
                rfa.rx_antenna_orientation.append(value)
            elif tag[:3] == '462':
                if value.find(',') == -1:
                    rfa.rx_antenna_orientation.append(value)
                else:
                    rfa.rx_antenna_orientation.append(value[:value.find(',')])
            elif tag[:3] == 'RAP' or tag[:3] == '463':
                rfa.rx_antenna_polarization.append(value)
            elif tag[:3] == 'RPT' or tag[:3] == '408':
                rfa.rx_repeater_indicator.append(value)
            elif tag[:3] == 'REQ' or tag[:3] == '440':
                rfa.rx_equipment_nomenclature.append(value)
            elif tag[:3] == '473':
                rfa.rx_jsc_area_code.append(value[0])
            elif tag == 'SPD01' or tag == '321.':
                rfa.tx_power_density = value
            elif tag[:3] == 'XBW' or tag[:3] == '360':
                rfa.tx_antenna_horizontal_beamwidth.append(value)
            elif tag == 'XIN01' or tag == '315.':
                rfa.tx_inclination_angle = value
            elif tag == 'XAE01' or tag == '316.':
                rfa.tx_apogee = value
            elif tag == 'XPE01' or tag == '317.':
                rfa.tx_perigee = value
            elif tag == 'XPD01' or tag == '318.':
                rfa.tx_period_of_orbit = value
            elif tag == 'XNR01' or tag == '319.':
                rfa.tx_number_of_satellites = value
            elif tag[:3] == 'RBW' or tag[:3] == '460':
                rfa.rx_antenna_horizontal_beamwidth.append(value)
            elif tag[:3] == 'RIN' or tag[:3] == '415':
                rfa.rx_inclination_angle.append(value)
            elif tag[:3] == 'RAE' or tag[:3] == '416':
                rfa.rx_apogee.append(value)
            elif tag[:3] == 'RPE' or tag[:3] == '417':
                rfa.rx_perigee.append(value)
            elif tag[:3] == 'RPD' or tag[:3] == '418':
                rfa.rx_period_of_orbit.append(value)
            elif tag[:3] == 'RNR' or tag[:3] == '419':
                rfa.rx_number_of_satellites.append(value)
            elif tag[:3] == 'JNT' or tag[:3] == '147':
                rfa.joint_agency_names.append(value)
            elif tag == 'MFI01' or tag == '511.':
                rfa.main_function_id = value
            elif tag == 'IFI01' or tag == '512.':
                rfa.intermediate_function_id = value
            elif tag[:3] == 'DFI' or tag[:3] == '513':
                rfa.detailed_function_id = value
            elif tag[:3] == 'NTS' or tag[:3] == '500':
                rfa.irac_notes.append(value)
            elif tag[:3] == '117':
                rfa.effective_radiated_power.append(value)
            elif tag == 'ICI01' or tag == '151.':
                rfa.international_coordination_id = value
            elif tag[:3] == 'CAN':
                rfa.canadian_coordination_comments.append(value)
            elif tag[:3] == 'MEX':
                rfa.mexican_coordination_comments.append(value)
            elif tag[:3] == '152':
                if value[0] == 'C':
                    rfa.canadian_coordination_comments.append(value[2:])
                elif value[0] == 'M':
                    rfa.mexican_coordination_comments.append(value[2:])
                else:
                    print(f'Unrecognized format for SFAF 152 in record {rfa.serial_number}')
            elif tag[:3] == 'NOT' or tag[:3] == '501':
                rfa.free_text.append(value)
            elif tag == 'DOC01' or tag == '108.':
                rfa.docket_number_old = value
            elif tag == 'POC01' or tag == '803.':
                rfa.point_of_contact = value
                rfa.poc_name, rfa.poc_phone_number, rfa.poc_verification_date = rfa.point_of_contact.split(',')
                rfa.poc_verification_date = formatGMFDate(rfa.poc_verification_date)
            elif tag[:3] == 'AGN' or tag[:3] == '503':
                rfa.misc_agency_data.append(value)
            elif tag[:3] == 'FAS' or tag[:3] == '504':
                rfa.fas_agenda.append(value)
            elif tag == 'RTN01' or tag == '958.':
                # rfa.routine_agenda_item = value
                continue
            elif tag[:3] == 'SUP' or tag[:3] == '520':
                if rfa.supplementary_details != '':
                    rfa.supplementary_details += f' {value}'
                else:
                    rfa.supplementary_details = value
            elif tag[:3] == 'AUS' or tag[:3] == '103':
                rfa.irac_docket_number.append(value)
            elif tag == 'AUD01' or tag == '107.':
                rfa.authorization_date = formatGMFDate(value) if tag == 'AUD01' else formatSFAFDate(value)
            elif tag == 'RVD01' or tag == '143.':
                rfa.revision_date = formatGMFDate(value) if tag == 'RVD01' else formatSFAFDate(value)
            elif tag[:3] == 'AST' or tag[:3] == '531':
                if value[:3] == 'ESB':
                    rfa.excepted_states_both.append(value[4:])
                elif value[:3] == 'ESR':
                    rfa.rx_excepted_states.append(value[4:])
                elif value[:3] == 'EST':
                    rfa.tx_excepted_states.append(value[4:])
                elif value[:3] == 'LSB':
                    rfa.authorized_states_both.append(value[4:])
                elif value[:3] == 'LSR':
                    rfa.rx_authorized_states.append(value[4:])
                elif value[:3] == 'LST':
                    rfa.tx_authorized_states.append(value[4:])
                else:
                    print(f'Unrecognized format for SFAF 531 or GMF AST in record {rfa.serial_number}')
            elif tag == '924.':
                # rfa.data_source = value
                continue
            elif tag == '927.':
                rfa.entry_date = formatSFAFDate(value)
            elif tag == '928.':
                rfa.receipt_date = formatSFAFDate(value)
            else:
                print(f'Unknown Tag {tag} at line {line_no}')
        except Exception as error:
            if rfa is not None and rfa.serial_number is not None:
                print(f'Unexpected Error: {type(error)} on RFA with serial number {rfa.serial_number}', file=sys.stderr)
            else:
                print(f'Unexpected Error: {type(error)} in line {line_no}', file=sys.stderr)
            raise error
    addPowerWAndMaxPower(rfa)
    addBandwidthAndFrequencyBand(rfa)
    addLatLong(rfa)
    RFAs.append(rfa)
    return RFAs


def importRFAsFromGMFViewerFile(filename):
    with open(filename, 'r') as iFile:
        lines = iFile.readlines()
    mode = 'assignment'
    if '_p_' in filename:
        mode = 'proposal'
    return importRFAsFromGMFViewer(lines, mode)

def importRFAsFromGMFViewer(lines, mode='assignment'):
    if mode == 'proposal':
        serial_suffix = 'p'

    RFAs = []
    for line in lines:
        rfa = RFA()
        try:
            temp = line.strip().split('\t')
            rfa.serial_number = temp[0] #Applicant Serial Number
            rfa.classification = unquote(temp[1]) #Classification
            rfa.sxxi_frequency = f'M{temp[2]}' #Assigned Frequency MHz
            rfa.sxxi_frequency_upper_limit = None if temp[3] == '0' else f'M{temp[3]}' #Frequency Band Upper Limit MHz
            rfa.gmf_time = None if temp[4] == "" else unquote(temp[4]) #Time
            #rfa.foi_exempt = unquote(temp[5]) #Exemption from Freedom Of Information Act
            rfa.service = unquote(temp[6]) #Service
            rfa.last_transaction_date = formatGMFDate(temp[7]) if unquote(temp[7]) != '' else None #Date of Last Transaction
            rfa.tx_antenna_location = unquote(temp[8]) #Tx Antenna Location
            rfa.tx_state_country_code = unquote(temp[9]) #Tx State/Country
            for gain in unquote(temp[10]).split(','):
                if gain != 'NaN' and gain != '':
                    rfa.tx_antenna_gain.append(int(gain)) #Tx Antenna Gain Needs to handle NaN
            for height in unquote(temp[11]).split(','):
                if height != 'N/A' and height != '':
                    suffix = detectSuffix(height)
                    if suffix != None:
                        height = convertPrefix(height[0:-1], suffix if suffix != 'K' else 'k', 'N')
                    rfa.tx_antenna_feed_point_height.append(float(height)) # Tx Antenna Feed Point Height Needs to handle N/A and '' and possible units on height
            rfa.station_class = unquote(temp[12]).split(',') #Station Class
            rfa.emission_designator = unquote(temp[13]).split(',') #Emission Designator
            for power in unquote(temp[14]).split(','):
                if power != 'N/A':
                    rfa.sxxi_power.append(power)  #Power
            for gain in unquote(temp[15]).split(','):
                if gain != 'NaN' and gain != '':
                    rfa.rx_antenna_gain.append(int(gain)) #Rx Antenna Gain Needs to handle NaN and ''
            for height in unquote(temp[16]).split(','):
                # rfa.rx_antenna_feed_point_height.append(None if height == 'N/A' else height) #Rx Antenna Feed Point Height Needs to handle N/A and possible units on height
                if height != 'N/A':
                    suffix = detectSuffix(height)
                    if suffix != None:
                        height = convertPrefix(height[0:-1], suffix if suffix != 'K' else 'k', 'N')
                    rfa.rx_antenna_feed_point_height.append(float(height)) #Rx Antenna Feed Point Height Needs to handle N/A and possible units on height
            rfa.rx_antenna_location = unquote(temp[17]).split(',') #Rx Antenna Location
            rfa.rx_state_country_code = unquote(temp[18]).split(',') #Rx State/Country
            splitTxNomenclature = unquote(temp[19]).split(',') #First split by commas
            groupedTxNomenclature = map(','.join, zip(splitTxNomenclature[::2], splitTxNomenclature[1::2])) #Reassemble a list by grouping elements in pairs to effectively split original string by every other comma
            for nomen in groupedTxNomenclature:
                if nomen != 'N/A' and nomen != 'N/A,N/A':
                    rfa.tx_equipment_nomenclature.append(nomen) #Tx Equipment Nomenclature Needs to handle N/A This captures the leading character (C, G, or U) which indicates type of equipment and the following nomenclature
            splitRxNomenclature = unquote(temp[20]).split(',') #Process is same as above for Tx Equipment Nomenclature
            groupedRxNomenclature = map(','.join, zip(splitRxNomenclature[::2], splitRxNomenclature[1::2]))
            for nomen in groupedRxNomenclature:
                if nomen != 'N/A' and nomen != 'N/A,N/A':
                    rfa.rx_equipment_nomenclature.append(nomen) #Rx Equipment Nomenclature Needs to handle N/A
            rfa.tx_antenna_latitude = unquote(temp[21]) if unquote(temp[21]) != '' else None #Tx Antenna Latitude Needs to handle ''
            rfa.tx_antenna_longitude = unquote(temp[22]) if unquote(temp[22]) != '' else None #Tx Antenna Longitude Needs to handle ''
            #rfa.tx_antenna_latitude_dd = temp[23] #Tx Antenna Latitude Decimal Degrees
            #rfa.tx_antenna_longitude_dd = temp[24] #Tx Antenna Longitude Decimal Degrees
            for lat in unquote(temp[25]).split(','):
                if lat != '' and lat != 'N/A':
                    rfa.rx_antenna_latitude.append(lat) #Rx Antenna Latitude Needs to handle ''
            for long in unquote(temp[26]).split(','):
                if long != '' and long !='N/A':
                    rfa.rx_antenna_longitude.append(long) #Rx Antenna Longitude Needs to handle ''
            rfa.tx_authorized_radius = None if temp[27] == '' else unquote(temp[27]) #TxRad Needs to handle ''
            for radius in unquote(temp[28]).split(','):
                if radius != '':
                    rfa.rx_authorized_radius.append(radius) #RxRad Needs to handle ''
            for serial in unquote(temp[29]).split(','):
                if serial != '':
                    rfa.paired_serial_number.append(serial) #Paired With Serial Number Needs to handle ''
            for frequency in unquote(temp[30]).split(','):
                if frequency != '':
                    rfa.sxxi_paired_frequency.append(frequency) #Paired With Frequency Needs to handle ''
            # splitFreeText = unquote(temp[31]).split(',')
            # groupedFreeText = map(','.join, zip(splitFreeText[::2], splitFreeText[1::2]))
            # for text in groupedFreeText:
            #     if text != '':
            #         rfa.free_text.append(text) #IRAC or FAS Minutes Notes Needs to handle '' Will need to work on this further as some notes are note well handled by assuming a grouping of 2
            for text in unquote(temp[31]).split(','):
                if text != '':
                    rfa.free_text.append(text) #IRAC or FAS Minutes Notes Needs to handle ''
            for note in unquote(temp[32]).split(','):
                if note != '':
                    rfa.irac_notes.append(note) #Record Notes
            for data in unquote(temp[33]).split(','):
                if data != '':
                    rfa.misc_agency_data.append(data) #Miscellaneous Agency Data Needs to handle ''
            rfa.supplementary_details = unquote(temp[34]) #Supplementary Details
            rfa.agency = unquote(temp[35]) #Agency
            rfa.main_function_id = unquote(temp[36]) #Major Function Identifier
            addPowerWAndMaxPower(rfa)
            addBandwidthAndFrequencyBand(rfa)
            addLatLong(rfa)
            RFAs.append(rfa)
        except Exception as error:
            print(f'Unexpected Error: {type(error)} on RFA with serial number {rfa.serial_number}', file=sys.stderr)
            raise error
    return RFAs

# This function takes a list of RFAs and a string that represents the name of a .csv file, and converts each RFA
#     into .csv format, and exports them into a new file where each record is on its own line. The first line of
#     the file is a header.
# This function includes file creation and writing which will require additional error handling to ensure proper
#     resource handling and protection. This will be included in future versions.
def exportRFAsToCSV(RFAs, filename='output.csv'):
    if os.path.isdir('./outputs/'):
        filename = './outputs/' + filename
    with open(filename, 'w') as oFile:
        headers = ','.join(RFAs[0].__dict__.keys())
        oFile.write(f'{headers}\n')
        for rfa in RFAs:
            oFile.write(f'{rfa.toCSVRow()}\n')


def exportRFAsToCSV_formatted(RFAs, filename='output.csv'):
    if os.path.isdir('./outputs/'):
        filename = './outputs/' + filename
    with open(filename, 'w') as oFile:
        headers = ','.join(RFAs[0].__dict__.keys())
        oFile.write(f'{headers}\n')
        for rfa in RFAs:
            oFile.write(f'{rfa.toCSVRow_formatted()}\n')


# This function is the same as exportRFAsToCSV(), but uses the NWS format.
def exportRFAsToCSV_NWSFormat(RFAs, filename='output.csv'):
    if os.path.isdir('./outputs/'):
        filename = './outputs/' + filename
    with open(filename, 'w') as oFile:
        oFile.write('Serial Number,Action Number,Main Function Identifier,Intermediate Function Identifier,Detailed Function Identifier,Frequency,Point of Contact,Revision Date,Transmitter State/Country Code,Transmitter Antenna Location,Transmitter Latitude,Transmitter Longitude,Station Class(es),Emission Designator(s),Power(s),Last Transaction Date,Type of Action,Transmitter Antenna Name,Transmitter Antenna Polarization,Transmitter Antenna Orientation,Station Call Sign\n')
        for rfa in RFAs:
            oFile.write(f'{rfa.toCSVRow_NWSFormat()}\n')


def exportRFAsToCSV_TrackerFormat(RFAs, filename='output.csv'):
    if os.path.isdir('./outputs/'):
        filename = './outputs/' + filename
    with open(filename, 'w') as oFile:
        oFile.write('Serial Number,Action Number,Revision Date,Point of Contact\n')
        for rfa in RFAs:
            oFile.write(f'{rfa.toCSVRow_TrackerFormat()}\n')

def addPowerWAndMaxPower(rfa):
    for power in rfa.sxxi_power:
        rfa.power_w.append(formatPower(power))

    max_power = 0
    for power in rfa.power_w:
        power = float(power)
        if power > max_power:
            max_power = power
    rfa.max_power = str(max_power)

def addBandwidthAndFrequencyBand(rfa):
    bandwidth = 0
    for emission_designator in rfa.emission_designator:
        band = extractBandwidthFromEMS(emission_designator)
        if band is not None and band > bandwidth:
            bandwidth = band
    rfa.bandwidth = str(bandwidth)

    if rfa.sxxi_frequency_upper_limit is not None:
        lower_limit = float(formatFrequency(rfa.sxxi_frequency))
        upper_limit = float(formatFrequency(rfa.sxxi_frequency_upper_limit))
        rfa.frequency_band = '['+str(lower_limit) + ',' + str(upper_limit)+']'
        rfa.center_frequency = str(lower_limit + ((upper_limit - lower_limit) / 2))
    else:
        rfa.center_frequency = formatFrequency(rfa.sxxi_frequency)
        if bandwidth != 0:
            rfa.frequency_band = '['+str(float(rfa.center_frequency) - (bandwidth / 2)) + ',' + str(float(rfa.center_frequency) + (bandwidth / 2))+']'

def addLatLong(rfa):
    if rfa.tx_antenna_latitude is not None and rfa.tx_antenna_longitude is not None:
        rfa.tx_lat_long = formatLatLong(rfa.tx_antenna_latitude, rfa.tx_antenna_longitude)

    for rx_lat, rx_long in zip(rfa.rx_antenna_latitude, rfa.rx_antenna_longitude):
        # print(rx_lat, rx_long)
        rfa.rx_lat_long.append(formatLatLong(rx_lat, rx_long))