import copy

from pypdevs.DEVS import AtomicDEVS
from pypdevs.infinity import INFINITY
from events import Request
import numpy as np


class Pool(AtomicDEVS):
	def __init__(self, name):
		super(Pool, self).__init__(name)

		self.state = {
			"ships": {},
			"requests": [],
			"delta": 0.,
			"time": 0.
		}

		self.req_in = self.addInPort("req_in")
		self.ship_in = self.addInPort("ship_in")
		self.ship_out_sail = self.addOutPort("ship_out_sail")
		self.ship_out_tug = self.addOutPort("ship_out_tug")

	def timeAdvance(self):
		if len(self.state["requests"]) > 0:
			return self.state["delta"]
		return INFINITY

	def extTransition(self, inputs):
		self.state["time"] += self.elapsed
		if self.req_in in inputs:
			self.state["requests"].append(inputs[self.req_in])
		if self.ship_in in inputs:
			ship = inputs[self.ship_in]
			if ship.id in self.state["ships"]:
				raise ValueError("Impossible entry to pool.")
			self.state["ships"][ship.id] = ship
		return self.state

	def intTransition(self):
		self.state["time"] += self.timeAdvance()
		if len(self.state["requests"]) > 0:
			if self.state["requests"][0].ship in self.state["ships"]:
				req = self.state["requests"].pop(0)
				del self.state["ships"][req.ship]
				self.state["delta"] = 0
			else:
				self.state["delta"] = 0.001
		return self.state

	def outputFnc(self):
		if len(self.state["requests"]) > 0:
			request = self.state["requests"][0]
			if request.ship not in self.state["ships"]:
				print("Cannot find free ship", request.ship)
				return {}
			ship = self.state["ships"][request.ship]
			if ship.position != request.source:
				print("[%s] Ship %s is not at position %s, but at %s" % (self.state["time"], request.ship, request.source, ship.position))
			ship.x = request.distance
			ship.v = request.speed
			ship.position = request.destination
			if request.tugging:
				return {
					self.ship_out_tug: ship
				}
			return {
				self.ship_out_sail: ship
			}
		return {}


class Sailer(AtomicDEVS):
	def __init__(self, name, alpha, beta):
		super(Sailer, self).__init__(name)

		self.alpha = alpha
		self.beta = beta

		self.state = {
			"time": 0,
			"ships": []
		}

		self.ship_in = self.addInPort("ship_in")
		self.ship_out = self.addOutPort("ship_out")

	def timeAdvance(self):
		if len(self.state["ships"]) > 0:
			return self.state["ships"][0].time_until_departure
		return INFINITY

	def update_ships(self, elapsed):
		for ship in self.state["ships"]:
			ship.x -= ship.v * elapsed
			ship.total_distance += ship.v * elapsed
			ship.time_until_departure = round(ship.time_until_departure - elapsed, 6)
			ship.time_used += elapsed

	def extTransition(self, inputs):
		self.state["time"] += self.elapsed
		self.update_ships(self.elapsed)
		if self.ship_in in inputs:
			ship = inputs[self.ship_in]
			# ship.v = np.random.gamma(self.alpha, self.beta)
			ship.time_start = self.state["time"]
			ship.time_until_departure = round(ship.x / ship.v, 6)
			self.state["ships"].append(ship)
		self.state["ships"] = sorted(self.state["ships"], key=lambda s: s.time_until_departure)
		return self.state

	def intTransition(self):
		elapsed = self.timeAdvance()
		self.state["time"] += elapsed
		self.state["ships"].pop(0)
		self.update_ships(elapsed)
		self.state["ships"] = sorted(self.state["ships"], key=lambda s: s.time_until_departure)
		return self.state

	def outputFnc(self):
		if len(self.state["ships"]) > 0:
			elapsed = self.timeAdvance()
			ship = copy.deepcopy(self.state["ships"][0])
			ship.total_distance += ship.v * elapsed
			ship.time_until_departure -= elapsed
			ship.time_used += elapsed

			return {
				self.ship_out: ship
			}
		return {}


class Planner(AtomicDEVS):
	def __init__(self, name, plan, layer=None):
		super(Planner, self).__init__(name)

		self.plan = plan
		self.state = {
			"time": 0,
			"delta": 0,
			"index": 0,
			"layer": layer
		}
		self.reqs = self.addOutPort("reqs")

	def timeAdvance(self):
		if self.state["index"] > len(self.plan):
			return INFINITY
		return self.state["delta"]

	def intTransition(self):
		self.state["time"] += self.timeAdvance()

		self.state["layer"].paths = [p for p in self.state["layer"].paths if p["time_end"] < self.state["time"]]
		record = self.plan.iloc[self.state["index"]]
		if self.state["layer"] is not None:
			self.state["layer"].paths.append(record)

		self.state["index"] += 1
		self.state["delta"] = self.plan.iloc[self.state["index"]]["time"] - self.state["time"]
		return self.state

	def outputFnc(self):
		record = self.plan.iloc[self.state["index"]]
		# TODO: fix location check
		speed = record["distance"] / (record["time_end"] - record["time"])
		req = Request(round(self.state["time"] + self.timeAdvance(), 6), record["Sleepboot"], record["distance"], record["tugging"], record["Locatie van"], record["Locatie naar"], speed)
		return {
			self.reqs: req
		}