#!/usr/bin/env python
from groupflow_shared import *
from mininet.net import *
from mininet.node import OVSSwitch, UserSwitch
from mininet.link import TCLink
from mininet.log import setLogLevel
from mininet.cli import CLI
from mininet.node import Node, RemoteController
from scipy.stats import truncnorm
from numpy.random import randint, uniform
from subprocess import *
import sys
import signal
from time import sleep, time
from datetime import datetime
from multiprocessing import Process, Pipe
import numpy as np
import traceback
import os.path

# Hardcoded purely for testing / debug, these will be moved once functionality is stable
ARRIVAL_RATE = 5 * (1.0 / 60)
SERVICE_RATE = 1.0 / 60
TRIAL_DURATION_SECONDS = 60.0 * 3
RECEIVERS_AT_TRIAL_START = 5
STATS_RECORDING_INTERVAL = 5
MEDIA_DURATION_SECONDS = 72

def mcastTestDynamic(topo, hosts = [], log_file_name = 'test_log.log', replacement_mode='none', link_weight_type = 'linear', number_of_groups = 30, pipe = None):
    test_groups = []
    test_success = True
    
    # First, check if the log file already exists, and stop the test if it does
    # (This is primarily in place to allow continuing an interrupted trial set by running the same console command)
    if os.path.isfile(log_file_name):
        print 'Skipping trial, log file already exists: ' + str(log_file_name)
        if pipe is not None:
            pipe.send(test_success)
            pipe.close()
        return

    # Launch the external controller
    pox_link_weight_type = link_weight_type
    static_link_weight = 0
    util_link_weight = 1
    
    if link_weight_type == 'linear':    # Linear link weights
        pox_link_weight_type = 'linear'
        static_link_weight = 0
        util_link_weight = 1
    elif link_weight_type == 'sh':  # Shortest-hop routing
        pox_link_weight_type = 'linear'
        static_link_weight = 1
        util_link_weight = 0
    elif link_weight_type == 'exponential': # Exponential link weights
        pox_link_weight_type = 'exponential'
        static_link_weight = 0
        util_link_weight = 1
        
    
    pox_arguments = []
    static_link_weight = 0
    if util_link_weight == 0:
        static_link_weight = 1
        
    if 'periodic' in replacement_mode:
        pox_arguments = ['pox.py', 'log', '--file=pox.log,w', 'openflow.discovery', '--link_timeout=30', 'openflow.keepalive',
                'openflow.flow_tracker', '--query_interval=1', '--link_max_bw=19', '--link_cong_threshold=13', '--avg_smooth_factor=0.5', '--log_peak_usage=True',
                'misc.benchmark_terminator', 'openflow.igmp_manager', 'misc.groupflow_event_tracer',
                'openflow.groupflow', '--static_link_weight=' + str(static_link_weight), '--util_link_weight=' + str(util_link_weight), '--link_weight_type=' + link_weight_type, '--flow_replacement_mode=' + replacement_mode,
                '--flow_replacement_interval=10',
                'log.level', '--WARNING', '--openflow.flow_tracker=INFO']
    else:
        pox_arguments = ['pox.py', 'log', '--file=pox.log,w', 'openflow.discovery', '--link_timeout=30', 'openflow.keepalive',
                'openflow.flow_tracker', '--query_interval=1', '--link_max_bw=19', '--link_cong_threshold=13', '--avg_smooth_factor=0.5', '--log_peak_usage=True',
                'misc.benchmark_terminator', 'openflow.igmp_manager', 'misc.groupflow_event_tracer',
                'openflow.groupflow', '--static_link_weight=' + str(static_link_weight), '--util_link_weight=' + str(util_link_weight), '--link_weight_type=' + link_weight_type, '--flow_replacement_mode=' + replacement_mode,
                '--flow_replacement_interval=10',
                'log.level', '--WARNING', '--openflow.flow_tracker=INFO']
    print 'Launching external controller: ' + str(pox_arguments[0])
    print 'Launch arguments:'
    print ' '.join(pox_arguments)
    
    with open(os.devnull, "w") as fnull:
        pox_process = Popen(pox_arguments, stdout=fnull, stderr=fnull, shell=False, close_fds=True)
        # Allow time for the log file to be generated
        sleep(1)
    
    # Determine the flow tracker log file
    pox_log_file = open('./pox.log', 'r')
    flow_log_path = None
    event_log_path = None
    got_flow_log_path = False
    got_event_log_path = False
    while (not got_flow_log_path) or (not got_event_log
_path):
        pox_log = pox_log_file.readline()

        if 'Writing flow tracker info to file:' in pox_log:
            pox_log_split = pox_log.split()
            flow_log_path = pox_log_split[-1]
            got_flow_log_path = True
        
        if 'Writing event trace info to file:' in p
ox_log:
            pox_log_split = pox_log.split()
            event_log_path = pox_log_split[-1]
            got_event_log_path = True
            
            
    print 'Got flow tracker log file: ' + str(flow_log_path)
    print 'Got event trace log file: ' + str(event_log_path)
    print 'Controller initialized'
    pox_log_offset = pox_log_file.tell()
    pox_log_file.close()
    # External controller launched
    
    # Launch Mininet
    net = Mininet(topo, controller=RemoteController, switch=OVSSwitch, link=TCLink, build=False, autoSetMacs=True)
    # pox = RemoteController('pox', '127.0.0.1', 6633)
    net.addController('pox', RemoteController, ip = '127.0.0.1', port = 6633)
    net.start()
    for switch_name in topo.get_switch_list():
        #print switch_name + ' route add -host 127.0.0.1 dev lo'
        net.get(switch_name).controlIntf = net.get(switch_name).intf('lo')
        net.get(switch_name).cmd('route add -host 127.0.0.1 dev lo')
        #print 'pox' + ' route add -host ' + net.get(switch_name).IP() + ' dev lo'
        net.get('pox').cmd('route add -host ' + net.get(switch_name).IP() + ' dev lo')
        #print net.get(switch_name).cmd('ifconfig')
        
    topo.mcastConfig(net)
    # Wait for controller topology discovery
    controller_init_sleep_time = 10
    print 'Waiting ' + str(controller_init_sleep_time) + ' seconds to allow for controller topology discovery.'
    sleep(controller_init_sleep_time)
    # Mininet launched
    
    # Generate the test groups, and launch the sender applications
    rand_seed = int(time())
    print 'Using random seed: ' + str(rand_seed)
    np.random.seed(rand_seed)
    
    trial_start_time = time() + MEDIA_DURATION_SECONDS + 10   # Assume generation of test group events will take no more than 10 seconds
    trial_end_time = trial_start_time + TRIAL_DURATION_SECONDS
    mcast_group_last_octet = 1
    mcast_port = 5010
    for i in range(0, number_of_groups):
        mcast_ip = '224.1.1.{last_octet}'.format(last_octet = str(mcast_group_last_octet))
        test_group = DynamicMulticastGroupDefinition(net.hosts, mcast_ip, mcast_port, mcast_port + 1)
        print 'Generating events for group: ' + mcast_ip
        test_group.generate_receiver_events(trial_start_time, TRIAL_DURATION_SECONDS, RECEIVERS_AT_TRIAL_START, ARRIVAL_RATE, SERVICE_RATE)
        test_groups.append(test_group)
        mcast_group_last_octet += 1
        mcast_port += 2
        
    test_group_start_times = []
    for i in range(0, number_of_groups):
        test_group_start_times.append(uniform(0, MEDIA_DURATION_SECONDS))
    test_group_start_times.sort()
    # Test groups generated
    
    # Launch initial receiver applications
    group_launch_index = 0
    launch_start_time = time()
    while len(test_group_start_times) > 0:
        cur_time = time() - launch_start_time
        if cur_time >= test_group_start_times[0]:
            test_group_start_times.pop(0)
            print 'Launching test group ' + str(group_launch_index) + ' at launch time: ' + str(cur_time)
            test_groups[group_launch_index].launch_sender_application()
            test_groups[group_launch_index].update_receiver_applications(trial_start_time)
            group_launch_index += 1
        else:
            sleep_time = test_group_start_times[0] - cur_time
            sleep(sleep_time)
            
    
    # Wait for trial run start time
    sleep_time = trial_start_time - time()
    if sleep_time < 0:
        print 'WARNING: sleep_time is negative!'
    e