# Copyright (c) 2021 Satlab A/S <satlab@satlab.com>
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.

from gnuradio import analog
import math
from gnuradio import blocks
from gnuradio import digital
from gnuradio import filter
from gnuradio.filter import firdes
from gnuradio import gr
from gnuradio import satlab


class srs3_rx(gr.hier_block2):
    def __init__(self, samp_rate=2.56e6, rate=512000, size=217, idnum=0, cc=True, rs=True, crc=True, rand=True, key='\x00'*32, decrypt=False, auth=False, swtol=0, v1compat=False):
        gr.hier_block2.__init__(
            self, "SRS-3 Receiver",
            gr.io_signature(1, 1, gr.sizeof_gr_complex),
            gr.io_signaturev(2, 2, [gr.sizeof_gr_complex, gr.sizeof_float]),
        )
        self.message_port_register_hier_out("csp_out")
        self.message_port_register_hier_out("ip_out")

        # Parameters
        self.samp_rate = samp_rate
        self.rate = rate
        self.size = size
        self.idnum = idnum
        self.cc = cc
        self.rs = rs
        self.crc = crc
        self.rand = rand
        self.key = key
        self.decrypt = decrypt
        self.auth = auth
        self.swtol = swtol
        self.v1compat = v1compat

        # Variables
        self.sps = 5
        self.chan_transition = 100e3
        self.chan_bw = 1.5 * rate
        self.bt = 0.5
        self.chan_taps = firdes.low_pass_2(1, self.samp_rate, self.chan_bw, self.chan_transition, 40)
        self.chan_samp_rate = self.sps * self.rate
        self.chan_decim = 1
        self.mf_taps = analog.cpm.phase_response(analog.cpm.GAUSSIAN, self.sps, 3, self.bt)
        self.frame_size = satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth)
        self.asm = 0x1aa73d3e if (self.cc and not self.v1compat) else 0x1acffc1d
        self.ss_loopbw = 2 * math.pi / 100
        self.ss_tedgain = 1.0

        # Blocks
        self.chanfilt = filter.fir_filter_ccc(self.chan_decim, self.chan_taps)
        self.chanfilt.declare_sample_delay(0)
        self.resampler = filter.mmse_resampler_cc(0, (self.samp_rate / self.chan_decim) / (self.chan_samp_rate))
        self.demod = analog.quadrature_demod_cf(self.sps / math.pi)
        self.mf = filter.fir_filter_fff(1, self.mf_taps)
        self.mf.declare_sample_delay(0)
        self.symsync = digital.symbol_sync_ff(
            digital.TED_ZERO_CROSSING,
            self.sps,
            self.ss_loopbw,
            1,
            self.ss_tedgain,
            0.05,
            1,
            digital.constellation_bpsk().base(),
            digital.IR_MMSE_8TAP,
            128,
            [])
        self.slicer = digital.binary_slicer_fb()
        self.swdetect = satlab.correlate_access_code_tag_fixed('{:032b}'.format(self.asm), self.swtol, 'packet_len', self.frame_size * 8)
        self.unpack = blocks.unpacked_to_packed_bb(1, gr.GR_MSB_FIRST)
        self.tsmult = blocks.tagged_stream_multiply_length(gr.sizeof_char * 1, 'packet_len', 1 / 8.0)
        self.tspdu = blocks.tagged_stream_to_pdu(blocks.byte_t, 'packet_len')
        self.decoder = satlab.srs3_decode(self.size + 2 + (2 if self.idnum > 0 else 0) + (12 if self.decrypt or self.auth else 0) + (16 if self.auth else 0), self.crc, self.cc, self.rand, self.rs, self.v1compat)
        self.decryptor = satlab.srs3_decrypt(self.key, self.decrypt, self.auth)
        self.deframer = satlab.srs3_deframe(self.size, self.idnum)

        # Main demodulation chain
        self.connect((self, 0), (self.chanfilt, 0))
        self.connect((self.chanfilt, 0), (self.resampler, 0))
        self.connect((self.resampler, 0), (self.demod, 0))
        self.connect((self.demod, 0), (self.mf, 0))
        self.connect((self.mf, 0), (self.symsync, 0))
        self.connect((self.symsync, 0), (self.slicer, 0))
        self.connect((self.slicer, 0), (self.swdetect, 0))
        self.connect((self.swdetect, 0), (self.unpack, 0))
        self.connect((self.unpack, 0), (self.tsmult, 0))
        self.connect((self.tsmult, 0), (self.tspdu, 0))
        self.msg_connect((self.tspdu, 'pdus'), (self.decoder, 'decode'))
        self.msg_connect((self.decoder, 'output'), (self.decryptor, 'decrypt'))
        self.msg_connect((self.decryptor, 'output'), (self.deframer, 'deframe'))
        self.msg_connect((self.deframer, 'csp'), (self, 'csp_out'))
        self.msg_connect((self.deframer, 'ip'), (self, 'ip_out'))

        # Channel filter debug output
        self.connect((self.resampler, 0), (self, 0))

        # Matched filter debug output
        self.connect((self.mf, 0), (self, 1))

    def _set_chan_taps(self, chan_taps):
        self.chan_taps = chan_taps
        self.chanfilt.set_taps(self.chan_taps)

    def _set_chan_bw(self, chan_bw):
        self.chan_bw = chan_bw
        self._set_chan_taps(firdes.low_pass_2(1, self.samp_rate, self.chan_bw, self.chan_transition, 40))

    def _set_chan_samp_rate(self, chan_samp_rate):
        self.chan_samp_rate = chan_samp_rate
        self.resampler.set_resamp_ratio((self.samp_rate / self.chan_decim) / (self.chan_samp_rate))

    def _set_asm(self, asm):
        self.asm = asm
        self.swdetect.set_access_code('{:032b}'.format(self.asm))

    def _set_frame_size(self, frame_size):
        self.frame_size = frame_size
        self.swdetect.set_packet_len(self.frame_size * 8)

    def get_rate(self):
        return self.rate

    def set_rate(self, rate):
        self.rate = rate
        self._set_chan_bw(1.5 * self.rate)
        self._set_chan_samp_rate(self.sps * self.rate)

    def get_size(self):
        return self.size

    def set_size(self, size):
        self.size = size
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_size(self.size + 2 + (2 if self.idnum > 0 else 0) + (12 if self.decrypt or self.auth else 0) + (16 if self.auth else 0))
        self.deframer.set_size(self.size)

    def get_id(self):
        return self.idnum

    def set_id(self, idnum):
        self.idnum = idnum
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_size(self.size + 2 + (2 if self.idnum > 0 else 0) + (12 if self.decrypt or self.auth else 0) + (16 if self.auth else 0))
        self.deframer.set_id(self.idnum)

    def get_cc(self):
        return self.cc

    def set_cc(self, cc):
        self.cc = cc
        self._set_asm(0x1aa73d3e if (self.cc and not self.v1compat) else 0x1acffc1d)
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_cc(self.cc)

    def get_rand(self):
        return self.rand

    def set_rand(self, rand):
        self.rand = rand
        self.decoder.set_rand(self.rand)

    def get_crc(self):
        return self.crc

    def set_crc(self, crc):
        self.crc = crc
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_crc(self.crc)

    def get_rs(self):
        return self.rs

    def set_rs(self, rs):
        self.rs = rs
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_rs(self.rs)

    def get_key(self):
        return self.key

    def set_key(self, key):
        self.key = key
        self.decryptor.set_key(self.key)

    def get_decrypt(self):
        return self.decrypt

    def set_decrypt(self, decrypt):
        self.decrypt = decrypt
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_size(self.size + 2 + (2 if self.idnum > 0 else 0) + (12 if self.decrypt or self.auth else 0) + (16 if self.auth else 0))
        self.decryptor.set_decrypt(self.decrypt)

    def get_auth(self):
        return self.auth

    def set_auth(self, auth):
        self.auth = auth
        self._set_frame_size(satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth))
        self.decoder.set_size(self.size + 2 + (2 if self.idnum > 0 else 0) + (12 if self.decrypt or self.auth else 0) + (16 if self.auth else 0))
        self.decryptor.set_auth(self.auth)

    def get_swtol(self):
        return self.swtol

    def set_swtol(self, swtol):
        self.swtol = swtol
        self.swdetect.set_threshold(self.swtol)

    def get_v1compat(self):
        return self.v1compat

    def set_v1compat(self, v1compat):
        self.v1compat = v1compat
        self._set_asm(0x1aa73d3e if (self.cc and not self.v1compat) else 0x1acffc1d)
        self.decoder.set_v1compat(self.v1compat)

    def get_frames(self):
        return self.deframer.get_frames()

    def get_bit_rate(self):
        frame_size = satlab.frame_size(self.size, self.crc, self.rs, self.cc, self.idnum, self.decrypt, self.auth, cc_term=True, include_asm=True)
        return self.rate * self.size / frame_size

    def get_modulation(self):
        return 'GMSK'
