text: """
  Copyright (c) Contributors to the Open 3D Engine Project.
  For complete copyright and license terms please see the LICENSE at the root of this distribution.
  
  SPDX-License-Identifier: Apache-2.0 OR MIT
  """
  
  
  # Test Case ID    : C12712452
  # Test Case Title : Verify ScriptCanvas Collision Events
  
  
  
  # fmt: off
  class Tests:
      enter_game_mode            = ("Entered game mode",                  "Failed to enter game mode")
      terrain_found_valid        = ("PhysX Terrain found and validated",  "PhysX Terrain not found and validated")
      sphere_found_valid         = ("Sphere found and validated",         "Sphere not found and validated")
      begin_signal_found_valid   = ("Begin Signal found and validated",   "Begin Signal not found and validated")
      persist_signal_found_valid = ("Persist Signal found and validated", "Persist Signal not found and validated")
      end_signal_found_valid     = ("End Signal found and validated",     "End Signal not found and validated")
      sphere_above_terrain       = ("Sphere is above terrain",            "Sphere is not above terrain")
      sphere_gravity_enabled     = ("Gravity is enabled on Sphere",       "Gravity is not enabled on Sphere")
      sphere_started_bouncing    = ("Sphere started bouncing",            "Sphere did not start bouncing")
      sphere_stopped_bouncing    = ("Sphere stopped bouncing",            "Sphere did not stop bouncing")
      event_records_match        = ("Event records match",                "Event records do not match")
      exit_game_mode             = ("Exited game mode",                   "Failed to exit game mode")
  # fmt: on
  
  
  def ScriptCanvas_CollisionEvents():
      """
      Summary:
      This script runs an automated test to verify that the Script Canvas nodes "On Collision Begin", "On Collision
      Persist", and "On Collision End" will function as intended for the entity to which their Script Canvas is attached.
  
      Level Description:
      A sphere (entity: Sphere) is above a terrain (entity: PhysX Terrain). The sphere has a PhysX Rigid Body, a PhysX
      Collider with shape Sphere, and gravity enabled. The sphere has a Script Canvas attached to it which will toggle the
      activation of three signal entities (entity: Begin Signal), (entity: Persist Signal), and (entity: End Signal).
      Begin Signal's activation will toggle (switch from activated to deactivated or vice versa) when a collision begins
      with the sphere, Persist Signal's activation will toggle when a collision persists with the sphere, and End Signal's
      activation will toggle when a collision ends with the sphere.
  
      Expected behavior:
      The sphere will fall toward and collide with the terrain. The sphere will bounce until it comes to rest. The Script
      Canvas will cause the signal entities to activate and deactivate in the same pattern as the collision events which
      occur on the sphere.
  
      Test Steps:
      1) Open level and enter game mode
      2) Retrieve and validate entities
      3) Check that the sphere is above the terrain
      4) Check that gravity is enabled on the sphere
      5) Wait for the initial collision between the sphere and the terrain or timeout
      6) Wait for the sphere to stop bouncing
      7 Check that the event records match
      8) Exit game mode and close editor
  
      Note:
      - This test file must be called from the Open 3D Engine Editor command terminal
      - Any passed and failed tests are written to the Editor.log file.
              Parsing the file or running a log_monitor are required to observe the test results.
  
      :return: None
      """
      # Setup path
      import os
      import sys
      import azlmbr.legacy.general as general
      import azlmbr.bus
      import azlmbr.components
      import azlmbr.entity
      import azlmbr.physics
      from editor_python_test_tools.utils import Report
      from editor_python_test_tools.utils import TestHelper as helper
  
      # Constants
      TIME_OUT_SECONDS = 3.0
      TERRAIN_START_Z = 32.0
      SPHERE_RADIUS = 1.0
  
      class Entity:
          def __init__(self, name, found_valid_test):
              self.name = name
              self.id = general.find_game_entity(name)
              self.found_valid_test = found_valid_test
  
      class Sphere(Entity):
          def __init__(self, name, found_valid_test, event_records_match_test):
              Entity.__init__(self, name, found_valid_test)
              self.event_records_match_test = event_records_match_test
              self.collided = False
              self.stopped_bouncing = False
              self.collision_event_record = []
              self.script_canvas_event_record = []
  
              # Set up collision notification handler
              self.handler = azlmbr.physics.CollisionNotificationBusHandler()
              self.handler.connect(self.id)
              self.handler.add_callback("OnCollisionBegin", self.on_collision_begin)
              self.handler.add_callback("OnCollisionPersist", self.on_collision_persist)
              self.handler.add_callback("OnCollisionEnd", self.on_collision_end)
  
          def get_z_position(self):
              z_position = azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", self.id)
              Report.info("{}'s z-position: {}".format(self.name, z_position))
              return z_position
  
          def is_gravity_enabled(self):
              return azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsGravityEnabled", self.id)
  
          # Set up reporting of the event records and whether they match
          def match_event_records(self):
              Report.info("{} collision event record: {}".format(self.name, self.collision_event_record))
              Report.info("Script Canvas event record: {}".format(self.script_canvas_event_record))
              return self.collision_event_record == self.script_canvas_event_record
  
          # Set up collision event detection and update collision event record
          def on_collision(self, event, other_id):
              if not self.collided:
                  self.collided = True
              other_name = azlmbr.entity.GameEntityContextRequestBus(azlmbr.bus.Broadcast, "GetEntityName", other_id)
              Report.info("{} collision {}s with {}".format(self.name, event, other_name))
              self.collision_event_record.append(event)
  
          def on_collision_begin(self, args):
              self.on_collision("begin", args[0])
  
          def on_collision_persist(self, args):
              self.on_collision("persist", args[0])
  
          def on_collision_end(self, args):
              self.on_collision("end", args[0])
  
          # Set up detection of the sphere coming to rest
          def bouncing_stopped(self):
              if not azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "IsAwake", self.id):
                  self.stopped_bouncing = True
              return self.stopped_bouncing
  
      class SignalEntity(Entity):
          def __init__(self, name, found_valid_test, monitored_entity, event):
              Entity.__init__(self, name, found_valid_test)
              self.monitored_entity = monitored_entity
              self.event = event
  
              # Set up activation and deactivation notification handler
              self.handler = azlmbr.entity.EntityBusHandler()
              self.handler.connect(self.id)
              self.handler.add_callback("OnEntityActivated", self.on_entity_activated)
              self.handler.add_callback("OnEntityDeactivated", self.on_entity_deactivated)
  
          # Set up activation and deactivation detection and update Script Canvas event record
          def on_entity_activated(self, args):
              self.monitored_entity.script_canvas_event_record.append(self.event)
  
          def on_entity_deactivated(self, args):
              self.monitored_entity.script_canvas_event_record.append(self.event)
  
      # 1) Open level and enter game mode
      helper.init_idle()
      helper.open_level("Physics", "ScriptCanvas_CollisionEvents")
      helper.enter_game_mode(Tests.enter_game_mode)
  
      # 2) Retrieve and validate entities
      terrain = Entity("PhysX Terrain", Tests.terrain_found_valid)
      sphere = Sphere("Sphere", Tests.sphere_found_valid, Tests.event_records_match)
      begin_signal = SignalEntity("Begin Signal", Tests.begin_signal_found_valid, sphere, "begin")
      persist_signal = SignalEntity("Persist Signal", Tests.persist_signal_found_valid, sphere, "persist")
      end_signal = SignalEntity("End Signal", Tests.end_signal_found_valid, sphere, "end")
  
      entities = [terrain, sphere, begin_signal, persist_signal, end_signal]
      for entity in entities:
          Report.critical_result(entity.found_valid_test, entity.id.IsValid())
  
      # 3) Check that the sphere is above the terrain
      Report.critical_result(Tests.sphere_above_terrain, sphere.get_z_position() - SPHERE_RADIUS > TERRAIN_START_Z)
  
      # 4) Check that gravity is enabled on the sphere
      Report.critical_result(Tests.sphere_gravity_enabled, sphere.is_gravity_enabled())
  
      # 5) Wait for the initial collision between the sphere and the terrain or timeout
      helper.wait_for_condition(lambda: sphere.collided, TIME_OUT_SECONDS)
      Report.critical_result(Tests.sphere_started_bouncing, sphere.collided)
  
      # 6) Wait for the sphere to stop bouncing
      helper.wait_for_condition(sphere.bouncing_stopped, TIME_OUT_SECONDS)
      Report.result(Tests.sphere_stopped_bouncing, sphere.stopped_bouncing)
  
      # 7 Check that the event records match
      Report.result(Tests.event_records_match, sphere.match_event_records())
  
      # 8) Exit game mode and close editor
      helper.exit_game_mode(Tests.exit_game_mode)
  
  
  if __name__ == "__main__":
      from editor_python_test_tools.utils import Report
      Report.start_test(ScriptCanvas_CollisionEvents)
  