import alea from 'alea'
import { createNoise2D, type NoiseFunction2D } from 'simplex-noise'
import createBlockGenerator from './blockGen'
import createBlockExtender from './blockExt'

import {blockTypes, blockTypes as T, level as L, type Block, type BlockType} from './def'

// describes a changed block, eg digged or placed by the player
type DamagedBlock = {
  type: 'damage'
  x: number
  y: number
  damage: number
}
type ChangedBlock = {
  type: 'exchange'
  x: number
  y: number
  newType: BlockType
}
type Change = DamagedBlock | ChangedBlock

const MAX_LIGHT = 100 // maximum level where light shines

export default function createLevel(width: number, height: number, seed = 'extremely random seed') {
  const prng = alea(seed)
  const noise2D = createNoise2D(prng)
  const rand: NoiseFunction2D = (x, y) => 0.5 + 0.5 * noise2D(x, y)

  // stores the current grid of blocks, visible on the screen
  const _grid: Block[][] = new Array(height)
  // stores the limit to where light still shines,
  // for each column currently visible on the screen
  const _lightBarrier: number[] = [...new Array(width)].map(() => MAX_LIGHT)
  const _changes: Change[][] = []

  const blockGen = createBlockGenerator(rand)
  const blockExt = createBlockExtender(rand)

  // Apply changes, coming from the player (tocktocktock-plopp!)
  function change(change: Change) {
    const x = change.x
    if (!_changes[x]) _changes[x] = [change]
    else _changes[x].push(change)
  }

  function applyPlayerChanges(columnOffset: number, levelOffset: number) {
    for (let col = columnOffset; col < columnOffset + width; col++) {
      const changes = _changes[col]
      if (changes) {
        const maxLevel = levelOffset + height
        changes.forEach(c => {
          if (c.type !== 'exchange' || c.y < levelOffset || c.y >= maxLevel) return
          _grid[c.y - levelOffset][c.x - columnOffset] = blockTypes[c.newType]
        })
      }
    }
  }

  // takes the current columnOffset and generates all blocks from the very top
  // until a block is generated that blocks light. The height of that block is
  // stored in the lightBarrier list
  function calcLightBarrier(columnOffset: number) {
    let previousBlock: Block = T.air

    for (let col = 0; col < width; col++) {
      for (let level = 0; level < MAX_LIGHT; level++) {
        let block = blockGen.generateBlock(level, col + columnOffset)
        block = blockExt.extendBlock(level, col, columnOffset, block, previousBlock)

        const changes = _changes[columnOffset + col]
        if (changes) {
          const change = changes.find(c => c.y === level)
          if (change && change.type === 'exchange') block = blockTypes[change.newType]
        }

        previousBlock = block

        if (!block.transparent) {
          _lightBarrier[col] = level
          break
        }
      }
    }
  }

  function generate(columnOffset: number, levelOffset: number) {
    for (let i = 0; i < height; i++) {
      const level = levelOffset + i
      const row: Block[] = Array(width)
      const previousRow = i ? _grid[i-1] : [] as Block[]

      blockGen.fillRow(level, columnOffset, row)
      blockExt.extendRow(level, columnOffset, row, previousRow)

      _grid[i] = row
    }
    applyPlayerChanges(columnOffset, levelOffset)
  }

  function sunLight(columnOffset: number) {
    calcLightBarrier(columnOffset)
    return _lightBarrier
  }

  function grid(x: number, y: number) {
    generate(x, y)
    return _grid
  }

  return { grid, sunLight, change }
}
