from utils_ak.clock import *
from utils_ak.fluid_flow.actor import Actor
from utils_ak.fluid_flow.actors.pipe import PipeMixin
from utils_ak.fluid_flow.calculations import *
import pandas as pd


class Container(Actor, PipeMixin):
    def __init__(self, name, value=0, item="default", max_pressures=None, limits=None):
        super().__init__(name)
        self.item = item
        self.value = value

        self.df = pd.DataFrame(
            index=["in", "out"], columns=["max_pressure", "limit", "collected"]
        )
        self.df["max_pressure"] = max_pressures
        self.df["limit"] = limits
        self.df["collected"] = 0.0

        self.transactions = []

    def is_limit_reached(self, orient):
        if (
            self.df.at[orient, "limit"]
            and abs(self.df.at[orient, "collected"] - self.df.at[orient, "limit"])
            < ERROR
        ):
            return True
        return False

    def update_value(self, ts, factor=1):
        if self.last_ts is None:
            return

        self.add_value(ts, "in", (ts - self.last_ts) * self.speed("in") * factor)
        self.add_value(ts, "out", -(ts - self.last_ts) * self.speed("out"))

    def add_value(self, ts, orient, value):
        if not value:
            return
        self.value += value
        self.df.at[orient, "collected"] += abs(value)
        self.transactions.append([self.last_ts, ts, value])

    def active_periods(self, orient="in"):
        if not self.transactions:
            return []
        return [[self.item, self.transactions[0][0], self.transactions[-1][1]]]

    def update_pressure(self, ts, orients=("in", "out")):
        for orient in orients:
            if self.pipe(orient):
                pressure = (
                    self.df.at[orient, "max_pressure"]
                    if not self.is_limit_reached(orient)
                    else 0
                )
                self.pipe(orient).set_pressure(orient, pressure, self.item)

    def update_speed(self, ts, set_out_pressure=True):
        input_speed = self.speed("in")

        if set_out_pressure:
            if self.pipe("out") and abs(self.value) < ERROR:
                self.pipe("out").set_pressure(
                    "out",
                    nanmin([self.pipe("out").pressures["out"], input_speed]),
                    self.item,
                )

    def reset(self):
        super().reset()
        self.transactions = []

    def update_triggers(self, ts):
        values = []
        if self.drain() < 0:
            values.append(["empty_container", self.value, self.drain()])

        for orient in ["in", "out"]:
            if self.df.at[orient, "limit"]:
                values.append(
                    [
                        f"{orient} limit",
                        self.df.at[orient, "limit"] - self.df.at[orient, "collected"],
                        self.speed(orient),
                    ]
                )

        values = [
            value for value in values if value[1] > ERROR and abs(value[2]) > ERROR
        ]
        etas = [value[1] / abs(value[2]) for value in values]
        if etas:
            self.add_event("update.trigger", ts + min(etas), {})

    def __str__(self):
        return f"Container {self.name}:{self.item}"

    def stats(self):
        return {"value": self.value}

    def display_stats(self):
        return self.value
