from ryu.base.app_manager import RyuApp	        
from ryu.controller.ofp_event import EventOFPSwitchFeatures
from ryu.controller.ofp_event import EventOFPPacketIn
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.ofproto.ofproto_v1_2 import OFPG_ANY
from ryu.ofproto.ofproto_v1_3 import OFP_VERSION
from ryu.lib.mac import haddr_to_bin
from ryu.lib.ip import ipv4_to_str
from ryu.lib.ip import ipv4_to_bin
from ryu.lib.packet import packet
from ryu.lib.mac import haddr_to_str
from ryu.ofproto import inet
import struct
import array


class L2Switch(RyuApp):
    OFP_VERSIONS = [OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(L2Switch, self).__init__(*args, **kwargs)

    @set_ev_cls(EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        """Handle switch features reply to install table miss flow entries."""
        datapath = ev.msg.datapath
        [self.install_table_miss(datapath, n) for n in [0, 1]]

    def create_match(self, parser, fields):
        """Create OFP match struct from the list of fields."""
	#print len(fields
        match = parser.OFPMatch()
        for (field, value) in fields.iteritems():
            #print field, value
            match.append_field(field, value)
            
        return match

        
    def create_flow_mod(self, datapath, table_id, priority,
                         match, instructions):
        """Create OFP flow mod message."""
        ofproto = datapath.ofproto
        flow_mod = datapath.ofproto_parser.OFPFlowMod(datapath, 0, 0, table_id,
                                                      ofproto.OFPFC_ADD, 0, 0,
                                                      priority,ofproto.OFPCML_NO_BUFFER,
                                                      ofproto.OFPP_ANY,
                                                      OFPG_ANY, 0, 
                                                      match, instructions)
        return flow_mod
    '''def create_field(self, datapath, field):
        ofproto = datapath.ofproto
        sfield = datapath.ofproto_parser.OFPActionSetField(field)
        return sfield '''
    def install_table_miss(self, datapath, table_id):
        """Create and install table miss flow entries."""
        print "there is a table miss"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        output = parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                        ofproto.OFPCML_NO_BUFFER)
        write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS,
                                             [output])
        empty_match = parser.OFPMatch()
        instructions = [write]
        flow_mod = self.create_flow_mod(datapath, table_id, 0, 
                                        empty_match, instructions)
        datapath.send_msg(flow_mod)

    
    @set_ev_cls(EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        """Handle packet_in events."""
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        table_id = msg.table_id
        fields = msg.match.fields
        
        # Extract fields
        for f in fields:
	    if f.header == ofproto.OXM_OF_IN_PORT:
                in_port = f.value
            elif f.header == ofproto.OXM_OF_ETH_SRC:
                eth_src = f.value
            elif f.header == ofproto.OXM_OF_ETH_DST:
                eth_dst = f.value
	    elif f.header == ofproto.OXM_OF_ETH_TYPE:
                eth_type = f.value
                #print hex(eth_type)
            #Test for other fields
            elif f.header == ofproto.OXM_OF_IP_PROTO:
		ip_proto = f.value
                #print  ip_proto
            elif f.header == ofproto.OXM_OF_ICMPV4_TYPE:
                icmpv4_type = f.value     
            elif f.header == ofproto.OXM_OF_IPV4_SRC:
                ipv4_src = f.value
            elif f.header == ofproto.OXM_OF_IPV4_DST:
                ipv4_dst = f.value
            elif f.header == ofproto.OXM_OF_TCP_SRC:
                tcp_src = f.value
            elif f.header == ofproto.OXM_OF_TCP_DST:
                tcp_dst = f.value
            elif f.header == ofproto.OXM_OF_UDP_SRC:
                udp_src = f.value
            elif f.header == ofproto.OXM_OF_UDP_DST:
                udp_dst = f.value
                  
        # Install flow entries
        if table_id == 0:
                                
		if  (hex(eth_type)) == '0x806':
              		print "ARP"
              		self.install_src_entry(datapath, in_port, eth_src)
              		self.install_dst_entry(datapath, in_port, eth_src)
		elif (hex(eth_type)) == '0x800': 
              		print "ip"        
              		if(ip_proto == 1):
                                print "icmp"
                    		self.install_icmpsrc_entry(datapath, in_port, eth_src, eth_type, ip_proto, ipv4_src)
                    		self.install_icmpdst_entry(datapath, in_port, eth_src, ipv4_src)
                	
        #Flood
    
	#self.flood(datapath, msg.data)
      
    def install_src_entry(self, datapath, in_port, eth_src):
        """Install flow entry matching on eth_src in table 0."""
        print "a"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = self.create_match(parser,
                                  {ofproto.OXM_OF_IN_PORT: in_port,
                                   ofproto.OXM_OF_ETH_SRC: eth_src 
                                   })
        goto = parser.OFPInstructionGotoTable(1)
        flow_mod = self.create_flow_mod(datapath, 0, 123, match, [goto])
        datapath.send_msg(flow_mod)

    def install_dst_entry(self, datapath, in_port, eth_src):
        """Install flow entry matching on eth_dst in table 1."""
        print "b"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = self.create_match(parser, 
				 {ofproto.OXM_OF_ETH_DST: eth_src
				  })
        output = parser.OFPActionOutput(in_port, ofproto.OFPCML_NO_BUFFER)
        write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS,
                                             [output])
        flow_mod = self.create_flow_mod(datapath, 1, 123, match, [write])
        datapath.send_msg(flow_mod)
    #for ICMP
    def install_icmpsrc_entry(self, datapath, in_port, eth_src, eth_type, ip_proto, ipv4_src):
        """Install flow entry matching on ip_proto in table 0."""
        print "c"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = self.create_match(parser,
                                  {ofproto.OXM_OF_IN_PORT: in_port,
				   ofproto.OXM_OF_ETH_SRC: eth_src,
				   ofproto.OXM_OF_ETH_TYPE: eth_type,
                                   ofproto.OXM_OF_IP_PROTO: ip_proto,
                                   ofproto.OXM_OF_IPV4_SRC: ipv4_src
                                   })
               
        goto = parser.OFPInstructionGotoTable(1)
        flow_mod = self.create_flow_mod(datapath, 0, 126, match, [goto])
        datapath.send_msg(flow_mod)

    def install_icmpdst_entry(self, datapath, in_port, eth_src, ipv4_src):
        """Install flow entry matching on ip_proto in table 1."""
        print "d"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        match = self.create_match(parser, 
				  {ofproto.OXM_OF_ETH_DST: eth_src,
                                   ofproto.OXM_OF_IPV4_DST: ipv4_src 
                                   })
        output = parser.OFPActionOutput(in_port, ofproto.OFPCML_NO_BUFFER)
        write = parser.OFPInstructionActions(ofproto.OFPIT_WRITE_ACTIONS, [output])
        
        flow_mod = self.create_flow_mod(datapath, 1, 126, match, [write])
        datapath.send_msg(flow_mod)
    
    def flood(self, datapath, data):
        """Send a packet_out with output to all ports."""
        print "Flooding now"
        parser = datapath.ofproto_parser
        ofproto = datapath.ofproto
        output_all = parser.OFPActionOutput(ofproto.OFPP_ALL,
                                            ofproto.OFPCML_NO_BUFFER)
        packet_out = parser.OFPPacketOut(datapath, 0xffffffff,
                                         ofproto.OFPP_CONTROLLER,
                                         [output_all], data)
        datapath.send_msg(packet_out)

