from ryu.controller import handler
from ryu.controller import dpset
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3 # or ofproto_v1_3
#from ryu.controller import ofp_event
#from ryu.controller.handler import MAIN_DISPATCHER
#from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ether
#from ryu.ofproto import inet
from ryu.lib import mac
#from ryu.lib import ip

class L2Switch(app_manager.RyuApp):
    _CONTEXTS = {
        'dpset': dpset.DPSet,
        }
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] # or ofproto_v1_3.OFP_VERSION

    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)

    def clean_tables(self, dp):
        match = dp.ofproto_parser.OFPMatch()
        m = dp.ofproto_parser.OFPFlowMod(dp, 0, 0, dp.ofproto.OFPTT_ALL,
                                         dp.ofproto.OFPFC_DELETE,
                                         0, 0, 0, 0xffffffff,
                                         dp.ofproto.OFPP_ANY,
                                         dp.ofproto.OFPG_ANY,
                                         0, match, [])
        dp.send_msg(m)

    def add_flow1(self, datapath, my_mac, eth_type, vlan_vid, ip_dst,
                  eth_dst, mpls_label, outport):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Flow 1: MPLS encap
        # match: eth_dst=MYMAC, eth_type=0x0800 (IPv4) or 0x86dd (IPv6)
        # vlan_vid (excact), ip_dst or ipv6_dst
        # action: set-field:eth_dst, set-field:eth_src=MYMAC,
        # pop_vlan, dec_ip_ttl, push_mpls, set-field:mpls_label, output:port

        actions = []

        field = ofproto.OXM_OF_ETH_DST
        dl_dst = eth_dst
        value = mac.haddr_to_bin(dl_dst)
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        field = ofproto.OXM_OF_ETH_SRC
        dl_src = my_mac
        value = mac.haddr_to_bin(dl_src)
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        actions.append(parser.OFPActionPopVlan())

        actions.append(parser.OFPActionDecNwTtl())

        ethertype=ether.ETH_TYPE_MPLS
        actions.append(parser.OFPActionPushMpls(ethertype))

        field = ofproto.OXM_OF_MPLS_LABEL
        value = mpls_label
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        out_port=outport
        actions.append(parser.OFPActionOutput(out_port))

        inst=[parser.OFPInstructionActions(
            ofproto.OFPIT_APPLY_ACTIONS, actions)]

        if (eth_type == ether.ETH_TYPE_IP):
            match = parser.OFPMatch(
            eth_dst=my_mac,
            eth_type=eth_type,
            vlan_vid=vlan_vid,
            ipv4_dst=ip_dst)
        elif (eth_type == ether.ETH_TYPE_IPV6):
            match = parser.OFPMatch(
            eth_dst=my_mac,
            eth_type=eth_type,
            vlan_vid=vlan_vid,
            ipv6_dst=ip_dst)

        mod = parser.OFPFlowMod(
            datapath=datapath, table_id=0,cookie=0, cookie_mask=0,
            command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=65535, buffer_id=0xffffffff, out_port=ofproto.OFPP_ANY,
            out_group=0xffffffff, flags=0,
            match=match, instructions=inst)
        datapath.send_msg(mod)

    def add_flow2(self, datapath, inport, my_mac, mpls_label, eth_dst, vlan_vid, outport):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Flow 2: MPLPS decap
        # match: in_port, eth_dst=MYMAC, eth_type=0x8847, mpls_label (exact)
        # action: set-field:eth_dst, set-field:eth_src=MYMAC, pop_mpls, dec_ip_ttl,
        # push_vlan, set-field:vlan_vid, output:port

        actions = []

        field = ofproto.OXM_OF_ETH_DST
        dl_dst = eth_dst
        value = mac.haddr_to_bin(dl_dst)
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        field = ofproto.OXM_OF_ETH_SRC
        dl_src = my_mac
        value = mac.haddr_to_bin(dl_src)
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        ethertype=ether.ETH_TYPE_MPLS
        actions.append(parser.OFPActionPopMpls(ethertype))

        actions.append(parser.OFPActionDecNwTtl())

        ethertype=ether.ETH_TYPE_8021Q
        actions.append(parser.OFPActionPushVlan(ethertype))

        field = ofproto.OXM_OF_VLAN_VID
        value = vlan_vid
        f = parser.OFPMatchField.make(field, value)
        actions.append(parser.OFPActionSetField(f))

        out_port=outport
        actions.append(parser.OFPActionOutput(out_port))

        inst=[parser.OFPInstructionActions(
            ofproto.OFPIT_APPLY_ACTIONS, actions)]

        match = parser.OFPMatch(
            in_port=inport,
            eth_dst=my_mac,
            eth_type=ether.ETH_TYPE_MPLS,
            mpls_label=mpls_label),

        mod = parser.OFPFlowMod(
            datapath=datapath, table_id=0,cookie=0, cookie_mask=0,
            command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=65535, buffer_id=0xffffffff, out_port=ofproto.OFPP_ANY,
            out_group=0xffffffff, flags=0,
            match=match, instructions=inst)
        datapath.send_msg(mod)


    @handler.set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
    def handler_datapath(self, ev):
        print "datapath join/leave"
        dp = ev.dp

        self.clean_tables(dp)
        self.add_flow1(dp,
                       my_mac='70:72:cf:9f:83:2e',
                       eth_type=ether.ETH_TYPE_IP,
                       vlan_vid=0x3ef,
                       ip_dst='192.168.100.200',
                       eth_dst='aa:bb:cc:dd:ee:ff',
                       mpls_label=0x10,
                       outport=2)

        self.add_flow2(dp,
                       inport=2,
                       my_mac='70:72:cf:9f:83:2e',
                       mpls_label=0x20,
                       eth_dst='00:11:22:33:44:55',
                       vlan_vid=0x100,
                       outport=3)
