import StateHandler from './StateHandler';

class StateMachine<T> {
  constructor() {
    this.stateMap = new Map();
  }
  private readonly stateMap: Map<T, State<T>>;
  private currentState!: State<T> | null;
  public get CurrentState(): T {
    return this.currentState!.label;
  }

  private previousState!: State<T>;
  public get PreviousState(): T {
    return this.previousState.label;
  }

  private enterState!: T;

  private isRunning!: boolean;
  public get IsRunning(): boolean {
    return this.isRunning;
  }

  public get StateCount(): number {
    return this.stateMap.size;
  }

  public start(): void {
    if (this.StateCount == 0) return;
    this.currentState = this.stateMap.get(this.enterState)!;
    this.currentState?.onStart?.();
  }
  public stop(): void {
    this.currentState?.onStop?.();
    this.currentState = null;
  }

  public addState(label: T, handler: StateHandler<T>): void;
  public addState(
    label: T,
    onStart: Function | null,
    onUpdate: Function | null,
    onStop: Function | null,
  ): void;
  public addState(
    label: T,
    query: Function | StateHandler<T> | null,
    onUpdate?: Function | null,
    onStop?: Function | null,
  ): void {
    let newState;
    if (query instanceof Function || query === null) {
      newState = new State<T>(label, query, onUpdate!, onStop!);
    } else {
      const { stateIn, stateRunning, stateExit } = query;
      newState = new State<T>(label, stateIn, stateRunning, stateExit);
    }
    this.stateMap.set(label, newState);
    if (this.currentState == null) {
      this.currentState = newState;
      this.enterState = this.currentState.label;
    }
  }

  public changeState(newState: T) {
    this.currentState?.onStop?.();
    this.previousState = this.currentState!;
    this.currentState = this.stateMap.get(newState)!;
    this.currentState?.onStart?.();
  }
}
export default StateMachine;

class State<T> {
  public readonly label: T;
  public readonly onStart: Function | null;
  public readonly onUpdate: Function | null;
  public readonly onStop: Function | null;
  constructor(
    label: T,
    onStart: Function | null,
    onUpdate: Function | null,
    onStop: Function | null,
  ) {
    this.label = label;
    this.onStart = onStart;
    this.onUpdate = onUpdate;
    this.onStop = onStop;
  }
}
