#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2019 Daniel Estevez <daniel@destevez.net>.
#
# 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.

import satellites.core
from satellites.utils.config import open_config
import satellites.components.datasources as datasources
from gnuradio import gr, blocks

import signal
import argparse
import sys

version = f"""gr_satellites {satellites.__version__}
Copyright (C) 2020 Daniel Estevez
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."""

def argument_parser():
    description = 'gr-satellites - GNU Radio decoders for Amateur satellites'
    info = 'The satellite parameter can be specified using name, NORAD ID or path to YAML file'
    p = argparse.ArgumentParser(description = description, conflict_handler = 'resolve', prog = f'gr_satellites satellite', epilog = info, formatter_class = argparse.RawTextHelpFormatter)

    p.add_argument('--version', action = 'version', version = version)

    p_input = p.add_argument_group('input')

    p_sources = p_input.add_mutually_exclusive_group(required = True)
    p_sources.add_argument('--wavfile', help = 'WAV input file')
    p_sources.add_argument('--rawfile', help = 'RAW input file (float32 or complex64)')
    p_sources.add_argument('--rawint16', help = 'RAW input file (int16)')
    p_sources.add_argument('--udp', action = 'store_true', help = 'Use UDP input')
    p_sources.add_argument('--kiss_in', help = 'KISS input file')

    p_input.add_argument('--samp_rate', type = float, help = 'Sample rate (Hz)')
    p_input.add_argument('--udp_ip', default = '::', help = 'UDP input listen IP [default=%(default)r]')
    p_input.add_argument('--udp_port', default = '7355', type = int, help = 'UDP input listen port [default=%(default)r]')
    p_input.add_argument('--iq', action = 'store_true', help = 'Use IQ input')
    p_input.add_argument('--input_gain', type = float, default = 1, help = 'Input gain (can be negative to invert signal) [default=%(default)r]')    
    
    p_output = p.add_argument_group('output')
    p_output.add_argument('--kiss_out', help = 'KISS output file')
    p_output.add_argument('--kiss_append', action = 'store_true', help = 'Append to KISS output file')
    p_output.add_argument('--hexdump', action = 'store_true', help = 'Hexdump instead of telemetry parse')
    p_output.add_argument('--dump_path', help = 'Path to dump internal signals')
    return p

def check_options(options, parser):
    if options.kiss_in is None and options.samp_rate is None:
        print('Need to specify --samp_rate unless --kiss_in is used', file = sys.stderr)
        parser.print_usage(file = sys.stderr)
        sys.exit(1)

def parse_satellite(satellite):
    if satellite.lower().endswith('.yml'):
        return {'file' : satellite}
    elif satellite.isnumeric():
        return {'norad' : int(satellite)}
    else:
        return {'name' : satellite}

class gr_satellites_top_block(gr.top_block):
    def __init__(self, parser):
        gr.top_block.__init__(self, 'gr-satellites top block')
        sat = parse_satellite(sys.argv[1])
        satellites.core.gr_satellites_flowgraph.add_options(parser, **sat)
        options = parser.parse_args(sys.argv[2:])
        check_options(options, parser)
        
        self.options = options

        pdu_in = options.kiss_in is not None

        self.config = open_config()
        
        self.flowgraph = satellites.core.gr_satellites_flowgraph(samp_rate = options.samp_rate, iq = self.options.iq,
                                                                 pdu_in = pdu_in, options = options, config = self.config,
                                                                 dump_path = options.dump_path, **sat)
        self.setup_input()
        if pdu_in:
            self.msg_connect((self.input, 'out'), (self.flowgraph, 'in'))
        else:
            gain = self.options.input_gain
            self.gain = blocks.multiply_const_cc(gain) if self.options.iq else\
              blocks.multiply_const_ff(gain)
            self.connect(self.input, self.gain, self.flowgraph)

    def setup_input(self):
        if self.options.wavfile is not None:
            return self.setup_wavfile_input()
        elif self.options.rawfile is not None:
            return self.setup_rawfile_input()
        elif self.options.rawint16 is not None:
            return self.setup_rawint16_input()
        elif self.options.udp is True:
            return self.setup_udp_input()
        elif self.options.kiss_in is not None:
            return self.setup_kiss_input()
        else:
            raise Exception('No input source set for flowgraph')

    def setup_wavfile_input(self):
        self.wavfile_source = blocks.wavfile_source(self.options.wavfile, False)
        if self.options.iq:
            self.float_to_complex = blocks.float_to_complex(1)
            self.connect((self.wavfile_source, 0), (self.float_to_complex, 0))
            self.connect((self.wavfile_source, 1), (self.float_to_complex, 1))
            self.input = self.float_to_complex
        else:
            self.input = self.wavfile_source

    def setup_rawfile_input(self):
        size = gr.sizeof_gr_complex if self.options.iq else gr.sizeof_float
        self.input = blocks.file_source(size, self.options.rawfile, False, 0, 0)

    def setup_rawint16_input(self):
        self.input_int16 = blocks.file_source(gr.sizeof_short, self.options.rawint16, False, 0, 0)
        self.setup_input_int16()

    def setup_udp_input(self):
        self.input_int16 = blocks.udp_source(gr.sizeof_short, self.options.udp_ip, self.options.udp_port, 1472, False)
        self.setup_input_int16()

    def setup_input_int16(self):
        self.short_to_float = blocks.short_to_float(1, 32767)
        self.connect(self.input_int16, self.short_to_float)
        if self.options.iq:
            self.deinterleave = blocks.deinterleave(gr.sizeof_float, 1)
            self.float_to_complex = blocks.float_to_complex(1)
            self.connect(self.short_to_float, self.deinterleave)
            self.connect((self.deinterleave, 0), (self.float_to_complex, 0))
            self.connect((self.deinterleave, 1), (self.float_to_complex, 1))
            self.input = self.float_to_complex
        else:
            self.input = self.short_to_float

    def setup_kiss_input(self):
        self.input = datasources.kiss_file_source(self.options.kiss_in)

def main():
    parser = argument_parser()
    if len(sys.argv) <= 1 or sys.argv[1][0] == '-':
        parser.print_usage(file = sys.stderr)
        sys.exit(1)
    
    tb = gr_satellites_top_block(parser)

    def sig_handler(sig=None, frame=None):
        tb.stop()
        tb.wait()
        sys.exit(0)

    signal.signal(signal.SIGINT, sig_handler)
    signal.signal(signal.SIGTERM, sig_handler)

    tb.start()
    tb.wait()
        
if __name__ == '__main__':
    main()
