# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import matplotlib.pyplot as plt
from base64 import b64encode
from io import BytesIO

from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.app.wsgi import ControllerBase
from ryu.app.wsgi import Response
from ryu.app.wsgi import route
from ryu.app.wsgi import WSGIApplication
from ryu.lib import dpid as dpid_lib

import simple_switch_13

simple_switch_instance_name = 'simple_switch_api_app'
url = '/simpleswitch/mactable/{dpid}'


class SimpleSwitchRest13(simple_switch_13.SimpleSwitch13):

    _CONTEXTS = {'wsgi': WSGIApplication}

    def __init__(self, *args, **kwargs):
        super(SimpleSwitchRest13, self).__init__(*args, **kwargs)
        self.switches = {}
        wsgi = kwargs['wsgi']
        wsgi.register(SimpleSwitchController,
                      {simple_switch_instance_name: self})

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        super(SimpleSwitchRest13, self).switch_features_handler(ev)
        datapath = ev.msg.datapath
        self.switches[datapath.id] = datapath
        self.mac_to_port.setdefault(datapath.id, {})

    def set_mac_to_port(self, dpid, entry):
        mac_table = self.mac_to_port.setdefault(dpid, {})
        datapath = self.switches.get(dpid)

        entry_port = entry['port']
        entry_mac = entry['mac']

        if datapath is not None:
            parser = datapath.ofproto_parser
            if entry_port not in mac_table.values():

                for mac, port in mac_table.items():

                    # from known device to new device
                    actions = [parser.OFPActionOutput(entry_port)]
                    match = parser.OFPMatch(in_port=port, eth_dst=entry_mac)
                    self.add_flow(datapath, 1, match, actions)

                    # from new device to known device
                    actions = [parser.OFPActionOutput(port)]
                    match = parser.OFPMatch(in_port=entry_port, eth_dst=mac)
                    self.add_flow(datapath, 1, match, actions)

                mac_table.update({entry_mac: entry_port})
        return mac_table


class SimpleSwitchController(ControllerBase):
    img_html = '<img src="data:image/png;base64,%s"/>'

    def __init__(self, req, link, data, **config):
        super(SimpleSwitchController, self).__init__(req, link, data, **config)
        self.simple_switch_app = data[simple_switch_instance_name]

    @route('simpleswitch', url, methods=['GET'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def list_mac_table(self, req, **kwargs):

        simple_switch = self.simple_switch_app
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])

        if dpid not in simple_switch.mac_to_port:
            return Response(status=404)

        mac_table = simple_switch.mac_to_port.get(dpid, {})
        body = json.dumps(mac_table)
        return Response(content_type='application/json', body=body)

    @route('simpleswitch', url, methods=['PUT'],
           requirements={'dpid': dpid_lib.DPID_PATTERN})
    def put_mac_table(self, req, **kwargs):

        simple_switch = self.simple_switch_app
        dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
        try:
            new_entry = req.json if req.body else {}
        except ValueError:
            raise Response(status=400)

        if dpid not in simple_switch.mac_to_port:
            return Response(status=404)

        try:
            mac_table = simple_switch.set_mac_to_port(dpid, new_entry)
            body = json.dumps(mac_table)
            return Response(content_type='application/json', body=body)
        except Exception as e:
            return Response(status=500)

    @route("hello", "/mac_to_port", methods=['GET'])
    def get_mac_to_port(self, req, **kwargs):
        return json.dumps(self.simple_switch_app.mac_to_port)

    @route("packet_stat", "/stat", methods=['GET'])
    def get_packet_stat(self, req, **kwargs):
        try:
            body = json.dumps(self.simple_switch_app.packet_stat)

            pkt_types = list(self.simple_switch_app.packet_stat.keys())
            pkt_nums = list(self.simple_switch_app.packet_stat.values())
            fig = plt.figure(figsize=(16,9))
            plt.bar(pkt_types, pkt_nums)

            fig_data = BytesIO()
            fig.savefig(fig_data, format='png')
            body = self.img_html % b64encode(fig_data.getvalue()).decode('utf-8').replace(' ','')
            fig_data.close()
            return body
            # return Response(content_type='image/png', body=body)
        except Exception as e:
            print(e)
            return Response(status=500)

    @route("main", "/main", methods=['GET'])
    def get_main(self, req, **kwargs):
        body = self.get_packet_stat(req, **kwargs)
        html ="<!DOCTYPE html>\r\n" +\
              "<html>\r\n" +\
              "\t<head>" +\
              "\t\t<style>" +\
              "\t\t\timg {width:1000px}" +\
              "\t\t</style>" +\
              "\t</head>" +\
              "\t<body  id='container' onload = 'javascript:AutoRefresh();'>\r\n" +\
              "\t\t<h1>OVS-Exercise</h1>\r\n" + "<div>" + body + "</div>" +\
              "\t</body>\r\n" +\
              "\r\n" +\
              "\t<script type='text/javascript'>\r\n" +\
              "\t\tsetInterval(AutoRefresh, 1000);" +\
              "\t\tfunction AutoRefresh(){var xhttp = new XMLHttpRequest();xhttp.onreadystatechange = function() {if (this.readyState == 4 && this.status == 200) {document.getElementById('container').innerHTML = this.responseText;}};xhttp.open('GET', './main', true);xhttp.send();}\r\n" +\
              "\t</script>\r\n" +\
              "</html>"
        return html
