#!/usr/bin/env python
#
# Copyright 2012-2014 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio 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.
#
# GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from __future__ import division

import numpy

from gnuradio import gr, gr_unittest, blocks, fft, analog, digital
import pmt

class qa_ofdm_serializer_vcc (gr_unittest.TestCase):

    def setUp (self):
        self.tb = gr.top_block ()
        self.tsb_key = "ts_last"

    def tearDown (self):
        self.tb = None

    def test_001_simple (self):
        """ Standard test """
        fft_len = 16
        tx_symbols = (0, 1,  1j,  2,  3, 0, 0, 0, 0, 0, 0, 4,  5,  2j, 6,  0,
                      0, 7,  8,  3j,  9, 0, 0, 0, 0, 0, 0, 10, 4j, 11, 12, 0,
                      0, 13, 1j, 14, 15, 0, 0, 0, 0, 0, 0, 0,  0,  2j, 0,  0)
        expected_result = tuple(range(1, 16)) + (0, 0, 0)
        occupied_carriers = ((1, 3, 4, 11, 12, 14), (1, 2, 4, 11, 13, 14),)
        n_syms = len(tx_symbols) // fft_len
        src = blocks.vector_source_c(tx_symbols, False, fft_len)
        serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, self.tsb_key, "", 0, "", False)
        sink = blocks.tsb_vector_sink_c(tsb_key=self.tsb_key)
        self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, fft_len, n_syms, self.tsb_key), serializer, sink)
        self.tb.run ()
        self.assertEqual(sink.data()[0], expected_result)

    def test_001b_shifted (self):
        """ Same as before, but shifted, because that's the normal mode in OFDM Rx """
        fft_len = 16
        tx_symbols = (
            0, 0, 0, 0, 0,  0,  1,  2,    0,   3, 4,   5,  0, 0, 0, 0,
            0, 0, 0, 0, 6, 1j,  7,  8,    0,   9, 10, 1j, 11, 0, 0, 0,
            0, 0, 0, 0, 0, 12, 13, 14,    0,  15, 16, 17,  0, 0, 0, 0,
        )
        expected_result = tuple(range(18))
        occupied_carriers = ((13, 14, 15, 1, 2, 3), (-4, -2, -1, 1, 2, 4),)
        n_syms = len(tx_symbols) // fft_len
        src = blocks.vector_source_c(tx_symbols, False, fft_len)
        serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, self.tsb_key)
        sink = blocks.tsb_vector_sink_c(tsb_key=self.tsb_key)
        self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, fft_len, n_syms, self.tsb_key), serializer, sink)
        self.tb.run ()
        self.assertEqual(sink.data()[0], expected_result)

    def test_002_with_offset (self):
        """ Standard test, carrier offset """
        fft_len = 16
        tx_symbols = list(range(1, 16));
        tx_symbols = (0, 0, 1,  1j,  2,  3, 0, 0, 0, 0, 0, 0, 4,  5,  2j, 6,
                      0, 0, 7,  8,  3j,  9, 0, 0, 0, 0, 0, 0, 10, 4j, 11, 12,
                      0, 0, 13, 1j, 14, 15, 0, 0, 0, 0, 0, 0, 0,  0,  2j, 0)
        carr_offset = 1 # Compare this with tx_symbols from the previous test
        expected_result = tuple(range(1, 16)) + (0, 0, 0)
        occupied_carriers = ((1, 3, 4, 11, 12, 14), (1, 2, 4, 11, 13, 14),)
        n_syms = len(tx_symbols) // fft_len
        offsettag = gr.tag_t()
        offsettag.offset = 0
        offsettag.key = pmt.string_to_symbol("ofdm_sync_carr_offset")
        offsettag.value = pmt.from_long(carr_offset)
        src = blocks.vector_source_c(tx_symbols, False, fft_len, (offsettag,))
        sink = blocks.tsb_vector_sink_c(tsb_key=self.tsb_key)
        serializer = digital.ofdm_serializer_vcc(
                fft_len,
                occupied_carriers,
                self.tsb_key,
                "", 0,
                "ofdm_sync_carr_offset",
                False
        )
        self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, fft_len, n_syms, self.tsb_key), serializer, sink)
        self.tb.run ()
        self.assertEqual(sink.data()[0], expected_result)
        self.assertEqual(len(sink.tags()), 1)

    def test_003_connect (self):
        """ Connect carrier_allocator to ofdm_serializer,
            make sure output==input """
        fft_len = 8
        n_syms = 1
        occupied_carriers = ((1, 2, 6, 7),)
        pilot_carriers = ((3,),(5,))
        pilot_symbols = ((1j,),(-1j,))
        #tx_data = tuple([numpy.random.randint(0, 10) for x in range(4 * n_syms)])
        tx_data = (1, 2, 3, 4)
        src = blocks.vector_source_c(tx_data, False, 1)
        alloc = digital.ofdm_carrier_allocator_cvc(
                fft_len,
                occupied_carriers,
                pilot_carriers,
                pilot_symbols,
                (), # No sync word
                self.tsb_key,
                True # Output is shifted (default)
        )
        serializer = digital.ofdm_serializer_vcc(
                alloc,
                "", # Len tag key
                0, # Symbols skipped
                "", # Carrier offset key
                True # Input is shifted (default)
        )
        sink = blocks.tsb_vector_sink_c(tsb_key=self.tsb_key)
        self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, 1, len(tx_data), self.tsb_key), alloc, serializer, sink)
        self.tb.run ()
        self.assertEqual(sink.data()[0], tx_data)

    def test_004_connect (self):
        """
        Advanced test:
        - Allocator -> IFFT -> Frequency offset -> FFT -> Serializer
        - FFT does shift (moves DC to middle)
        - Make sure input == output
        - Frequency offset is -2 carriers
        """
        fft_len = 8
        n_syms = 1
        carr_offset = -2
        freq_offset = 1.0 / fft_len * carr_offset # Normalized frequency
        occupied_carriers = ((-2, -1, 1, 2),)
        pilot_carriers = ((-3,),(3,))
        pilot_symbols = ((1j,),(-1j,))
        tx_data = (1, 2, 3, 4)
        offsettag = gr.tag_t()
        offsettag.offset = 0
        offsettag.key = pmt.string_to_symbol("ofdm_sync_carr_offset")
        offsettag.value = pmt.from_long(carr_offset)
        src = blocks.vector_source_c(tx_data, False, 1, (offsettag,))
        alloc = digital.ofdm_carrier_allocator_cvc(fft_len,
                       occupied_carriers,
                       pilot_carriers,
                       pilot_symbols, (),
                       self.tsb_key)
        tx_ifft = fft.fft_vcc(fft_len, False, (1.0 / fft_len,)*fft_len, True)
        oscillator = analog.sig_source_c(1.0, analog.GR_COS_WAVE, freq_offset, 1.0)
        mixer = blocks.multiply_cc()
        rx_fft  = fft.fft_vcc(fft_len, True, (), True)
        sink2 = blocks.tsb_vector_sink_c(vlen=fft_len, tsb_key=self.tsb_key)
        self.tb.connect(rx_fft, sink2)
        serializer = digital.ofdm_serializer_vcc(
                alloc, "", 0, "ofdm_sync_carr_offset", True
        )
        sink = blocks.tsb_vector_sink_c(tsb_key=self.tsb_key)
        self.tb.connect(
                src,
                blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, 1, len(tx_data), self.tsb_key),
                alloc, tx_ifft,
                blocks.vector_to_stream(gr.sizeof_gr_complex, fft_len),
                (mixer, 0),
                blocks.stream_to_vector(gr.sizeof_gr_complex, fft_len),
                rx_fft, serializer, sink
        )
        self.tb.connect(oscillator, (mixer, 1))
        self.tb.run ()
        self.assertComplexTuplesAlmostEqual(sink.data()[0][-len(occupied_carriers[0]):], tx_data, places=4)

    def test_005_packet_len_tag (self):
        """ Standard test """
        fft_len = 16
        tx_symbols = list(range(1, 16));
        tx_symbols = (0, 1,  1j,  2,  3, 0, 0, 0, 0, 0, 0, 4,  5,  2j, 6,  0,
                      0, 7,  8,  3j,  9, 0, 0, 0, 0, 0, 0, 10, 4j, 11, 12, 0,
                      0, 13, 1j, 14, 15, 0, 0, 0, 0, 0, 0, 0,  0,  2j, 0,  0)
        expected_result = tuple(range(1, 16))
        occupied_carriers = ((1, 3, 4, 11, 12, 14), (1, 2, 4, 11, 13, 14),)
        n_syms = len(tx_symbols) // fft_len
        packet_len_tsb_key = "packet_len"
        tag2 = gr.tag_t()
        tag2.offset = 0
        tag2.key = pmt.string_to_symbol("packet_len")
        tag2.value = pmt.from_long(len(expected_result))
        src = blocks.vector_source_c(tx_symbols, False, fft_len, (tag2,))
        serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, self.tsb_key, packet_len_tsb_key , 0, "", False)
        sink = blocks.tsb_vector_sink_c(tsb_key=packet_len_tsb_key)
        self.tb.connect(src, blocks.stream_to_tagged_stream(gr.sizeof_gr_complex, fft_len, n_syms, self.tsb_key), serializer, sink)
        self.tb.run ()
        self.assertEqual(sink.data()[0], expected_result)

    def test_099 (self):
        """ Make sure it fails if it should """
        fft_len = 16
        occupied_carriers = ((1, 3, 4, 11, 12, 112),) # Something invalid
        self.assertRaises(RuntimeError, digital.ofdm_serializer_vcc, fft_len, occupied_carriers, self.tsb_key)


if __name__ == '__main__':
    gr_unittest.run(qa_ofdm_serializer_vcc, "qa_ofdm_serializer_vcc.xml")

