import json
import requests
from enum import Enum

class DataType(str, Enum):
    iq = 'iq'
    spectrogram = 'spectrogram'

class Format(str, Enum):
    raw_float32 = 'raw_float32'
    raw_float64 = 'raw_float64'
    wav = 'wav'

class Signal(str, Enum):
    tone = 'tone'
    psk = 'psk'
    iridium = 'iridium'
    ook = 'ook'
    ask = 'ask'
    fsk = 'fsk'
    dpsk = 'dpsk'
    qam = 'qam'
    ofdm = 'ofdm'

class DataGenerator(str, Enum):
    random = 'random'
    specify_bits = 'specify_bits'
    file = 'file'

class _NbTone:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, duration=0.1, phase=0):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.duration = duration
        self.phase = phase

    def to_dict(self):
        return {
            'signal': 'tone',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'duration': self.duration,
            'phase': self.phase,
        }

class _NbPSK:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=4, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'psk',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbIridium:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, slot_index=0, sample_rate=100000, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, duration=0.1, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.slot_index = slot_index
        self.sample_rate = sample_rate
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.duration = duration
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'iridium',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'slot_index': self.slot_index,
            'sample_rate': self.sample_rate,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'duration': self.duration,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbOOK:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ook',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbASK:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=2, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ask',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbFSK:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, modulation_order=4, baud=4800, samples_per_symbol=8, freq_sep=1000, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.modulation_order = modulation_order
        self.baud = baud
        self.samples_per_symbol = samples_per_symbol
        self.freq_sep = freq_sep
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'fsk',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'samples_per_symbol': self.samples_per_symbol,
            'freq_sep': self.freq_sep,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbDpsk:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'dpsk',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbQAM:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'qam',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _NbOfdm:
    def __init__(self, pre_padding=0, post_padding=0, power=-2, center_frequency=0, num_subcarriers=64, fft_size=64, cyclic_prefix_length=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.pre_padding = pre_padding
        self.post_padding = post_padding
        self.power = power
        self.center_frequency = center_frequency
        self.num_subcarriers = num_subcarriers
        self.fft_size = fft_size
        self.cyclic_prefix_length = cyclic_prefix_length
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ofdm',
            'pre-padding': self.pre_padding,
            'post-padding': self.post_padding,
            'power': self.power,
            'center_frequency': self.center_frequency,
            'num_subcarriers': self.num_subcarriers,
            'fft_size': self.fft_size,
            'cyclic_prefix_length': self.cyclic_prefix_length,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbTone:
    def __init__(self, power=-2, center_frequency=0, signal_start_time=0, duration=0.1, phase=0):
        self.power = power
        self.center_frequency = center_frequency
        self.signal_start_time = signal_start_time
        self.duration = duration
        self.phase = phase

    def to_dict(self):
        return {
            'signal': 'tone',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'signal_start_time': self.signal_start_time,
            'duration': self.duration,
            'phase': self.phase,
        }

class _WbPSK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, modulation_order=4, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'psk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbIridium:
    def __init__(self, power=-2, center_frequency=0, slot_index=0, sample_rate=100000, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, duration=0.1, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.slot_index = slot_index
        self.sample_rate = sample_rate
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.duration = duration
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'iridium',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'slot_index': self.slot_index,
            'sample_rate': self.sample_rate,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'duration': self.duration,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbOOK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ook',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbASK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, modulation_order=2, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ask',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbFSK:
    def __init__(self, power=-2, center_frequency=0, signal_start_time=0, modulation_order=4, baud=4800, samples_per_symbol=8, freq_sep=1000, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.signal_start_time = signal_start_time
        self.modulation_order = modulation_order
        self.baud = baud
        self.samples_per_symbol = samples_per_symbol
        self.freq_sep = freq_sep
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'fsk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'signal_start_time': self.signal_start_time,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'samples_per_symbol': self.samples_per_symbol,
            'freq_sep': self.freq_sep,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbDpsk:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'dpsk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbQAM:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, signal_start_time=0, modulation_order=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.signal_start_time = signal_start_time
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'qam',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'signal_start_time': self.signal_start_time,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _WbOfdm:
    def __init__(self, power=-2, center_frequency=0, signal_start_time=0, num_subcarriers=64, fft_size=64, cyclic_prefix_length=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.signal_start_time = signal_start_time
        self.num_subcarriers = num_subcarriers
        self.fft_size = fft_size
        self.cyclic_prefix_length = cyclic_prefix_length
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ofdm',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'signal_start_time': self.signal_start_time,
            'num_subcarriers': self.num_subcarriers,
            'fft_size': self.fft_size,
            'cyclic_prefix_length': self.cyclic_prefix_length,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _Narrowband:
    def __init__(self, data_type=DataType.iq, format=Format.raw_float32, noise=-10, sample_rate=100000):
        self.data_type = data_type
        self.format = format
        self.noise = noise
        self.sample_rate = sample_rate
        self._signals = []

    def Tone(self, **kwargs):
        sig = _NbTone(**kwargs)
        self._signals.append(sig)
        return sig

    def PSK(self, **kwargs):
        sig = _NbPSK(**kwargs)
        self._signals.append(sig)
        return sig

    def Iridium(self, **kwargs):
        sig = _NbIridium(**kwargs)
        self._signals.append(sig)
        return sig

    def OOK(self, **kwargs):
        sig = _NbOOK(**kwargs)
        self._signals.append(sig)
        return sig

    def ASK(self, **kwargs):
        sig = _NbASK(**kwargs)
        self._signals.append(sig)
        return sig

    def FSK(self, **kwargs):
        sig = _NbFSK(**kwargs)
        self._signals.append(sig)
        return sig

    def Dpsk(self, **kwargs):
        sig = _NbDpsk(**kwargs)
        self._signals.append(sig)
        return sig

    def QAM(self, **kwargs):
        sig = _NbQAM(**kwargs)
        self._signals.append(sig)
        return sig

    def Ofdm(self, **kwargs):
        sig = _NbOfdm(**kwargs)
        self._signals.append(sig)
        return sig
    def to_dict(self):
        return {
            'mode': 'dataset_generation',
            'segmentation': 'narrowband',
            'data_type': self.data_type,
            'format': self.format,
            'noise': self.noise,
            'sample_rate': self.sample_rate,
            'signal': [s.to_dict() for s in self._signals],
        }

class _Wideband:
    def __init__(self, data_type=DataType.iq, format=Format.raw_float32, noise=-10, length=10, sample_rate=100000):
        self.data_type = data_type
        self.format = format
        self.noise = noise
        self.length = length
        self.sample_rate = sample_rate
        self._signals = []

    def Tone(self, **kwargs):
        sig = _WbTone(**kwargs)
        self._signals.append(sig)
        return sig

    def PSK(self, **kwargs):
        sig = _WbPSK(**kwargs)
        self._signals.append(sig)
        return sig

    def Iridium(self, **kwargs):
        sig = _WbIridium(**kwargs)
        self._signals.append(sig)
        return sig

    def OOK(self, **kwargs):
        sig = _WbOOK(**kwargs)
        self._signals.append(sig)
        return sig

    def ASK(self, **kwargs):
        sig = _WbASK(**kwargs)
        self._signals.append(sig)
        return sig

    def FSK(self, **kwargs):
        sig = _WbFSK(**kwargs)
        self._signals.append(sig)
        return sig

    def Dpsk(self, **kwargs):
        sig = _WbDpsk(**kwargs)
        self._signals.append(sig)
        return sig

    def QAM(self, **kwargs):
        sig = _WbQAM(**kwargs)
        self._signals.append(sig)
        return sig

    def Ofdm(self, **kwargs):
        sig = _WbOfdm(**kwargs)
        self._signals.append(sig)
        return sig
    def to_dict(self):
        return {
            'mode': 'dataset_generation',
            'segmentation': 'wideband',
            'data_type': self.data_type,
            'format': self.format,
            'noise': self.noise,
            'length': self.length,
            'sample_rate': self.sample_rate,
            'signal': [s.to_dict() for s in self._signals],
        }

class _EnvTxTone:
    def __init__(self, power=-2, center_frequency=0, duration=0.1, phase=0):
        self.power = power
        self.center_frequency = center_frequency
        self.duration = duration
        self.phase = phase

    def to_dict(self):
        return {
            'signal': 'tone',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'duration': self.duration,
            'phase': self.phase,
        }

class _EnvTxPSK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=4, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'psk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxIridium:
    def __init__(self, power=-2, center_frequency=0, slot_index=0, sample_rate=100000, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, duration=0.1, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.slot_index = slot_index
        self.sample_rate = sample_rate
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.duration = duration
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'iridium',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'slot_index': self.slot_index,
            'sample_rate': self.sample_rate,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'duration': self.duration,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxOOK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ook',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxASK:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=2, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ask',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxFSK:
    def __init__(self, power=-2, center_frequency=0, modulation_order=4, baud=4800, samples_per_symbol=8, freq_sep=1000, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.modulation_order = modulation_order
        self.baud = baud
        self.samples_per_symbol = samples_per_symbol
        self.freq_sep = freq_sep
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'fsk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'samples_per_symbol': self.samples_per_symbol,
            'freq_sep': self.freq_sep,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxDpsk:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'dpsk',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxQAM:
    def __init__(self, power=-2, center_frequency=0, samples_per_symbol=8, pulse_shape='rectangle', pulse_shape_span=6, pulse_shape_rolloff=0.35, modulation_order=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.samples_per_symbol = samples_per_symbol
        self.pulse_shape = pulse_shape
        self.pulse_shape_span = pulse_shape_span
        self.pulse_shape_rolloff = pulse_shape_rolloff
        self.modulation_order = modulation_order
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'qam',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'samples_per_symbol': self.samples_per_symbol,
            'pulse_shape': self.pulse_shape,
            'pulse_shape_span': self.pulse_shape_span,
            'pulse_shape_rolloff': self.pulse_shape_rolloff,
            'modulation_order': self.modulation_order,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvTxOfdm:
    def __init__(self, power=-2, center_frequency=0, num_subcarriers=64, fft_size=64, cyclic_prefix_length=16, baud=4800, data_generator=DataGenerator.random, n_bits=8192, bits='1010101', input_file=''):
        self.power = power
        self.center_frequency = center_frequency
        self.num_subcarriers = num_subcarriers
        self.fft_size = fft_size
        self.cyclic_prefix_length = cyclic_prefix_length
        self.baud = baud
        self.data_generator = data_generator
        self.n_bits = n_bits
        self.bits = bits
        self.input_file = input_file

    def to_dict(self):
        return {
            'signal': 'ofdm',
            'power': self.power,
            'center_frequency': self.center_frequency,
            'num_subcarriers': self.num_subcarriers,
            'fft_size': self.fft_size,
            'cyclic_prefix_length': self.cyclic_prefix_length,
            'baud': self.baud,
            'data_generator': self.data_generator,
            'n_bits': self.n_bits,
            'bits': self.bits,
            'input_file': self.input_file,
        }

class _EnvReceiver:
    def __init__(self, data_type=DataType.iq, format=Format.raw_float32, noise=-10, sample_rate=100000):
        self.data_type = data_type
        self.format = format
        self.noise = noise
        self.sample_rate = sample_rate

    def to_dict(self):
        return {
            'element': 'receiver',
            'data_type': self.data_type,
            'format': self.format,
            'noise': self.noise,
            'sample_rate': self.sample_rate,
        }

class _EnvTransmitter:
    def __init__(self):
        self._sig = None

    def Tone(self, **kwargs):
        self._sig = _EnvTxTone(**kwargs)
        return self._sig

    def PSK(self, **kwargs):
        self._sig = _EnvTxPSK(**kwargs)
        return self._sig

    def Iridium(self, **kwargs):
        self._sig = _EnvTxIridium(**kwargs)
        return self._sig

    def OOK(self, **kwargs):
        self._sig = _EnvTxOOK(**kwargs)
        return self._sig

    def ASK(self, **kwargs):
        self._sig = _EnvTxASK(**kwargs)
        return self._sig

    def FSK(self, **kwargs):
        self._sig = _EnvTxFSK(**kwargs)
        return self._sig

    def Dpsk(self, **kwargs):
        self._sig = _EnvTxDpsk(**kwargs)
        return self._sig

    def QAM(self, **kwargs):
        self._sig = _EnvTxQAM(**kwargs)
        return self._sig

    def Ofdm(self, **kwargs):
        self._sig = _EnvTxOfdm(**kwargs)
        return self._sig
    def to_dict(self):
        d = {"element": "transmitter"}
        if self._sig:
            d.update(self._sig.to_dict())
        return d

class _EnvPlatform:
    def __init__(self, position_x=0, position_y=0, position_z=0, self_interference_cancelation='True'):
        self.position_x = position_x
        self.position_y = position_y
        self.position_z = position_z
        self.self_interference_cancelation = self_interference_cancelation
        self._elements = []

    def Receiver(self, **kwargs):
        el = _EnvReceiver(**kwargs)
        self._elements.append(el)
        return el

    def Transmitter(self):
        el = _EnvTransmitter()
        self._elements.append(el)
        return el

    def to_dict(self):
        return {
            'platform': 'platform',
            'position_x': self.position_x,
            'position_y': self.position_y,
            'position_z': self.position_z,
            'self_interference_cancelation': self.self_interference_cancelation,
            'element': [e.to_dict() for e in self._elements],
        }

class _Environment:
    def __init__(self, length=10):
        self.length = length
        self._platforms = []

    def Platform(self, **kwargs):
        plat = _EnvPlatform(**kwargs)
        self._platforms.append(plat)
        return plat

    def to_dict(self):
        return {
            'mode': 'environment_simulation',
            'length': self.length,
            'platform': [p.to_dict() for p in self._platforms],
        }

class SigteraClient:
    def __init__(self, url='https://api.sigtera.com'):
        self.url = url
        self._requests = []

    def narrowband(self, **kwargs):
        req = _Narrowband(**kwargs)
        self._requests.append(req)
        return req

    def wideband(self, **kwargs):
        req = _Wideband(**kwargs)
        self._requests.append(req)
        return req

    def environment(self, **kwargs):
        req = _Environment(**kwargs)
        self._requests.append(req)
        return req

    def simulate(self):
        for req in self._requests:
            yield from self._send(req)

    def _send(self, req):
        with requests.post(f'{self.url}/api', json=req.to_dict(), stream=True) as r:
            r.raise_for_status()
            yield from self._parse(r)

    def _parse(self, r):
        buf = bytearray()
        it = r.iter_content(chunk_size=65536)

        def fill(n):
            while len(buf) < n:
                try:
                    buf.extend(next(it))
                except StopIteration:
                    return

        def read_frame():
            fill(4)
            if len(buf) < 4:
                return None, b''
            n = int.from_bytes(buf[:4], 'big')
            if n == 0:
                del buf[:4]
                return 0, b''
            fill(4 + n)
            data = bytes(buf[4:4 + n])
            del buf[:4 + n]
            return n, data

        while True:
            n, data = read_frame()
            if n is None:
                return
            sig_params = json.loads(data)
            chunks = []
            while True:
                n, data = read_frame()
                if n == 0 or n is None:
                    break
                chunks.append(data)
            yield sig_params, b''.join(chunks)
