import { System, SystemNames } from ".";
import { Game } from "..";
import { Entity, EntityNames } from "../entities";
import { BoundingBox, Colliding, ComponentNames, Grid } from "../components";

const collisionMap: Record<string, Set<string>> = {
  [EntityNames.Key]: new Set([EntityNames.LockedDoor]),
  [EntityNames.Curry]: new Set([EntityNames.Player]),
  [EntityNames.FunctionApplication]: new Set([EntityNames.FunctionBox]),
  [EntityNames.Portal]: new Set([EntityNames.Player]),
};

export class Collision extends System {
  static canCollide(entityName: string, otherEntityName: string) {
    if (collisionMap[entityName]) {
      return collisionMap[entityName].has(otherEntityName);
    }
    return collisionMap[otherEntityName]?.has(entityName) ?? false;
  }

  constructor() {
    super(SystemNames.Collision);
  }

  public update(_dt: number, game: Game) {
    game.forEachEntityWithComponent(ComponentNames.Colliding, (entity) => {
      if (!entity.hasComponent(ComponentNames.BoundingBox)) {
        return;
      }
      const collidingBox = entity.getComponent<BoundingBox>(
        ComponentNames.BoundingBox
      );
      let collidingGrid = entity.hasComponent(ComponentNames.Grid)
        ? entity.getComponent<Grid>(ComponentNames.Grid)
        : null;

      const collidingWith: Entity[] = [];
      game.forEachEntityWithComponent(
        ComponentNames.BoundingBox,
        (otherEntity) => {
          const otherBoundingBox = otherEntity.getComponent<BoundingBox>(
            ComponentNames.BoundingBox
          );
          let otherGrid = otherEntity.hasComponent(ComponentNames.Grid)
            ? otherEntity.getComponent<Grid>(ComponentNames.Grid)
            : null;

          if (collidingGrid && otherGrid) {
            if (
              collidingGrid.gridPosition.x === otherGrid.gridPosition.x &&
              collidingGrid.gridPosition.y === otherGrid.gridPosition.y
            ) {
              collidingWith.push(otherEntity);
            }
            return;
          }

          if (collidingBox.isCollidingWith(otherBoundingBox)) {
            collidingWith.push(otherEntity);
          }
        }
      );

      for (const collision of collidingWith) {
        this.handleCollision(entity, collision, game);
      }
    });
  }

  private handleCollision(entity: Entity, otherEntity: Entity, game: Game) {
    if (!Collision.canCollide(entity.name, otherEntity.name)) {
      return;
    }

    [entity, otherEntity].forEach((e) => {
      if (!e.hasComponent(ComponentNames.Colliding)) {
        return;
      }
      const colliding = e.getComponent<Colliding>(ComponentNames.Colliding);
      if (colliding?.onCollision) {
        colliding.onCollision(game, e === entity ? otherEntity : entity);
      }
    });
  }
}
