# Lint as: python2, python3
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import collections
import logging
import os
import re
import six

IW_REMOTE_EVENT_LOG_FILE_NAME = 'iw_event.log'

LogEntry = collections.namedtuple('LogEntry', ['timestamp',
                                               'interface',
                                               'message'])

class IwEventLogger(object):
    """Context enclosing the use of iw event logger."""
    def __init__(self, host, command_iw, local_file):
        self._host = host
        self._command_iw = command_iw
        self._iw_event_log_path = os.path.join(self._host.get_tmp_dir(),
                                               IW_REMOTE_EVENT_LOG_FILE_NAME)
        self._local_file = local_file
        self._pid = None
        self._start_time = 0


    def __enter__(self):
        return self


    def __exit__(self, exception, value, traceback):
        self.stop()


    def _check_message_for_disconnect(self, message):
        """Check log message for disconnect event.

        This function checks log messages for signs the connection was
        terminated.

        @param: message String message to check for disconnect event.

        @returns True if the log message is a disconnect event, false otherwise.

        """
        return (message.startswith('disconnected') or
            message.startswith('Deauthenticated') or
            message == 'Previous authentication no longer valid')


    @property
    def local_file(self):
        """@return string local host path for log file."""
        return self._local_file


    def start(self):
        """Start event logger.

        This function will start iw event process in remote host, and redirect
        output to a temporary file in remote host.

        """
        command = 'nohup %s event -t </dev/null >%s 2>&1 & echo $!' % (
                self._command_iw, self._iw_event_log_path)
        command += ';date +%s'
        out_lines = self._host.run(command).stdout.splitlines()
        self._pid = int(out_lines[0])
        self._start_time = float(out_lines[1])


    def stop(self):
        """Stop event logger.

        This function will kill iw event process, and copy the log file from
        remote to local.

        """
        if self._pid is None:
            return
        # Kill iw event process
        self._host.run('kill %d' % self._pid, ignore_status=True)
        self._pid = None
        # Copy iw event log file from remote host
        self._host.get_file(self._iw_event_log_path, self._local_file)
        logging.info('iw event log saved to %s', self._local_file)


    def get_log_entries(self):
        """Parse local log file and yield LogEntry named tuples.

        This function will parse the iw event log and return individual
        LogEntry tuples for each parsed line.
        Here are example of lines to be parsed:
            1393961008.058711: wlan0 (phy #0): scan started
            1393961019.758599: wlan0 (phy #0): connected to 04:f0:21:03:7d:bd

        @yields LogEntry tuples for each log entry.

        """
        iw_log = self._host.run('cat %s' % self._iw_event_log_path).stdout
        iw_log_file = six.StringIO(iw_log)
        for line in iw_log_file.readlines():
            parse_line = re.match('\s*(\d+).(\d+): (\w[^:]*): (\w.*)', line)
            if parse_line:
                time_integer = parse_line.group(1)
                time_decimal = parse_line.group(2)
                timestamp = float('%s.%s' % (time_integer, time_decimal))
                yield LogEntry(timestamp=timestamp,
                               interface=parse_line.group(3),
                               message=parse_line.group(4))


    def get_reassociation_time(self):
        """Return reassociation time.

        This function will search the iw event log to determine the time it
        takes from start of reassociation request to being connected. Start of
        reassociation request could be either an attempt to scan or to
        disconnect. Assume the one that appeared in the log first is the start
        of the reassociation request.

        @returns float number of seconds it take from start of reassociation
                request to being connected. Return None if unable to determine
                the time based on the log.

        """
        start_time = None
        end_time = None
        # Figure out the time when reassociation process started and the time
        # when client is connected.
        for entry in self.get_log_entries():
            if (entry.message.startswith('scan started') and
                    start_time is None):
                start_time = entry.timestamp
            # Newer wpa_supplicant would attempt to disconnect then reconnect
            # without scanning. So if no scan event is detected before the
            # disconnect attempt, we'll assume the disconnect attempt is the
            # beginning of the reassociate attempt.
            if (self._check_message_for_disconnect(entry.message) and
                    start_time is None):
                start_time = entry.timestamp
            if entry.message.startswith('connected'):
                if start_time is None:
                    return None
                end_time = entry.timestamp
                break;
        else:
            return None
        return end_time - start_time


    def get_disconnect_count(self):
        """Return number of times the system disconnected during the log.

        This function will search the iw event log to determine how many
        times the "disconnect" and "Deauthenticated" messages appear.

        @returns int number of times the system disconnected in the logs.

        """
        count = 0
        for entry in self.get_log_entries():
            if self._check_message_for_disconnect(entry.message):
                count += 1
        return count


    def get_time_to_disconnected(self):
        """Return disconnect time.

        This function will search the iw event log to determine the number of
        seconds between the time iw event logger is started to the time the
        first "disconnected" or "Deauthenticated" event is received.

        @return float number of seconds between the time iw event logger is
                started to the time "disconnected" or "Deauthenticated" event
                is received. Return None if no "disconnected" or
                "Deauthenticated" event is detected in the iw event log.
        """
        for entry in self.get_log_entries():
            if self._check_message_for_disconnect(entry.message):
                return entry.timestamp - self._start_time
        return None
