from operator import attrgetter
from ryu.app import simple_switch_13
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.lib import hub
import os
import logging
from ControllerApp import Controller
from ControllerApp.FlowDB import Flow

LOG = logging.getLogger("Mind Controller")
LOG.setLevel(logging.INFO)
logging.basicConfig()


class SimpleMonitor(simple_switch_13.SimpleSwitch13):
    def __init__(self, *args, **kwargs):
        super(SimpleMonitor, self).__init__(*args, **kwargs)
        self.datapaths = {}
        self.monitor_thread = hub.spawn(self._monitor)
        self.portStats = {}
        self.flowStats = {}
        self.controller = Controller.Controller()
        self.flowCounter = 0

    @set_ev_cls(ofp_event.EventOFPStateChange,
                [MAIN_DISPATCHER, DEAD_DISPATCHER])
    def _state_change_handler(self, ev):
        datapath = ev.datapath
        if ev.state == MAIN_DISPATCHER:
            if not datapath.id in self.datapaths:
                self.logger.debug('register datapath: %016x', datapath.id)
                self.datapaths[datapath.id] = datapath
        elif ev.state == DEAD_DISPATCHER:
            if datapath.id in self.datapaths:
                self.logger.debug('unregister datapath: %016x', datapath.id)
                del self.datapaths[datapath.id]

    def _monitor(self):
        while True:
            for dp in self.datapaths.values():
                self._request_stats(dp)
            hub.sleep(5)

    def _request_stats(self, datapath):
        self.logger.debug('send stats request: %016x', datapath.id)
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        req = parser.OFPFlowStatsRequest(datapath)
        datapath.send_msg(req)

        req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
        datapath.send_msg(req)

    @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
    def _port_stats_reply_handler(self, ev):
        body = ev.msg.body
        self.logger.info('start')
        self.logger.info('datapath,         port,    '
                         'rx_pkts,  rx_bytes, rx_error,'
                         'tx_pkts,  tx_bytes, tx_error,')
        for stat in sorted(body, key=attrgetter('port_no')):
            self.logger.info('%016x, %8x, %8d, %8d, %8d, %8d, %8d, %8d,',
                             ev.msg.datapath.id, stat.port_no,
                             stat.rx_packets, stat.rx_bytes, stat.rx_errors,
                             stat.tx_packets, stat.tx_bytes, stat.tx_errors)
        self.logger.info('end')

    @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
    def _flow_stats_reply_handler(self, ev):
        body = ev.msg.body
        self.logger.info('start')
        for stat in [flow for flow in body if flow.priority == 255]:
                    self.flowStats[stat.match["in_port"]] = FlowStats(
                        stat.match["in_port"],
                        stat.match["eth_src"],
                        stat.match["eth_dst"],
                        stat.match["ipv4_dst"],
                        stat.packet_count,
                        stat.byte_count)
                    # MINDCTRL functions
                    f = Flow(fid=self.flowCounter, srcIP=stat.match["ipv4_src"], dstIP=stat.match["ipv4_dst"],
                             srcPort=stat.match["tcp_src"], dstPort=["tcp_dst"])
                    f['size'] = self.controller.flowSizeEstm.predict(f)
                    self.controller.flowDB.reg(self.flowCounter, f)
                    # update coflow id
                    self.controller.flowDB = self.controller.coflowid(self.controller.getFlows())
                    # place flow on path
                    self.controller.flowDB = self.controller.allocator.placeFlow(self.flowCounter,
                                                                                 self.controller.getPaths(
                                                                                     self.controller.getServerId(
                                                                                         stat.match["ipv4_src"]
                                                                                     ),
                                                                                     self.controller.getServerId(
                                                                                         stat.match["ipv4_dst"]
                                                                                     )
                                                                                 ),
                                                                                 self.controller.getFlows())
                    self.controller.flowDB = self.controller.allocator.placeAllCoflows(self.controller.getCores())
                    self.flowCounter += 1
