from ecell4.core import *
from ecell4.gillespie import GillespieWorld as world_type, GillespieSimulator as simulator_type
# from ecell4.ode import ODEWorld as world_type, ODESimulator as simulator_type
# from ecell4.lattice import LatticeWorld as world_type, LatticeSimulator as simulator_type
# from ecell4.meso import MesoscopicWorld as world_type, MesoscopicSimulator as simulator_type
# from ecell4.bd import BDWorld as world_type, BDSimulator as simulator_type
# from ecell4.egfrd import EGFRDWorld as world_type, EGFRDSimulator as simulator_type

"""
Explanation: Tutorial 4 (Simulator)
This is a tutorial for E-Cell4. Here, we explain how to handle Simulators.
Each World has its corresponding Simulator.
End of explanation
"""


m = NetworkModel()
m.add_species_attribute(Species("A", "0.0025", "1"))
m.add_reaction_rule(create_degradation_reaction_rule(Species("A"), 0.693 / 1))

w = world_type(Real3(1, 1, 1))
w.bind_to(m)
w.add_molecules(Species("A"), 60)

sim = simulator_type(m, w)
sim.set_dt(0.01) #XXX: Optional

"""
Explanation: Simulator needs a Model and World at the instantiation.
End of explanation
"""


print(sim.num_steps())
print(sim.t(), w.t())
print(sim.next_time(), sim.t() + sim.dt())

"""
Explanation: A Simulator has getters for a simulation time, a step interval, and the next-event time. In principle, a Simulator returns the World's time as its simulation time, and does a sum of the current time and a step interval as the next-event time.
End of explanation
"""


print(sim.model(), sim.world())

"""
Explanation: A Simulator can return the connected model and world. They are not copies, but the shared objects.
End of explanation
"""


sim.world().add_molecules(Species("A"), 60) # w.add_molecules(Species("A"), 60)
sim.initialize()

# w.save('test.h5')

"""
Explanation: If you change a World after connecting it to a Simulator, you have to call initialize() manually before step(). The call will update the internal state of the Simulator.
End of explanation
"""


print("%.3e %.3e" % (sim.t(), sim.next_time()))
sim.step()
print("%.3e %.3e" % (sim.t(), sim.next_time()))

"""
Explanation: Simulator has two types of step functions. First, with no argument, step() increments the time until next_time().
End of explanation
"""


print("%.3e %.3e" % (sim.t(), sim.next_time()))
print(sim.step(0.1))
print("%.3e %.3e" % (sim.t(), sim.next_time()))

"""
Explanation: With an argument upto, if upto is later than next_time(), step(upto) increments the time upto the next_time() and returns True. Otherwise, it increments the time for upto and returns False. (If the current time t() is less than upto, it does nothing and returns False.)
End of explanation
"""


# w.load('test.h5')
sim.initialize()

next_time, dt = 0.0, 1e-2
for _ in range(5):
    while sim.step(next_time): pass
    next_time += dt
    print("%.3e %.3e %d %g" % (sim.t(), sim.dt(), sim.num_steps(), w.num_molecules(Species("A"))))

"""
Explanation: For a discrete-step simulation, the main loop should be written like:
End of explanation
"""


