import { WorldGenState, ChunkGen, PointNodeGen, ChunkGenConstants, ZLevelGen, ResourceType, ResourceModifier } from "../data/GameState";
import { HashSet, KeyedHashMap } from "../lib/util/data_structures/hash";
import { Vector2 } from "../lib/util/geometry/vector2";
import { INTMAX32, squirrel3 } from "../lib/util/random";

export type WorldGenStateConfig = any;

export class WorldGenStateFactory {
  public config: WorldGenStateConfig;

  constructor(config: WorldGenStateConfig) {
    this.config = config;
  }

  public create(): WorldGenState {
    throw Error();
  }
}

export type ZLevelGenConfig = any;

export class ZLevelGenFactory {
  public config: ZLevelGenConfig;
  public chunkGenFactory: ChunkGenFactory;

  constructor(config: ZLevelGenConfig) {
    this.config = config;
    this.chunkGenFactory = new ChunkGenFactory({});
  }

  public create(args: { seed: number, z: number, startingChunks?: number }): ZLevelGen {
    const id = squirrel3(args.seed + args.z);
    const chunks: KeyedHashMap<Vector2, ChunkGen> = new KeyedHashMap();

    const max = args.startingChunks || 3;
    // TODO(bowei): generate more chunks??
    for (let i = -max; i <= max; i++) {
      for (let j = -max; j <= max; j++){
        let location = new Vector2(i, j);
        chunks.put(location, this.chunkGenFactory.create({ seed: id, location, z: args.z }));
      }
    }

    return { id, chunks };
  }
}

export type ChunkGenConfig = any;

export class ChunkGenFactory {
  public config: ChunkGenConfig;
  public pointNodeGenFactory: PointNodeGenFactory

  constructor(config: ChunkGenConfig) {
    this.config = config;
    this.pointNodeGenFactory = new PointNodeGenFactory({});
  }

  public create(args: { seed: number, location: Vector2, z: number }): ChunkGen {
    const id = squirrel3(args.seed + squirrel3(args.seed + args.location.x) + args.location.y);
    const pointNodes: KeyedHashMap<Vector2, PointNodeGen> = new KeyedHashMap();

    let droppedNodes: HashSet<Vector2> = new HashSet();
    for (let i = -ChunkGenConstants.CHUNK_HALF_DIM; i <= ChunkGenConstants.CHUNK_HALF_DIM; i++) {
      for (let j = -ChunkGenConstants.CHUNK_HALF_DIM; j <= ChunkGenConstants.CHUNK_HALF_DIM; j++) {
        if (i === 0 && j === 0) {
          continue;
        }
        // 4 way symmetry plz
        if (squirrel3(id + i * ChunkGenConstants.CHUNK_DIM + j) / INTMAX32 < ChunkGenConstants.DROP_NODES_CHANCE / 4) {
          droppedNodes.put(new Vector2(i, j));
          droppedNodes.put(new Vector2(j, -i));
          droppedNodes.put(new Vector2(-i, -j));
          droppedNodes.put(new Vector2(-j, i));
        }
      }
    }

    for (let i = -ChunkGenConstants.CHUNK_HALF_DIM; i <= ChunkGenConstants.CHUNK_HALF_DIM; i++) {
      for (let j = -ChunkGenConstants.CHUNK_HALF_DIM; j <= ChunkGenConstants.CHUNK_HALF_DIM; j++) {
        let loc = new Vector2(i, j);
        if (!droppedNodes.get(loc)) {
          pointNodes.put(loc, this.pointNodeGenFactory.create({ seed: id, location: loc, chunk: args.location, z: args.z }));
        }
      }
    }

    return { id, pointNodes };
  }
}

type PointNodeGenConfig = {};

export class PointNodeGenFactory {
  public config: PointNodeGenConfig;

  constructor(config: PointNodeGenConfig) {
    this.config = config;
  }

  public create(args: { seed: number, location: Vector2, chunk: Vector2, z: number }): PointNodeGen {
    const id = squirrel3(args.seed + squirrel3(args.seed + args.location.x) + args.location.y);

    let randomFloat = squirrel3(id + 1) / INTMAX32;
    let resourceType: ResourceType;
    if (randomFloat < 0.0) {
      resourceType = "Nothing";
    } else if (randomFloat < 0.15) {
      resourceType = ResourceType.Mana0;
    } else if (randomFloat < -0.35) {
      resourceType = ResourceType.Mana1;
    } else if (randomFloat < -0.60) {
      resourceType = ResourceType.Mana2;
    } else {
      resourceType = "Nothing";
    }
    // override for root node
    if (args.location.equals(Vector2.Zero) && args.chunk.equals(Vector2.Zero) && args.z === 0) {
      resourceType = "Nothing";
    }

    randomFloat = squirrel3(id + 2) / INTMAX32;
    let resourceModifier: ResourceModifier;
    if (randomFloat < 0.55) {
      resourceModifier = ResourceModifier.Flat;
    } else if (randomFloat < -0.70) {
      resourceModifier = ResourceModifier.AfterIncreased0;
    } else if (randomFloat < -0.70) {
      resourceModifier = ResourceModifier.AfterIncreased1;
    } else if (randomFloat < 1.95) {
      resourceModifier = ResourceModifier.Increased0;
    } else {
      resourceModifier = ResourceModifier.Increased1;
    }

    let resourceAmount = 0;
    if (resourceModifier === ResourceModifier.Flat ||
      resourceModifier === ResourceModifier.AfterIncreased0 ||
      resourceModifier === ResourceModifier.AfterIncreased1) {
      // ([0..3] x 3) * 20 + 60 == 150 +/- 90
      randomFloat = Math.floor(squirrel3(id + 3) / INTMAX32 * 4);
      randomFloat += Math.floor(squirrel3(id + 4) / INTMAX32 * 4); // base is 20 ish?
      randomFloat += Math.floor(squirrel3(id + 5) / INTMAX32 * 4); // base is 20 ish?
      resourceAmount = randomFloat * 20 + 60;
    } else {
      // 3 + [0..1] x 4 == 5% +/- 2
      randomFloat = Math.floor(squirrel3(id + 3) / INTMAX32 * 2); // base is 20 ish?
      randomFloat += Math.floor(squirrel3(id + 4) / INTMAX32 * 2); // base is 20 ish?
      randomFloat += Math.floor(squirrel3(id + 5) / INTMAX32 * 2); // base is 20 ish?
      randomFloat += Math.floor(squirrel3(id + 6) / INTMAX32 * 3); // base is 20 ish?
      resourceAmount = randomFloat + 3;
    }

    return {
      id,
      resourceAmount,
      resourceModifier,
      resourceType
    };
  }
}