import { Grid as GridSystem, SystemNames, System } from ".";
import { Game } from "..";
import { ComponentNames, Grid, Interactable } from "../components";
import { Control } from "../components/Control";
import { Action, KeyConstants, MovingSound, SOUNDS } from "../config";
import { Entity, Particles } from "../entities";
import { Coord2D, Direction } from "../interfaces";
import { colors } from "../utils";

export class Input extends System {
  private keys: Set<string>;
  private mousePosition: Coord2D;

  constructor() {
    super(SystemNames.Input);

    this.keys = new Set();
    this.mousePosition = { x: 0, y: 0 };
  }

  public clearKeys() {
    this.keys.clear();
  }

  public keyPressed(key: string) {
    this.keys.add(key);
  }

  public keyReleased(key: string) {
    this.keys.delete(key);
  }

  public update(_dt: number, game: Game) {
    game.forEachEntityWithComponent(ComponentNames.Control, (entity) =>
      this.handleMovement(entity, game)
    );
    game.forEachEntityWithComponent(ComponentNames.Interactable, (entity) =>
      this.handleInteraction(entity)
    );
  }

  private handleInteraction(entity: Entity) {
    const interactable = entity.getComponent<Interactable>(
      ComponentNames.Interactable
    );

    const interact = this.hasSomeKey(
      KeyConstants.ActionKeys.get(Action.INTERACT)
    );

    if (!interact) {
      return;
    }

    interactable.interact();
    KeyConstants.ActionKeys.get(Action.INTERACT)!.forEach((key) =>
      this.keyReleased(key)
    );
  }

  public handleMovement(entity: Entity, game: Game) {
    const controlComponent = entity.getComponent<Control>(
      ComponentNames.Control
    );
    if (!controlComponent.isControllable) return;

    const hasGrid = entity.hasComponent(ComponentNames.Grid);

    // TODO: check grid via controlComponent to notify entity of interaction
    const [moveUp, moveLeft, moveRight, moveDown] = [
      Action.MOVE_UP,
      Action.MOVE_LEFT,
      Action.MOVE_RIGHT,
      Action.MOVE_DOWN,
    ].map((action) => this.hasSomeKey(KeyConstants.ActionKeys.get(action)));
    if (!hasGrid) {
      return;
    }

    const gridComponent = entity.getComponent<Grid>(ComponentNames.Grid)!;
    if (gridComponent.movingDirection !== Direction.NONE) {
      return;
    }

    if (moveUp) {
      gridComponent.movingDirection = Direction.UP;
      KeyConstants.ActionKeys.get(Action.MOVE_UP)!.forEach((key) =>
        this.keyReleased(key)
      );
    } else if (moveLeft) {
      gridComponent.movingDirection = Direction.LEFT;
      KeyConstants.ActionKeys.get(Action.MOVE_LEFT)!.forEach((key) =>
        this.keyReleased(key)
      );
    } else if (moveRight) {
      gridComponent.movingDirection = Direction.RIGHT;
      KeyConstants.ActionKeys.get(Action.MOVE_RIGHT)!.forEach((key) =>
        this.keyReleased(key)
      );
    } else if (moveDown) {
      gridComponent.movingDirection = Direction.DOWN;
      KeyConstants.ActionKeys.get(Action.MOVE_DOWN)!.forEach((key) =>
        this.keyReleased(key)
      );
    }

    if (moveUp || moveLeft || moveRight || moveDown) {
      this.spawnParticlesAround(entity, game);
      this.playMoveSound();
    }

    entity.addComponent(gridComponent);
  }

  private playMoveSound() {
    const movingSounds = Array.from(MovingSound.states!.values());
    const soundName =
      movingSounds[Math.floor(Math.random() * movingSounds.length)].name;
    SOUNDS.get(soundName)!.play();
  }

  private spawnParticlesAround(entity: Entity, game: Game) {
    const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid);
    const gridComponent = entity.getComponent<Grid>(ComponentNames.Grid)!;
    const particles = new Particles({
      center: gridSystem.gridToScreenPosition(gridComponent.gridPosition),
      particleCount: 4,
      spawnerShape: "circle",
      spawnerDimensions: {
        width: 10,
        height: 10,
      },
      particleShape: "rectangle",
      particleMeanSpeed: 0.05,
      particleSpeedVariance: 0.005,
      particleMeanLife: 120,
      particleMeanSize: 3,
      particleSizeVariance: 1,
      particleLifeVariance: 30,
      particleColors: [colors.gray, colors.darkGray, colors.lightPurple],
    });

    game.addEntity(particles);
  }

  private hasSomeKey(keys?: string[]): boolean {
    if (keys) {
      return keys.some((key) => this.keys.has(key));
    }
    return false;
  }

  public setMousePosition(mousePosition: Coord2D) {
    this.mousePosition = mousePosition;
  }

  public getMousePosition(): Coord2D {
    return this.mousePosition;
  }
}
