import { ContextI } from "../ContextI"
import { segments } from "../shared/segments"
import { audio, playSong, disableSound, enableSound } from '../shared/audio'
import { StateI } from "./StateI"
import * as PIXI from 'pixi.js'
import * as planck from 'planck'
import { DecorationInstance } from "../shared/decors"
import { SpriteInstance, sprites } from "../shared/sprites"
import { ShapeCircle, ShapePoints } from "../data/sprite"
import { SegmentZone } from "../data/segment"
import { Zone } from "../live/Zone"
import { Entity } from "../live/Entity"
import { isPlayerEntity, PlayerEntity } from "../live/PlayerEntity"
import { Action } from "../live/Action"
import { WorldContext } from "../live/World"

export interface PIXIMissingColorMatrix extends PIXI.Filter {
  night(intensity: number, multiply: boolean): void
  contrast(amount: number, multiply: boolean): void
  brightness(amount: number, multiply: boolean): void
  saturate(amount: number, multiply: boolean): void
  hue(rotation: number, multiply: boolean): void
  colorTone(desaturation: number, toned: number, lightColor: number, darkColor: number, multiple: boolean): void
  reset(): void
}

export interface Layer {
  title: string
  container: PIXI.Container
  decorations: DecorationInstance[]
  colorMatrix: PIXIMissingColorMatrix
}

export function GameState(ctx: ContextI): StateI {
  //disableSound()
  let isNight = false
  let modeTimer = 0
  let nightTime = 30 * 1000
  let dayTime = 30 * 1000
  let lastTime: number = performance.now()

  let world: planck.World = planck.World({
    gravity: planck.Vec2(0, 0),
  })
  let worldBody: planck.Body = world.createBody({
    type: 'static',
  })
  world.on('begin-contact', (contact: planck.Contact) => {
    let a = contact.getFixtureA().getUserData()
    let b = contact.getFixtureB().getUserData()
    if (a instanceof Zone) {
      if (b instanceof Entity) {
        b.addZoneContact(a)
      }
    } else if (a instanceof Entity) {
      if (b instanceof Entity) {
        a.addEntityContact(b)
        b.addEntityContact(a)
      }
    }
  })
  world.on('end-contact', (contact: planck.Contact) => {
    let a = contact.getFixtureA().getUserData()
    let b = contact.getFixtureB().getUserData()
    if (a instanceof Zone) {
      if (b instanceof Entity) {
        b.removeZoneContact(a)
      }
    } else if (a instanceof Entity) {
      if (b instanceof Entity) {
        a.removeEntityContact(b)
        b.removeEntityContact(a)
      }
    }
  })

  let rootContainer = new PIXI.Container()
  let playLayer: Layer
  let layers: Layer[] = []
  let entities: Entity[] = []
  let zones: Zone[] = []

  let enter = () => {
    lastTime = performance.now()
    hookKeyboard()
    // Load the world segment.
    let w = segments.world
    if (!w) return ctx.pop()
    rootContainer.width = w.width
    rootContainer.height = w.height
    rootContainer.scale.set(2, 2)
    for (let l of w.layers) {
      let layer: Layer = {
        title: l.title,
        container: new PIXI.Container(),
        decorations: [],
        colorMatrix: new PIXI.filters.ColorMatrixFilter()
      }
      layer.container.filters = [layer.colorMatrix]
      layer.container.width = w.width
      layer.container.height = w.height

      if (l.title === 'objects') {
        playLayer = layer
        layer.container.sortableChildren = true
      }
      layers.push(layer)

      for (let d of l.decorations) {
        let di = new DecorationInstance(d.decor, d.decoration)
        di.elapsed = d.timeOffset
        di.container.x = d.x
        di.container.y = d.y
        if (d.rotation !== 0) {
          di.container.angle = d.rotation
        }
        if (d.flip) {
          di.container.pivot.y = 1
          di.container.scale.y *= -1
          if (d.rotation === 0) {
            di.container.position.y--
          }
        }
        if (d.mirror) {
          di.container.pivot.x = 1
          di.container.scale.x *= -1
          di.container.position.x--
          if (d.rotation !== 0) {
            di.container.angle = -d.rotation
            di.container.position.x++
            di.container.position.y--
          }
        }
        if (l.title === 'objects') {
          di.container.zIndex = di.container.position.y + di.container.height/4
        }
        layer.container.addChild(di.container)
        layer.decorations.push(di)
      }

      rootContainer.addChild(layer.container)
    }

    for (let z of w.zones) {
      addZone(new Zone(z))
    }

    // Add bogus entity
    addEntity(new PlayerEntity('deer.animal.stand.west.0'), 'objects', 300, 100)

    ctx.app.stage.addChild(rootContainer)
  }
  let leave = () => {
    unhookKeyboard()
    for (let entity of entities) {
      removeEntity(entity)
    }
    ctx.app.stage.removeChild(rootContainer)
  }
  let elapsed: number = 0
  let update = (delta: number) => {
    let time = performance.now()
    let realDelta = time - lastTime
    lastTime = time
    checkGamepads()
    elapsed += delta
    modeTimer += realDelta
    if (isNight && modeTimer >= nightTime) {
      modeTimer = 0
      nightfall(false)
    } else if (!isNight && modeTimer >= dayTime) {
      modeTimer = 0
      nightfall(true)
    }
    // Run world sim.
    while (elapsed >= 1/60) {
      world.step(1/60)
      elapsed -= 1/60
    }
    // Update/render.
    for (let layer of layers) {
      for (let decoration of layer.decorations) {
        decoration.update(realDelta)
      }
    }
    for (let entity of entities) {
      if (isPlayerEntity(entity)) {
        entity.act(desiredActions)
        // FIXME: This isn't the right place for this.
        rootContainer.position.set(Math.round(ctx.app.renderer.width/2), Math.round(ctx.app.renderer.height/2))
        rootContainer.pivot.set(Math.max(rootContainer.width/6, Math.min(entity.x, rootContainer.width/3)), Math.max(rootContainer.height/6, Math.min(entity.y, rootContainer.height/3)))
      } else {
        //console.log('hmm, we do be tickin', entity, entity.x, entity.y)
      }
      entity.update(realDelta, worldContext)
      // I guess...
      entity.sprite.container.x = entity.x
      entity.sprite.container.y = entity.y
      entity.sprite.container.zIndex = entity.y + (entity.sprite.frame?entity.sprite.frame.originY:0)
    }
  }

  let addEntity = (entity: Entity, layerTitle: string, x: number, y: number) => {
    console.log('add called with', x, y)
    if (entities.find(v=>v===entity)) return
    console.log('add somethin', entity)
    entities.push(entity)
    let layer = layers.find(v=>v.title===layerTitle)
    if (!layer) layer = playLayer
    layer.container.addChild(entity.sprite.container)
    // I guess this is a fair enough place to create physics and add it to the entity.
    let spriteShape = entity.sprite.getBodyShape()
    if (spriteShape) {
      let shape: planck.Shape|undefined
      if (spriteShape instanceof ShapeCircle) {
        shape = planck.Circle(planck.Vec2(spriteShape.x, spriteShape.y), spriteShape.radius)
      } else if (spriteShape instanceof ShapePoints) {
        shape = planck.Polygon(spriteShape.points.map(v=>planck.Vec2(v[0], v[1])))
      }
      if (shape !== undefined) {
        let body = world.createDynamicBody({
          position: planck.Vec2(entity.x, entity.y),
          fixedRotation: true,
        })
        let fixture = body.createFixture({
          shape,
          density: 1,
          friction: 0.9,
          restitution: 0.05,
        })
        fixture.setUserData(entity)
        entity.body = body
        body.setUserData(entity)
      }
    }
    entity.x = x
    entity.y = y
    console.log('set entity xy', entity.x, entity.y)
  }
  let removeEntity = (entity: Entity) => {
    entities = entities.filter(v=>v!==entity)
    entity.sprite.container.parent.removeChild(entity.sprite.container)
    if (entity.body) {
      world.destroyBody(entity.body)
      entity.body = undefined
    }
  }

  // Zonage
  let addZone = (zone: Zone) => {
    if (zones.find(v=>v===zone)) return
    let shape = planck.Chain(zone.points.map(v=>planck.Vec2(v[0],v[1])))
    zone.fixture = worldBody.createFixture({
      shape: shape,
    })
    if (zone.type === 'fluid') {
      zone.fixture.setSensor(true)
    }
    zone.fixture.setUserData(zone)
  }
  let removeZone = (zone: Zone) => {
    zones = zones.filter(v=>v!==zone)
    if (zone.fixture) {
      worldBody.destroyFixture(zone.fixture)
    }
  }

  let worldContext: WorldContext = {
    addEntity,
    removeEntity,
  }

  let desiredActions: Action[] = []
  let adjustAction = (type: string, v: number, inc?: boolean) => {
    let action = desiredActions.find(v=>v.type===type)
    if (!action) {
      if (v === 0) return
      desiredActions.push({
        type: type,
        priority: v,
      })
    } else {
      action.priority = inc?action.priority+v:v
      if (action.priority <= 0) {
        desiredActions = desiredActions.filter(v=>v!==action)
      }
    }
  }
  let keyup = (e: KeyboardEvent) => {
    if (e.key === 'ArrowLeft') {
      adjustAction('west', 0)
    } else if (e.key === 'ArrowRight') {
      adjustAction('east', 0)
    } else if (e.key === 'ArrowUp') {
      adjustAction('north', 0)
    } else if (e.key === 'ArrowDown') {
      adjustAction('south', 0)
    }
  }
  let keydown = (e: KeyboardEvent) => {
    if (e.repeat) return
    if (e.key === 'ArrowLeft') {
      adjustAction('west', 1)
    } else if (e.key === 'ArrowRight') {
      adjustAction('east', 1)
    } else if (e.key === 'ArrowUp') {
      adjustAction('north', 1)
    } else if (e.key === 'ArrowDown') {
      adjustAction('south', 1)
    }
  }
  let hookKeyboard = () => {
    window.addEventListener('keyup', keyup)
    window.addEventListener('keydown', keydown)
  }
  let unhookKeyboard = () => {
    window.removeEventListener('keyup', keyup)
    window.removeEventListener('keydown', keydown)
  }

  let gamepads: Record<string, Gamepad> = {}
  window.addEventListener('gamepadconnected', (ev: GamepadEvent) => {
    gamepads[ev.gamepad.id] = ev.gamepad
  })
  window.removeEventListener('gamepaddisconnected', (ev: GamepadEvent) => {
    delete gamepads[ev.gamepad.id]
  })
  function checkGamepads() {
    //for (let gp of Object.values(gamepads)) {
    for (let gp of navigator.getGamepads()) {
      if (!gp || !gp.connected) break
      if (gp.axes[0] < 0) {
        adjustAction('west', Math.abs(gp.axes[0]))
      } else if (gp.axes[0] > 0) {
        adjustAction('east', gp.axes[0])
      } else {
        adjustAction('east', 0)
        adjustAction('west', 0)
      }
      if (gp.axes[1] < 0) {
        adjustAction('north', Math.abs(gp.axes[1]))
      } else if (gp.axes[1] > 0) {
        adjustAction('south', gp.axes[1])
      } else {
        adjustAction('north', 0)
        adjustAction('south', 0)
      }
      //console.log(gp.axes, gp.buttons)
      let attackHeld = false
      for (let i = 0; i < 4; i++) {
        let btn = gp.buttons[i]
        if (btn.pressed) {
          attackHeld = true
        }
      }
      if (attackHeld) {
        adjustAction('attack', 2)
      } else if (desiredActions.find(v=>v.type==='attack')) {
        adjustAction('attack', 0)
      }
    }
  }

  let nightfall = (b: boolean) => {
    if (b) {
      playSong('GGJ-ScaryMusic')
      for (let l of layers) {
        //l.colorMatrix.brightness(0.25, false)
        l.colorMatrix.night(0.2, true)
        l.colorMatrix.saturate(-0.75, true)
        //l.colorMatrix.hue(160, true)
      }
      for (let e of entities) {
      }
    } else {
      playSong('GGJ-HappyMusic')
      for (let l of layers) {
        l.colorMatrix.reset()
      }
      for (let e of entities) {
      }
    }
    isNight = b
  }

  nightfall(false)

  return {
    enter,
    leave,
    update,
  }
}