# SPDX-License-Identifier: GPL-2.0-only
# Copyright (c) 2019-2020 NITK Surathkal

"""
Runs ss command and parses socket stats from
its output
"""

import os
import time
import re
from ..results import SsResults
from .runnerbase import Runner
from ...topology_map import TopologyMap


class SsRunner(Runner):
    """
    Runs ss command and stores and parses the output

    Attributes
    ----------
    iterator : str
        absolute path of the ss iterator script
    param_list: list(str)
        list of parameters to be parsed
    ns_id : str
        network namespace to run ss from
    destination_ip : str
        ip address of the destination namespace
    start_time : num
        time at which ss is to be run
    run_time : num
        total time to run ss for
    """

    iterator = os.path.realpath(os.path.dirname(__file__)) + "/iterators/ss.sh"
    param_list = ['cwnd', 'rwnd', 'rtt', 'ssthresh',
                  'rto', 'delivery_rate', 'pacing_rate']

    def __init__(self, ns_id, destination_ip, start_time, run_time,):
        """
        Constructor to initialize ss runner

        Parameters
        ----------
        ns_id : str
            network namespace to run ss from
        destination_ip : str
            ip address of the destination namespace
        start_time : num
            time at which ss is to be run
        run_time : num
            total time to run ss for
        """
        self.ns_id = ns_id
        self.destination_ip = destination_ip
        self.start_time = start_time
        self.run_time = run_time
        super().__init__()

    def run(self):
        """
        Runs the ss iterator
        """
        if self.start_time != 0:
            time.sleep(self.start_time)

        command = 'ip netns exec {ns_id} /bin/bash {iterator} {destination} {duration} \
            {filter}'.format(
                ns_id=self.ns_id, iterator=SsRunner.iterator, destination=self.destination_ip,
                duration=self.run_time, filter="\"dport != 12865 and sport != 12865\"")

        super().run(command)

    def print_error(self):
        """
        Method to print error from `self.err`
        """
        self.err.seek(0)  # rewind to start of file
        error = self.err.read().decode()
        ns_name = TopologyMap.get_namespace(self.ns_id)['name']
        print('Error collecting socket stats at {}. {}'.format(ns_name, error))

    # pylint: disable=too-many-locals
    def parse(self):
        """
        parses the required data from `self.out`
        """
        stats_dict_list = {}

        self.out.seek(0)    # rewind to start of the temp file

        # See `iterators/ss.h` for output format
        raw_stats = self.out.read().decode().split("---")

        for raw_stat in raw_stats[:-1]:

            # Pattern to capture port numbers of flows to `destination ip`
            port_pattern = re.escape(self.destination_ip) + r':(?P<port>\d+)'
            port_list = [port.group('port')
                         for port in re.finditer(port_pattern, raw_stat)]
            timestamp_pattern = r'timestamp:(?P<timestamp>\d+\.\d+)'
            timestamp = re.search(
                timestamp_pattern, raw_stat).group("timestamp")
            for port in port_list:
                if port not in stats_dict_list:
                    stats_dict_list[port] = [{"timestamp": timestamp}]
                else:
                    stats_dict_list[port].append({"timestamp": timestamp})

            for param in SsRunner.param_list:
                pattern = r'\s' + \
                    re.escape(param) + \
                    r'[\s:](?P<value>\w+\.?\w*(?:[\/\,]\w+\.?\w*)*)\s'
                # result list stores all the string that is matched by the `pattern`
                param_value_list = [value.group('value')
                                    for value in re.finditer(pattern, raw_stat)]
                param_value = ''
                for i in range(len(param_value_list)):
                    param_value = param_value_list[i].strip()
                    # remove the units at the end
                    param_value = re.sub(r'[A-Za-z]', '', param_value)
                    try:
                        # rtt has both avg and dev rtt separated by a /
                        if param == 'rtt':
                            avg_rtt = param_value.split('/')[0]
                            dev_rtt = param_value.split('/')[1]
                            stats_dict_list[port_list[i]][-1]['rtt'] = avg_rtt
                            stats_dict_list[port_list[i]
                                            ][-1]['dev_rtt'] = dev_rtt
                        else:
                            stats_dict_list[port_list[i]
                                            ][-1][param] = param_value
                    except TypeError:
                        pass

        SsResults.add_result(
            self.ns_id, {self.destination_ip: stats_dict_list})
        self.clean_up()

    def clean_up(self):
        """
        Closes the temp files created
        """
        return super().clean_up()
