import logging
import time
import itertools
from ryu.controller.handler import CONFIG_DISPATCHER, 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 DONTCARE_STR
from ryu.lib.packet.packet import Packet
from ryu.topology.switches import LLDPPacket
from ryu.topology.switches import Port, PortState
from ryu.topology.switches import PortDataState
from ryu.topology.switches import Link, LinkState
from ryu.topology.switches import Switch
from ryu.lib import hub, mac
from ryu.lib.packet import arp
from ryu.base import app_manager
from ryu.base.app_manager import RyuApp, AppManager,register_app
from ryu.controller import mac_to_port
from ryu.controller.mac_to_port import MacToPortTable
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.mac import haddr_to_bin
from ryu.lib import hub
from ryu.lib.packet import packet, lldp, ipv4, ipv6
from ryu.lib.packet import ethernet, ether_types
from ryu.ofproto import ether
from ryu.ofproto.ether import ETH_TYPE_LLDP
from ryu.topology.api import get_switch, get_link, get_all_link, get_host
from ryu.app.wsgi import ControllerBase
from ryu.topology import event, switches
from ryu.topology.switches import Switch, Switches, PortDataState, Port, Host, HostState
from ryu.controller.dpset import DPSet



LOG = logging.getLogger("appln")
topology_api_instance_name = 'topology_api_app'
topology_switches_instance_name = 'topology_switches_app'
class ProjectController(app_manager.RyuApp):

    def __init__(self, *args, **kwargs):
        super(ProjectController, self).__init__(*args, **kwargs)
        self.mac_to_port = {}
        self.topology_api_app = self
	self.topology_switches_app = self
	self.ofctl_rest_app = self
	self.Mac_table = []
	self.llist = []
        self.nodes = {}
        self.links = {}
        self.no_of_links = 0
        self.dpid_list=[]
        self.link_list = {}
	self.all_link_list = ()
	self.lldp_event = hub.Event()
	self.link_event = hub.Event()
	_Event = []
	self.datapath_list=[]
	self.da_list=[]
	self.close= self
	self.topo_switches = topology_switches_instance_name
	self.ryu_mgr = AppManager.get_instance()
	self.linksrc = []
	self.linkdst = []
	self.links = []
	self.linksr = []
	self.linkdt = []
	self.switches = []
	self.arp_table = []
    	self.is_ac = 0
    	self.s={}
	self.l2= []
	self.reg=self
	self.allst = []
	self.wert= []
    def uninst(self):
	if self.is_ac==1 :
	    self.ryu_mgr.uninstantiate('switches')
	else:
	    return 
    
    def send_portdesc_stats_request(self, datapath):
	ofp_parser= datapath.ofproto_parser
	req = ofp_parser.OFPPortDescStatsRequest(datapath, 0)
	datapath.send_msg(req)

    def add_flow(self, datapath, priority, match, instructions, table_id):
        ofproto = datapath.ofproto
        
        parser = datapath.ofproto_parser
	
        mod = parser.OFPFlowMod(datapath=datapath,table_id=table_id,idle_timeout=0,
	   	                hard_timeout=0,priority=priority, match=match,
                                instructions=instructions)
        
	datapath.send_msg(mod)

    def del_flow(self, datapath, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        
	mod = parser.OFPFlowMod(datapath=datapath,command=ofproto.OFPFC_DELETE,
				out_port=ofproto.OFPP_ANY,
			        priority=ofproto.OFP_DEFAULT_PRIORITY, match=match)

        datapath.send_msg(mod)
    @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER)
    def portdesc_stats_reply_handler(self, ev):
	msg=ev.msg
	ofp = msg.datapath.ofproto
	body = msg.body
	dpid = msg.datapath.id
	ports = []
	llist= []
	for p in ev.msg.body:
            ports.append('port_no=%d hw_addr=%s name=%s config=0x%08x '
                         'state=0x%08x curr=0x%08x advertised=0x%08x '
                         'supported=0x%08x peer=0x%08x curr_speed=%d '
                         'max_speed=%d' %
                         (p.port_no, p.hw_addr,
                          p.name, p.config,
                          p.state, p.curr, p.advertised,
                          p.supported, p.peer, p.curr_speed,
                          p.max_speed))
	    llist.append(([dpid, p.port_no], [p.hw_addr]))
	    
	
	self.logger.info('OFPPortDescStatsReply received:'
                 	 'port_feature = %s dpid = %s', 
			 ports, dpid)
	if llist not in self.llist :
	    self.llist.append(llist)	    	
    
    
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        msg = ev.msg
        self.logger.info('OFPSwitchFeatures received: '
                         '\n\tdatapath_id=0x%016x n_buffers=%d '
                         '\n\tn_tables=%d auxiliary_id=%d '
                         '\n\tcapabilities=0x%08x',
                         msg.datapath_id, msg.n_buffers, msg.n_tables,
                         msg.auxiliary_id, msg.capabilities)

        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)]
	inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
        self.add_flow(datapath, 599, match, inst, 0)
	#self.add_flow(datapath, 599, match, inst, 1)

    @set_ev_cls(event.EventSwitchEnter)
    def _switch_enter_handler(self, ev):
        msg=ev.switch.to_dict()
        dpid=int(msg["dpid"],16)
	switch_list = get_switch(self.topology_api_app, None)
        self.switches=[switch.dp.id for switch in switch_list]
	self.logger.info("switches desc: %s ", switch.dp.id)
	links_list = get_link(self.topology_api_app, None)
	self.links =[([link.src.dpid,link.src.port_no],[link.dst.dpid, link.dst.port_no]) for link in links_list]
	self.linksr =[([link.src.dpid, link.src.port_no]) for link in links_list]
	self.linkdt =[([link.dst.dpid, link.dst.port_no]) for link in links_list]
	
    @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
    def _port_status_handler(self, ev):
        msg = ev.msg
        reason = msg.reason
        port_no = msg.desc.port_no
        dpid = msg.datapath.id
        ofproto = msg.datapath.ofproto
	parser = msg.datapath.ofproto_parser

        reason_dict = {ofproto.OFPPR_ADD: "added",
                       ofproto.OFPPR_DELETE: "deleted",
                       ofproto.OFPPR_MODIFY: "modified", }

        if reason in reason_dict:
	    print "___________flow mod___________"
	    for k in range(len(self.wert)):
	        match = parser.OFPMatch()
	        inst = [parser.OFPInstructionGotoTable(1)]
	        self.add_flow(self.wert[k], 65534, match, inst, 0)    

            print "switch%d: port %s %s" % (dpid, reason_dict[reason], port_no)
        else:
            print "switch%d: Illeagal port state %s %s" % (port_no, reason)
  	
	    

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)

    
    def _packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
	ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        in_port = msg.match['in_port']	
        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocol(ethernet.ethernet)
	switch = self.topo_switches
        dst = eth.dst
        src = eth.src
        dpid = datapath.id
	li=[]
	s_ip=[]
	s_mac=[]
	hst=[]
	Matches= []
	mm = []
	Mac=[]
	fn=[]
	f=[]
	f2=[]
	l2=[]
	sq=[]
	dq=[]
	l1=[]
	f1=[]
	t=[]
	wq=[]
	et =[]
	ret =[]
	dsa =[]
	dew =[]
	ir=[]
	inks=[]
	ip_table=[]
	arp_table=[]
	linksrc=[]
	linkdst=[]
	
	
	self.send_portdesc_stats_request(datapath)
        self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
	
	if self.is_ac==0:
	    links_list = get_link(self.topology_api_app, None)
	    self.links =[([link.src.dpid,link.src.port_no],[link.dst.dpid, link.dst.port_no]) for link in links_list]
	    self.linksr =[([link.src.dpid, link.src.port_no]) for link in links_list]
	    self.linkdt =[([link.dst.dpid, link.dst.port_no]) for link in links_list]
	else:
	    pass

     
	pkt_arp_list = pkt.get_protocols(arp.arp)
	pkt_ipv4_list = pkt.get_protocols(ipv4.ipv4)
	

        if pkt_arp_list:
	    print "datapath id: "+str(dpid)
            print "port: "+str(in_port)

            self.pkt_arp = pkt_arp_list[0]
            print ("pkt_arp: " + str(self.pkt_arp))
            print ("pkt_arp:dst_ip: " + str(self.pkt_arp.dst_ip))
            print ("pkt_arp:src_ip: " + str(self.pkt_arp.src_ip))
            print ("pkt_arp:dst_mac: " + str(self.pkt_arp.dst_mac))
            print ("pkt_arp:src_mac: " + str(self.pkt_arp.src_mac))
	    
	    match =parser.OFPMatch(eth_type=0x800, ipv4_dst=self.pkt_arp.src_ip)
	    actions = [parser.OFPActionOutput(in_port)]
	    inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
	    self.add_flow(datapath, 65534, match, inst, 0)
	        
	    match =parser.OFPMatch(eth_type=0x800, ipv4_dst=self.pkt_arp.src_ip)
	    actions = [parser.OFPActionOutput(in_port)]
	    inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
	    self.add_flow(datapath, 65534, match, inst, 1)	

            d_ip = self.pkt_arp.dst_ip
            s_ip = self.pkt_arp.src_ip

            d_mac = self.pkt_arp.dst_mac
            s_mac = self.pkt_arp.src_mac
	    
            #self.arp_table[pkt_arp_list.src_ip] = src  # ARP learning
            self.logger.info(" ARP: %s %s -> %s %s", self.pkt_arp.src_ip,self.pkt_arp.src_mac, self.pkt_arp.dst_ip,self.pkt_arp.dst_mac)
	    if (self.pkt_arp.src_ip, self.pkt_arp.src_mac, dpid, in_port) not in self.arp_table:
		self.arp_table.append((self.pkt_arp.src_ip, self.pkt_arp.src_mac, dpid, in_port))
	    	if dpid not in self.allst:
		    self.allst.append(dpid)
	
	    if self.arp_handler(msg):
                return 
	    
	for i in range(len(li)): 
	    if d_mac==li[i]:
	        l1.append((d_ip, d_mac, li[i+1]))           
	    elif s_mac==li[i]:
	   	f1.append((s_ip, s_mac, li[i+1]))

	print self.switches
	if dpid not in self.dpid_list and msg.datapath not in self.datapath_list:
	    self.dpid_list.append(dpid)
	    self.datapath_list.append(msg.datapath)
	    self.da_list.append([dpid, msg.datapath])		
	
	for i in self.da_list:
	    for k in i:
		if k not in self.l2:
		    self.l2.append(k)
	print self.l2
	
	for t in self.linksr:
	    if t not in linksrc:
	        linksrc.append(t)
	for g in self.linkdt:
	    if g not in linkdst:
  	        linkdst.append(g)
	f2=self.links[:]

	for r in f2:
	    for t in r:
	        mm.append(t)
	
	d = self.llist[:]


	for l in d:
	    for i in l:
		for t in i:
		    li.append(t)
	
	for i in range(len(linksrc)):
	    for j in range(len(linkdst)):
	        for k in range(len(li)):
		    for z in range(len(self.l2)):
		        if linkdst[j]==li[k] and linksrc[j][0]==self.l2[z]:
			    if (li[k+1], linksrc[j][1], self.l2[z+1]) not in wq:
		                wq.append((li[k+1], linksrc[j][1],self.l2[z+1]))
	for j in range(len(wq)):	
 	    et.append(wq[j][0])
	    dsa.append(wq[j][1])
	    dew.append(wq[j][2])	
	  
	for q in et:
	    for h in q:
	        ret.append(h)
	
	for i in range(len(ret)):
	    for j in range(len(dsa)):
		for k in range(len(dew)):					
		    match = parser.OFPMatch(eth_dst=ret[i])
	    	    actions = [parser.OFPActionOutput(dsa[i])]
		    inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
	    	    self.add_flow(dew[i], 65534, match, inst, 0)	
	
	for i in range(len(self.linksr)):
	    for j in range(len(li)):   
		if self.linksr[i]==li[j]:
		    Matches.append((self.linksr[i],li[j+1]))
		
	for l in Matches:
	    for item in l:
		if item not in Mac:
	            Mac.append(item)
	for i in range(len(mm)):
	    for j in range(len(Mac)):   
	        if mm[i]==Mac[j]:
	            fn.append((Mac[j+1]))
	for i in range(len(linksrc)):
	    for j in range(len(linkdst)):
		if (linksrc[i],linkdst[i]) not in ir:
		    ir.append((linksrc[i],linkdst[i]))

	for xy in ir:
	    inks.append(xy)
		
	
	for y in self.links:
	    for x in y:
		sq.append(x)

	print li
	print self.links
	print inks
	print time.clock()

	if inks==self.links and time.clock() >= 0.3:
	    if self.is_ac==0 :
	        self.is_ac = 1
	        self.uninst()
	        for i in range(len(self.l2)):
		    if dpid ==self.l2[i]:
		        match = parser.OFPMatch(eth_type=0x88cc, eth_dst='01:80:c2:00:00:0e')
		        actions=[]
			inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
		        self.add_flow(self.l2[i+1],65534, match, inst, 0)
	    
	else:
	    pass

	
				        
			
	print self.is_ac
	print self.arp_table
	print linksrc
	print linkdst  
	print time.clock()
	self.linksrc = linksrc[:]
	self.linkdst = linkdst[:]
	self.wert = self.l2[1:][::2]
	for k in range(len(self.wert)):
	    print self.wert[k]	        
	
    def arp_handler(self, msg):
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        in_port = msg.match['in_port']
        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]
        arp_pkt = pkt.get_protocol(arp.arp)
	edst = eth.dst
        esrc = eth.src
	self.s[arp_pkt.src_ip]=esrc
	dpid = datapath.id	
	print self.arp_table	
	print self.s	

	for i in range(len(self.arp_table)):
	    for j in range(len(self.linkdst)):
		for k in range(len(self.linksrc)):
		    if arp_pkt.dst_ip==self.arp_table[i][0]:
		        if self.arp_table[i][2]==self.linkdst[j][0] and dpid==self.linksrc[j][0]:
			    match = parser.OFPMatch(eth_type=0x800, ipv4_dst=arp_pkt.dst_ip)
	    		    actions = [parser.OFPActionOutput(self.linksrc[j][1])]
			    inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
	    		    self.add_flow(datapath, 65534, match, inst, 0)

	
	for i in range(len(self.arp_table)):
	    for j in range(len(self.linkdst)):
		for k in range(len(self.linksrc)):
		    if arp_pkt.dst_ip==self.arp_table[i][0]:
		        if self.arp_table[i][2]==self.linkdst[j][0] and dpid!=self.linksrc[j][0]:
			    match = parser.OFPMatch(eth_type=0x800, ipv4_dst=arp_pkt.dst_ip)
	    		    actions = [parser.OFPActionOutput(self.linkdst[j][1])]
			    inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
      	    		    self.add_flow(datapath, 65534, match, inst, 1)
	
	if len(self.arp_table) >= 2:
	    qoqo = [o for o in self.switches if o not in self.allst]
	    print qoqo 
	    for i in range(len(qoqo)):
		for k in range(len(self.l2)):
		    for j in range(len(self.linksrc)):
			for l in range(len(self.linkdst)):
			    for m in range(len(self.arp_table)):
				if arp_pkt.dst_ip==self.arp_table[m][0]:
				    if self.arp_table[m][2]==self.linkdst[j][0] and qoqo[i]==self.linksrc[j][0] and qoqo[i]==self.l2[k]:
				        match = parser.OFPMatch(eth_type=0x800, ipv4_dst=arp_pkt.dst_ip)
	    		                actions = [parser.OFPActionOutput(self.linksrc[j][1])]
				        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]
	    		                self.add_flow(self.l2[k+1], 65534, match, inst, 1)

	   	       
				
	
        # Try to reply arp request
        if arp_pkt:
            if arp_pkt.opcode == arp.ARP_REQUEST:
                hwtype = arp_pkt.hwtype
                proto = arp_pkt.proto
                hlen = arp_pkt.hlen
                plen = arp_pkt.plen
                arp_src_ip = arp_pkt.src_ip
                arp_dst_ip = arp_pkt.dst_ip
		arp_dst_mac =arp_pkt.dst_mac
	    	arp_src_mac =arp_pkt.src_mac

		
 		if arp_dst_ip in self.s:
                    actions = [parser.OFPActionOutput(in_port)]
                    ARP_Reply = packet.Packet()

                    ARP_Reply.add_protocol(ethernet.ethernet(
                        ethertype=eth.ethertype,
                        dst=esrc,
                        src=self.s[arp_dst_ip]))
                    ARP_Reply.add_protocol(arp.arp(
                        opcode=arp.ARP_REPLY,
                        src_mac=self.s[arp_dst_ip],
                        src_ip=arp_dst_ip,
                        dst_mac=esrc,
                        dst_ip=arp_src_ip))

                    ARP_Reply.serialize()

                    out = parser.OFPPacketOut(
                        datapath=datapath,
                        buffer_id=ofproto.OFP_NO_BUFFER,
                        in_port=ofproto.OFPP_CONTROLLER,
                        actions=actions, data=ARP_Reply.data)
                    datapath.send_msg(out)
                    print "ARP_Reply"
	            return True
		   
        return False
 
