use std::{io, path, fs};
use std::collections::{HashMap, BTreeSet};
use std::collections::hash_map::Entry;
use std::net::SocketAddr;
use std::cmp::{PartialOrd, Ord, Ordering};
use csv;
use message::common::{Services, NetAddress};
use message::types::addr::AddressEntry;
use util::time::{Time, RealTime};
use util::InternetProtocol;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Node {
	/// Node address.
	addr: SocketAddr,
	/// Timestamp of last interaction with a node.
	time: i64,
	/// Services supported by the node.
	services: Services,
	/// Node failures counter.
	failures: u32,
}

impl Node {
	pub fn address(&self) -> SocketAddr {
		self.addr
	}
}

impl From<AddressEntry> for Node {
	fn from(entry: AddressEntry) -> Self {
		Node {
			addr: SocketAddr::new(entry.address.address.into(), entry.address.port.into()),
			time: entry.timestamp as i64,
			services: entry.address.services,
			failures: 0,
		}
	}
}

impl From<Node> for AddressEntry {
	fn from(node: Node) -> Self {
		AddressEntry {
			timestamp: node.time as u32,
			address: NetAddress {
				services: node.services,
				address: node.addr.ip().into(),
				port: node.addr.port().into(),
			}
		}
	}
}

#[derive(Debug, PartialEq, Eq, Clone)]
struct NodeByScore(Node);

impl From<Node> for NodeByScore {
	fn from(node: Node) -> Self {
		NodeByScore(node)
	}
}

impl PartialOrd for NodeByScore {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		if self.0.failures == other.0.failures {
			if other.0.time == self.0.time {
				other.0.partial_cmp(&self.0)
			}
			else {
				other.0.time.partial_cmp(&self.0.time)
			}
		} else {
			self.0.failures.partial_cmp(&other.0.failures)
		}
	}
}

impl Ord for NodeByScore {
	fn cmp(&self, other: &Self) -> Ordering {
		if self.0.failures == other.0.failures {
			if other.0.time == self.0.time {
				other.0.cmp(&self.0)
			}
			else {
				other.0.time.cmp(&self.0.time)
			}
		} else {
			self.0.failures.cmp(&other.0.failures)
		}
	}
}

#[derive(Debug, PartialEq, Eq, Clone)]
struct NodeByTime(Node);

impl From<Node> for NodeByTime {
	fn from(node: Node) -> Self {
		NodeByTime(node)
	}
}

impl PartialOrd for NodeByTime {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		if other.0.time == self.0.time {
			other.0.partial_cmp(&self.0)
		}
		else {
			other.0.time.partial_cmp(&self.0.time)
		}
	}
}

impl Ord for NodeByTime {
	fn cmp(&self, other: &Self) -> Ordering {
		if other.0.time == self.0.time {
			other.0.cmp(&self.0)
		}
		else {
			other.0.time.cmp(&self.0.time)
		}
	}
}

impl Ord for Node {
	fn cmp(&self, other: &Self) -> Ordering {
		// some ordering using address as unique key
		match self.addr {
			SocketAddr::V4(self_addr) => match other.addr {
				SocketAddr::V4(other_addr) => {
					let self_port = self_addr.port();
					let other_port = other_addr.port();
					if self_port == other_port {
						self_addr.ip().cmp(other_addr.ip())
					}
					else {
						self_port.cmp(&other_port)
					}
				},
				SocketAddr::V6(_) => Ordering::Less,
			},
			SocketAddr::V6(self_addr) => match other.addr {
				SocketAddr::V4(_) => Ordering::Greater,
				SocketAddr::V6(other_addr) => {
					let self_port = self_addr.port();
					let other_port = other_addr.port();
					if self_port == other_port {
						self_addr.ip().cmp(other_addr.ip())
					}
					else {
						self_port.cmp(&other_port)
					}
				},
			},
		}
	}
}

impl PartialOrd for Node {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

#[derive(Default, Debug)]
pub struct NodeTable<T = RealTime> where T: Time {
	/// Time source.
	time: T,
	/// Nodes by socket address.
	by_addr: HashMap<SocketAddr, Node>,
	/// Nodes sorted by score.
	by_score: BTreeSet<NodeByScore>,
	/// Nodes sorted by time.
	by_time: BTreeSet<NodeByTime>,
}

impl NodeTable {
	/// Opens a file loads node_table from it.
	pub fn from_file<P>(path: P) -> Result<Self, io::Error> where P: AsRef<path::Path> {
		fs::OpenOptions::new()
			.create(true)
			.read(true)
			// without opening for write, mac os returns os error 22
			.write(true)
			.open(path)
			.and_then(Self::load)
	}

	/// Saves node table to file
	pub fn save_to_file<P>(&self, path: P) -> Result<(), io::Error> where P: AsRef<path::Path> {
		fs::File::create(path).and_then(|file| self.save(file))
	}
}

impl<T> NodeTable<T> where T: Time {
	/// Inserts new address and services pair into NodeTable.
	pub fn insert(&mut self, addr: SocketAddr, services: Services) {
		let now = self.time.get().sec;
		match self.by_addr.entry(addr) {
			Entry::Occupied(mut entry) => {
				let old = entry.get_mut();
				assert!(self.by_score.remove(&old.clone().into()));
				assert!(self.by_time.remove(&old.clone().into()));
				old.time = now;
				old.services = services;
				self.by_score.insert(old.clone().into());
				self.by_time.insert(old.clone().into());
			},
			Entry::Vacant(entry) => {
				let node = Node {
					addr: addr,
					time: now,
					services: services,
					failures: 0,
				};
				self.by_score.insert(node.clone().into());
				self.by_time.insert(node.clone().into());
				entry.insert(node);
			}
		}
	}

	/// Inserts many new addresses into node table.
	/// Used in `addr` request handler.
	/// Discards all nodes with timestamp newer than current time.
	pub fn insert_many(&mut self, nodes: Vec<Node>) {
		// discard all nodes with timestamp newer than current time.
		let now = self.time.get().sec;
		let iter = nodes.into_iter()
			.filter(|node| node.time <= now);

		// iterate over the rest
		for node in iter {
			match self.by_addr.entry(node.addr) {
				Entry::Occupied(mut entry) => {
					let old = entry.get_mut();
					// we've already seen this node
					if old.time < node.time {
						assert!(self.by_score.remove(&old.clone().into()));
						assert!(self.by_time.remove(&old.clone().into()));
						// update node info
						old.time = node.time;
						old.services = node.services;
						self.by_score.insert(old.clone().into());
						self.by_time.insert(old.clone().into());
					}
				},
				Entry::Vacant(entry)=> {
					// it's first time we see this node
					self.by_score.insert(node.clone().into());
					self.by_time.insert(node.clone().into());
					entry.insert(node);
				}
			}
		}
	}

	/// Returnes most reliable nodes with desired services.
	pub fn nodes_with_services(&self, services: &Services, protocol: InternetProtocol, limit: usize) -> Vec<Node> {
		self.by_score.iter()
			.filter(|node| protocol.is_allowed(&node.0.addr))
			.filter(|node| node.0.services.includes(services))
			.map(|node| node.0.clone())
			.take(limit)
			.collect()
	}

	/// Returns most recently active nodes.
	///
	/// The documenation says:
	/// "Non-advertised nodes should be forgotten after typically 3 hours"
	/// but bitcoin client still advertises them even after a month.
	/// Let's do the same.
	///
	/// https://en.bitcoin.it/wiki/Protocol_documentation#addr
	pub fn recently_active_nodes(&self, protocol: InternetProtocol) -> Vec<Node> {
		self.by_time.iter()
			.filter(|node| protocol.is_allowed(&node.0.addr))
			.map(|node| node.0.clone())
			.take(1000)
			.collect()
	}

	/// Marks address as recently used.
	pub fn note_used(&mut self, addr: &SocketAddr) {
		if let Some(ref mut node) = self.by_addr.get_mut(addr) {
			assert!(self.by_score.remove(&node.clone().into()));
			assert!(self.by_time.remove(&node.clone().into()));
			node.time = self.time.get().sec;
			self.by_score.insert(node.clone().into());
			self.by_time.insert(node.clone().into());
		}
	}

	/// Notes failure.
	pub fn note_failure(&mut self, addr: &SocketAddr) {
		if let Some(ref mut node) = self.by_addr.get_mut(addr) {
			assert!(self.by_score.remove(&node.clone().into()));
			assert!(self.by_time.remove(&node.clone().into()));
			node.failures += 1;
			self.by_score.insert(node.clone().into());
			self.by_time.insert(node.clone().into());
		}
	}

	/// Save node table in csv format.
	pub fn save<W>(&self, write: W) -> Result<(), io::Error> where W: io::Write {
		let mut writer = csv::Writer::from_writer(write)
			.delimiter(b' ');
		let iter = self.by_score.iter()
			.map(|node| &node.0)
			.take(1000);

		let err = || io::Error::new(io::ErrorKind::Other, "Write csv error");

		for n in iter {
			let record = (n.addr.to_string(), n.time, u64::from(n.services), n.failures);
			try!(writer.encode(record).map_err(|_| err()));
		}

		Ok(())
	}

	/// Loads table in from a csv source.
	pub fn load<R>(read: R) -> Result<Self, io::Error> where R: io::Read, T: Default {
		let mut rdr = csv::Reader::from_reader(read)
			.has_headers(false)
			.delimiter(b' ');

		let mut node_table = NodeTable::default();

		let err = || io::Error::new(io::ErrorKind::Other, "Load csv error");

		for row in rdr.decode() {
			let (addr, time, services, failures): (String, i64, u64, u32) = try!(row.map_err(|_| err()));

			let node = Node {
				addr: try!(addr.parse().map_err(|_| err())),
				time: time,
				services: services.into(),
				failures: failures,
			};

			node_table.by_score.insert(node.clone().into());
			node_table.by_time.insert(node.clone().into());
			node_table.by_addr.insert(node.addr, node);
		}

		Ok(node_table)
	}
}

#[cfg(test)]
mod tests {
	use std::net::SocketAddr;
	use message::common::Services;
	use util::InternetProtocol;
	use util::time::{IncrementalTime, ZeroTime};
	use super::NodeTable;

	#[test]
	fn test_node_table_insert() {
		let s0: SocketAddr = "127.0.0.1:8000".parse().unwrap();
		let s1: SocketAddr = "127.0.0.1:8001".parse().unwrap();
		let s2: SocketAddr = "127.0.0.1:8002".parse().unwrap();
		let mut table = NodeTable::<IncrementalTime>::default();
		table.insert(s0, Services::default());
		table.insert(s1, Services::default());
		table.insert(s2, Services::default());
		let nodes = table.nodes_with_services(&Services::default(), InternetProtocol::default(), 2);
		assert_eq!(nodes.len(), 2);
		assert_eq!(nodes[0].addr, s2);
		assert_eq!(nodes[0].time, 2);
		assert_eq!(nodes[0].failures, 0);
		assert_eq!(nodes[1].addr, s1);
		assert_eq!(nodes[1].time, 1);
		assert_eq!(nodes[1].failures, 0);
	}

	#[test]
	fn test_node_table_note() {
		let s0: SocketAddr = "127.0.0.1:8000".parse().unwrap();
		let s1: SocketAddr = "127.0.0.1:8001".parse().unwrap();
		let s2: SocketAddr = "127.0.0.1:8002".parse().unwrap();
		let s3: SocketAddr = "127.0.0.1:8003".parse().unwrap();
		let s4: SocketAddr = "127.0.0.1:8004".parse().unwrap();
		let mut table = NodeTable::<IncrementalTime>::default();
		table.insert(s0, Services::default());
		table.insert(s1, Services::default());
		table.insert(s2, Services::default());
		table.insert(s3, Services::default());
		table.insert(s4, Services::default());
		table.note_used(&s2);
		table.note_used(&s4);
		table.note_used(&s1);
		table.note_failure(&s2);
		table.note_failure(&s3);
		let nodes = table.nodes_with_services(&Services::default(), InternetProtocol::default(), 10);
		assert_eq!(nodes.len(), 5);

		assert_eq!(nodes[0].addr, s1);
		assert_eq!(nodes[0].time, 7);
		assert_eq!(nodes[0].failures, 0);

		assert_eq!(nodes[1].addr, s4);
		assert_eq!(nodes[1].time, 6);
		assert_eq!(nodes[1].failures, 0);

		assert_eq!(nodes[2].addr, s0);
		assert_eq!(nodes[2].time, 0);
		assert_eq!(nodes[2].failures, 0);

		assert_eq!(nodes[3].addr, s2);
		assert_eq!(nodes[3].time, 5);
		assert_eq!(nodes[3].failures, 1);

		assert_eq!(nodes[4].addr, s3);
		assert_eq!(nodes[4].time, 3);
		assert_eq!(nodes[4].failures, 1);

		let nodes = table.recently_active_nodes(InternetProtocol::default());
		assert_eq!(nodes.len(), 5);

		assert_eq!(nodes[0].addr, s1);
		assert_eq!(nodes[0].time, 7);
		assert_eq!(nodes[0].failures, 0);

		assert_eq!(nodes[1].addr, s4);
		assert_eq!(nodes[1].time, 6);
		assert_eq!(nodes[1].failures, 0);

		assert_eq!(nodes[2].addr, s2);
		assert_eq!(nodes[2].time, 5);
		assert_eq!(nodes[2].failures, 1);

		assert_eq!(nodes[3].addr, s3);
		assert_eq!(nodes[3].time, 3);
		assert_eq!(nodes[3].failures, 1);

		assert_eq!(nodes[4].addr, s0);
		assert_eq!(nodes[4].time, 0);
		assert_eq!(nodes[4].failures, 0);
	}

	#[test]
	fn test_node_table_duplicates() {
		let s0: SocketAddr = "127.0.0.1:8000".parse().unwrap();
		let s1: SocketAddr = "127.0.0.1:8001".parse().unwrap();
		let mut table = NodeTable::<ZeroTime>::default();
		table.insert(s0, Services::default());
		table.insert(s1, Services::default());
		table.note_failure(&s0);
		table.note_failure(&s1);
	}

	#[test]
	fn test_save_and_load() {
		let s0: SocketAddr = "127.0.0.1:8000".parse().unwrap();
		let s1: SocketAddr = "127.0.0.1:8001".parse().unwrap();
		let s2: SocketAddr = "127.0.0.1:8002".parse().unwrap();
		let s3: SocketAddr = "127.0.0.1:8003".parse().unwrap();
		let s4: SocketAddr = "127.0.0.1:8004".parse().unwrap();
		let mut table = NodeTable::<IncrementalTime>::default();
		table.insert(s0, Services::default());
		table.insert(s1, Services::default());
		table.insert(s2, Services::default());
		table.insert(s3, Services::default());
		table.insert(s4, Services::default());
		table.note_used(&s2);
		table.note_used(&s4);
		table.note_used(&s1);
		table.note_failure(&s2);
		table.note_failure(&s3);

		let mut db = Vec::new();
		assert_eq!(table.save(&mut db).unwrap(), ());
		let loaded_table = NodeTable::<IncrementalTime>::load(&db as &[u8]).unwrap();
		assert_eq!(table.by_addr, loaded_table.by_addr);
		assert_eq!(table.by_score, loaded_table.by_score);
		assert_eq!(table.by_time, loaded_table.by_time);

		let s = String::from_utf8(db).unwrap();
		assert_eq!(
"127.0.0.1:8001 7 0 0
127.0.0.1:8004 6 0 0
127.0.0.1:8000 0 0 0
127.0.0.1:8002 5 0 1
127.0.0.1:8003 3 0 1
".to_string(), s);
	}
}
