import { BEACH, ICECAP, LIGHT_FOREST, MOUNTAIN, WATER } from "./data";
import { IslandGrid } from "./grid";

export type IsDone = boolean;
export type LobeGenerator = () => IsDone;
export type LobeGeneratorConstructor = (
  islands: IslandGrid,
  basePos: number
) => LobeGenerator;

/** form mountain  */
export const SMALL_MOUNTAIN: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    const edgeTiles = islandTiles.filter((pos) => islands.data[pos] <= BEACH);
    islands.dropWithin(edgeTiles);

    const mountainTiles = islands.floodSearch(
      basePos,
      (tile) => tile > MOUNTAIN
    );
    islands.dropWithin(mountainTiles);

    return true;
  };

/** form mountain with icecap */
export const BIG_MOUNTAIN: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const mountainTiles = islands.floodSearch(
      basePos,
      (tile) => tile > MOUNTAIN
    );
    islands.dropWithin(mountainTiles);

    return mountainTiles.some((pos) => islands.data[pos] == ICECAP);
  };

/** form low-lying beach */
export const SMALL_BEACH: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);

    return true;
  };

/** form low-lying beach that meets another island */
export const BIG_BEACH: LobeGeneratorConstructor = (
  islands: IslandGrid,
  basePos: number
) => {
  let dropped = 0;
  return () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);
    dropped++;

    const landTiles = islandTiles.filter((pos) => islands.data[pos] > WATER);
    return landTiles.length > dropped;
  };
};

/** form forested zone wherever available */
export const SCATTERED_FOREST: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    // grow shore
    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);

    // seed beach
    const beachTiles = islandTiles.filter((pos) => islands.data[pos] == BEACH);
    islands.dropWithin(beachTiles);
    islands.dropWithin(beachTiles);

    // expand forest
    const forestLobe = islands.floodSearch(basePos, (tile) => tile > WATER);
    const forestTiles = forestLobe.filter(
      (pos) => islands.data[pos] == LIGHT_FOREST
    );
    islands.dropWithin(forestTiles);

    return true;
  };

/** form forested zone that tries to grow as one mass */
export const CONTIGUOUS_FOREST: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    // grow shore
    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);

    // seed beach
    const beachTiles = islandTiles.filter((pos) => islands.data[pos] == BEACH);
    islands.dropWithin(beachTiles);

    // locate & expand central forest edge
    const forestLobe = islands.floodSearch(basePos, (tile) => tile > BEACH);
    const forestTiles = forestLobe.filter(
      (pos) => islands.data[pos] == LIGHT_FOREST
    );
    islands.dropWithin(forestTiles);
    islands.dropWithin(forestTiles);

    return true;
  };

/** form forested zone that can raise additional peaks */
export const HILLY_FOREST: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    // grow shore
    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);

    // seed beach
    const beachTiles = islandTiles.filter((pos) => islands.data[pos] == BEACH);
    islands.dropWithin(beachTiles);

    // locate central forest
    const centralForest = islands.floodSearch(basePos, (tile) => tile > BEACH);
    islands.dropWithin(centralForest);

    // expand edge
    const edgeTiles = centralForest.filter(
      (pos) => islands.data[pos] == LIGHT_FOREST
    );
    islands.dropWithin(edgeTiles);

    const hillTiles = centralForest.filter(
      (pos) => islands.data[pos] >= MOUNTAIN
    );
    return hillTiles.length > 10;
  };

/** form low-lying beach with eroded sections */
export const ERODED_BEACH: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    const islandTiles = islands.floodSearch(basePos, (tile) => tile > WATER);

    const shoreTiles = islandTiles.filter((pos) => islands.data[pos] <= WATER);
    islands.dropWithin(shoreTiles);

    // erode
    if (islandTiles.length > 1) {
      const erodePos = islandTiles[islands.rng() % islandTiles.length];
      islands.data[erodePos] = Math.max(islands.data[erodePos] - 1, WATER);
    }

    return true;
  };

/** form low-lying beach with eroded sections */
export const NO_ISLAND: LobeGeneratorConstructor =
  (islands: IslandGrid, basePos: number) => () => {
    return true;
  };

const SINKHOLE_BURNOUT = 1500;

/** carve out elevation */
export const SINKHOLE: LobeGeneratorConstructor = (
  islands: IslandGrid,
  basePos: number
) => {
  // emergency cutoff in case sinkhole ends up fighting a generator that terminates at a given elevation
  let ticks = 0;
  return () => {
    if (ticks++ < SINKHOLE_BURNOUT) {
      const sunk = islands.floodSearch(basePos, (tile) => tile < 0);

      islands.sinkhole(islands.choose(sunk));
    }

    return true;
  };
};

/** carve out elevation more aggressively */
export const WIDE_SINKHOLE: LobeGeneratorConstructor = (
  islands: IslandGrid,
  basePos: number
) => {
  // emergency cutoff in case sinkhole ends up fighting a generator that terminates at a given elevation
  let ticks = 0;
  return () => {
    if (ticks++ < SINKHOLE_BURNOUT) {
      const sunk = islands.floodSearch(basePos, (tile) => tile < 0);

      const sunkEdge = sunk.filter((pos) => islands.data[pos] >= WATER);

      if (sunkEdge.length > 0) {
        islands.sinkhole(islands.choose(sunkEdge));
      } else {
        islands.sinkhole(islands.choose(sunk));
      }
    }

    return true;
  };
};

export const BIG_ISLANDS = [BIG_MOUNTAIN, BIG_BEACH, HILLY_FOREST];
export const ROCKY_ISLANDS = [SMALL_MOUNTAIN, BIG_MOUNTAIN, HILLY_FOREST];
export const GREEN_ISLANDS = [
  SCATTERED_FOREST,
  CONTIGUOUS_FOREST,
  HILLY_FOREST,
];
export const SMALL_ISLANDS = [SMALL_BEACH, SCATTERED_FOREST, ERODED_BEACH];
export const ALL_ISLANDS = [
  SMALL_MOUNTAIN,
  BIG_MOUNTAIN,
  SMALL_BEACH,
  BIG_BEACH,
  SCATTERED_FOREST,
  CONTIGUOUS_FOREST,
  HILLY_FOREST,
  ERODED_BEACH,
];
export const VOIDS = [NO_ISLAND, SINKHOLE, WIDE_SINKHOLE];