export type Dir = 0 | 1 | 2 | 3;
export const N = 0 as Dir;
export const E = 1 as Dir;
export const S = 2 as Dir;
export const W = 3 as Dir;
export const DR = [-1, 0, 1, 0] as const;
export const DC = [0, 1, 0, -1] as const;
export const OPP: Dir[] = [2, 3, 0, 1];
export const BIT = [1, 2, 4, 8] as const;
export const DIR_NAME = ["N", "E", "S", "W"] as const;

export type Playable =
  | "h"
  | "v"
  | "ne"
  | "se"
  | "sw"
  | "nw"
  | "cross"
  | "res_h"
  | "res_v"
  | "ow_n"
  | "ow_e"
  | "ow_s"
  | "ow_w";

export type PieceKind = Playable | "start" | "end" | "block";

export const PLAYABLE: Playable[] = [
  "h",
  "v",
  "ne",
  "se",
  "sw",
  "nw",
  "cross",
  "res_h",
  "res_v",
  "ow_n",
  "ow_e",
  "ow_s",
  "ow_w",
];

const OPEN: Record<Playable, number> = {
  h: BIT[E] | BIT[W],
  v: BIT[N] | BIT[S],
  ne: BIT[N] | BIT[E],
  se: BIT[S] | BIT[E],
  sw: BIT[S] | BIT[W],
  nw: BIT[N] | BIT[W],
  cross: BIT[N] | BIT[E] | BIT[S] | BIT[W],
  res_h: BIT[E] | BIT[W],
  res_v: BIT[N] | BIT[S],
  ow_n: BIT[N] | BIT[S],
  ow_e: BIT[E] | BIT[W],
  ow_s: BIT[N] | BIT[S],
  ow_w: BIT[E] | BIT[W],
};

export function openings(kind: PieceKind, port: Dir = E): number {
  if (kind === "block") return 0;
  if (kind === "start") return BIT[port];
  if (kind === "end") return BIT[port];
  return OPEN[kind as Playable];
}

export function hasOpen(kind: PieceKind, dir: Dir, port: Dir = E): boolean {
  return (openings(kind, port) & BIT[dir]) !== 0;
}

export function isElbow(k: PieceKind): boolean {
  return k === "ne" || k === "se" || k === "sw" || k === "nw";
}

export function isStraight(k: PieceKind): boolean {
  return k === "h" || k === "v" || k === "res_h" || k === "res_v";
}

export function isReservoir(k: PieceKind): boolean {
  return k === "res_h" || k === "res_v";
}

export function isOneway(k: PieceKind): boolean {
  return k === "ow_n" || k === "ow_e" || k === "ow_s" || k === "ow_w";
}

export function onewayArrow(k: PieceKind): Dir | null {
  if (k === "ow_n") return N;
  if (k === "ow_e") return E;
  if (k === "ow_s") return S;
  if (k === "ow_w") return W;
  return null;
}

export function axisOf(dir: Dir): 0 | 1 {
  return dir === N || dir === S ? 0 : 1;
}

/** Exit direction when flooz enters from `enter` (the side of this cell). */
export function exitDir(kind: PieceKind, enter: Dir, port: Dir = E): Dir | null {
  if (kind === "block") return null;
  if (kind === "start") return port;
  if (kind === "end") return null;
  if (!hasOpen(kind, enter, port)) return null;

  const arrow = onewayArrow(kind);
  if (arrow !== null) {
    if (enter !== OPP[arrow]) return null;
    return arrow;
  }

  if (kind === "cross") return OPP[enter];

  const mask = openings(kind, port);
  for (let d = 0; d < 4; d++) {
    if (d !== enter && mask & BIT[d as Dir]) return d as Dir;
  }
  return null;
}

export function canEnter(kind: PieceKind, enter: Dir, port: Dir = E): boolean {
  if (kind === "block" || kind === "start") return false;
  if (kind === "end") return enter === port;
  if (!hasOpen(kind, enter, port)) return false;
  const arrow = onewayArrow(kind);
  if (arrow !== null && enter !== OPP[arrow]) return false;
  return true;
}

export function segmentCost(kind: PieceKind): number {
  return isReservoir(kind) ? 2.6 : kind === "start" ? 0.55 : 1;
}

export function wrapIndex(i: number, n: number): number {
  return ((i % n) + n) % n;
}

export const ELBOW_FROM: Record<"ne" | "se" | "sw" | "nw", [Dir, Dir]> = {
  ne: [N, E],
  se: [S, E],
  sw: [S, W],
  nw: [N, W],
};

export interface PieceWeights {
  straight: number;
  elbow: number;
  cross: number;
  reservoir: number;
  oneway: number;
}

export const DEFAULT_WEIGHTS: PieceWeights = {
  straight: 22,
  elbow: 54,
  cross: 14,
  reservoir: 0,
  oneway: 0,
};

export function pickPiece(rand: () => number, w: PieceWeights): Playable {
  const bag: [Playable, number][] = [
    ["h", w.straight * 0.5],
    ["v", w.straight * 0.5],
    ["ne", w.elbow * 0.25],
    ["se", w.elbow * 0.25],
    ["sw", w.elbow * 0.25],
    ["nw", w.elbow * 0.25],
    ["cross", w.cross],
    ["res_h", w.reservoir * 0.5],
    ["res_v", w.reservoir * 0.5],
    ["ow_n", w.oneway * 0.25],
    ["ow_e", w.oneway * 0.25],
    ["ow_s", w.oneway * 0.25],
    ["ow_w", w.oneway * 0.25],
  ];
  let total = 0;
  for (const [, n] of bag) total += n;
  let r = rand() * total;
  for (const [p, n] of bag) {
    r -= n;
    if (r <= 0) return p;
  }
  return "h";
}

export function pieceLabel(k: PieceKind): string {
  if (k === "cross") return "CROSS";
  if (isReservoir(k)) return "CISTERN";
  if (isOneway(k)) return "ONE-WAY";
  if (k === "start") return "VALVE";
  if (k === "end") return "DRAIN";
  if (k === "block") return "RIVET";
  if (isElbow(k)) return "ELBOW";
  return "CONDUIT";
}
