from components.aplant import APlant
from components.plant import delay

class Pot(APlant):
	def __init__(self, dt):
		APlant.__init__(self)
		self.dt = dt

		self.e = 0
		self.x = 0
		self.p_pot = 0

		# Plant specific thermal capacity [W*s/(kg*K)], mass [kg], energy
		# loss coefficient [W/(kg*K)] and transport propagation delay [s]
		# - all None until set_plant_params() is called; process() checks
		# for that rather than silently computing on bogus values.
		self.C = None
		self.M = None
		self.L = None
		self.Td = None
		self.delay = None

		# Plant temperature [°C] - seeded from theta_amb the first time
		# set_ambient_temperature() is called (see there), or overridden
		# explicitly via initial(). None until either happens;
		# process() checks for that too.
		self.temp = None

		# Ambient temperature [°C] - None until set_ambient_temperature()
		# is called; process() checks for that as well.
		self.theta_amb = None

		# Set power [W]
		self.p_in = 0

	def set_plant_params(self, params):
		self.C = params['C']
		self.M = params['M']

		# Negative input power as a function of plant mass and
		# temperature difference T_plant and T_ambient:
		# P_loss = L * Mass * (T_plant - T_ambient)
		self.L = params['L']

		# Only rebuilds the delay line if Td actually changed - this now
		# gets called on every Sud step change (M/C vary with grain/water
		# mass), not just once at construction, and rebuilding unconditionally
		# would zero out whatever power was already in flight through the
		# transport delay at every single step transition.
		Td = params['Td']
		if Td != self.Td:
			self.Td = Td
			self.delay = delay.Delay(self.dt, self.Td, 0)

	def set_ambient_temperature(self, theta_amb):
		self.theta_amb = theta_amb
		# Only seeds the plant's own starting temperature the first time
		# this is called (i.e. there's no real temperature yet) - once a
		# run is in progress, changing the ambient setting must not
		# clobber whatever temperature has actually been simulated since.
		if self.temp is None:
			self.temp = theta_amb

	def initial(self, temp):
		self.temp = temp

	def is_configured(self):
		"""Whether both set_plant_params() and set_ambient_temperature()
		have been called - lets a caller driving this Pot as an internal
		model (e.g. TempController(Smith)) check upfront and raise its
		own comprehensive error, rather than letting process() fail on
		whichever of the two happens to be missing."""
		return self.delay is not None and self.theta_amb is not None

	def activate(self, enable):
		pass

	def process(self):
		if self.delay is None:
			raise RuntimeError("Pot.process(): plant params not set - call set_plant_params() first")
		if self.theta_amb is None:
			raise RuntimeError("Pot.process(): ambient temperature not set - call set_ambient_temperature() first")

		self.delay.put(self.p_in)

		p_loss = self.L * self.M * (self.temp - self.theta_amb)
		self.p_pot = self.delay.get() - p_loss

		self.temp = min(100, self.temp + self.p_pot/(self.M * self.C) * self.dt)

	def is_activated(self):
		return True

	def set_power(self, power):
		self.p_in = power

	def get_power(self):
		return round(self.p_in, 1)

	def get_temperature(self):
		return self.temp

	def get_p_pot(self):
		return self.p_pot

