import math
from collections import OrderedDict

from migen.fhdl.std import *
from migen.fhdl.decorators import ModuleDecorator
from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.record import *
from migen.genlib.fsm import FSM, NextState
from migen.genlib.misc import chooser, FlipFlop, Counter, Timeout
from migen.flow.actor import *
from migen.flow.plumbing import Buffer
from migen.actorlib.structuring import Converter, Pipeline
from migen.actorlib.fifo import SyncFIFO, AsyncFIFO
from migen.bank.description import *

eth_mtu = 1532
eth_min_len = 46
eth_interpacket_gap = 12
eth_preamble = 0xD555555555555555
buffer_depth = 2**log2_int(eth_mtu, need_pow2=False)

class HField():
	def __init__(self, byte, offset, width):
		self.byte = byte
		self.offset = offset
		self.width = width

ethernet_type_ip = 0x800
ethernet_type_arp = 0x806

mac_header_len = 14
mac_header = {
	"target_mac":		HField(0,  0, 48),
	"sender_mac":		HField(6,  0, 48),
	"ethernet_type":	HField(12, 0, 16)
}

arp_hwtype_ethernet = 0x0001
arp_proto_ip = 0x0800
arp_opcode_request = 0x0001
arp_opcode_reply = 0x0002

arp_header_len = 28
arp_header = {
	"hwtype":			HField( 0,  0, 16),
	"proto":			HField( 2,  0, 16),
	"hwsize":			HField( 4,  0, 8),
	"protosize":		HField( 5,  0, 8),
	"opcode":			HField( 6,  0, 16),
	"sender_mac":		HField( 8,  0, 48),
	"sender_ip":		HField(14,  0, 32),
	"target_mac":		HField(18,  0, 48),
	"target_ip":		HField(24,  0, 32)
}

ipv4_header_len = 20
ipv4_header = {
	"ihl":				HField(0,  0, 4),
	"version":			HField(0,  4, 4),
	"total_length":		HField(2,  0, 16),
	"identification":	HField(4,  0, 16),
	"ttl":				HField(8,  0, 8),
	"protocol":			HField(9,  0, 8),
	"checksum":			HField(10,  0, 16),
	"sender_ip":		HField(12,  0, 32),
	"target_ip":		HField(16,  0, 32)
}

icmp_header_len = 8
icmp_header = {
	"msgtype":		HField( 0,  0, 8),
	"code":			HField( 1,  0, 8),
	"checksum":		HField( 2,  0, 16),
	"quench":		HField( 4,  0, 32)
}
icmp_protocol = 0x01

udp_header_len = 8
udp_header = {
	"src_port":		HField( 0,  0, 16),
	"dst_port":		HField( 2,  0, 16),
	"length":		HField( 4,  0, 16),
	"checksum":		HField( 6,  0, 16)
}

udp_protocol = 0x11

etherbone_magic = 0x4e6f
etherbone_version = 1
etherbone_packet_header_len = 8
etherbone_packet_header = {
	"magic":		HField( 0,  0, 16),

	"version":		HField( 2,  4, 4),
	"nr":			HField( 2,  2, 1),
	"pr":			HField( 2,  1, 1),
	"pf":			HField( 2,  0, 1),

	"addr_size":	HField( 3,  4, 4),
	"port_size":	HField( 3,  0, 4)
}

etherbone_record_header_len = 4
etherbone_record_header = {
	"bca":			HField( 0,  0, 1),
	"rca":			HField( 0,  1, 1),
	"rff":			HField( 0,  2, 1),
	"cyc":			HField( 0,  4, 1),
	"wca":			HField( 0,  5, 1),
	"wff":			HField( 0,  6, 1),

	"byte_enable":	HField( 1,  0, 8),

	"wcount":		HField( 2,  0, 8),

	"rcount":		HField( 3,  0, 8)
}

def reverse_bytes(v):
	n = math.ceil(flen(v)/8)
	r = []
	for i in reversed(range(n)):
		r.append(v[i*8:min((i+1)*8, flen(v))])
	return Cat(iter(r))

# layouts
def _layout_from_header(header):
	_layout = []
	for k, v in sorted(header.items()):
		_layout.append((k, v.width))
	return _layout

def _remove_from_layout(layout, *args):
	r = []
	for f in layout:
		remove = False
		for arg in args:
			if f[0] == arg:
				remove = True
		if not remove:
			r.append(f)
	return r
def eth_phy_description(dw):
	payload_layout = [
		("data", dw),
		("last_be", dw//8),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, packetized=True)

def eth_mac_description(dw):
	payload_layout = _layout_from_header(mac_header) + [
		("data", dw),
		("last_be", dw//8),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, packetized=True)

def eth_arp_description(dw):
	param_layout = _layout_from_header(arp_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

arp_table_request_layout = [
	("ip_address", 32)
]

arp_table_response_layout = [
	("failed", 1),
	("mac_address", 48)
]

def eth_ipv4_description(dw):
	param_layout = _layout_from_header(ipv4_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_ipv4_user_description(dw):
	param_layout = [
		("length", 16),
		("protocol", 8),
		("ip_address", 32)
	]
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def convert_ip(s):
	ip = 0
	for e in s.split("."):
		ip = ip << 8
		ip += int(e)
	return ip

def eth_icmp_description(dw):
	param_layout = _layout_from_header(icmp_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_icmp_user_description(dw):
	param_layout = _layout_from_header(icmp_header) + [
		("ip_address", 32),
		("length", 16)
	]
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_udp_description(dw):
	param_layout = _layout_from_header(udp_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_udp_user_description(dw):
	param_layout = [
		("src_port", 16),
		("dst_port", 16),
		("ip_address", 32),
		("length", 16)
	]
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_etherbone_packet_description(dw):
	param_layout = _layout_from_header(etherbone_packet_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_etherbone_packet_user_description(dw):
	param_layout = _layout_from_header(etherbone_packet_header)
	param_layout = _remove_from_layout(param_layout, "magic", "portsize", "addrsize", "version")
	param_layout += eth_udp_user_description(dw).param_layout
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_etherbone_record_description(dw):
	param_layout = _layout_from_header(etherbone_record_header)
	payload_layout = [
		("data", dw),
		("error", dw//8)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_etherbone_mmap_description(dw):
	param_layout = [
		("we", 1),
		("count", 8),
		("base_addr", 32),
		("be", dw//8)
	]
	payload_layout = [
		("addr", 32),
		("data", dw)
	]
	return EndpointDescription(payload_layout, param_layout, packetized=True)

def eth_tty_description(dw):
	payload_layout = [("data", dw)]
	return EndpointDescription(payload_layout, packetized=False)