import * as planck from 'planck'
import { AnimalDefinition, CreatureDefinition } from '../data/animals'
import { playSong, playSound } from '../shared/audio'
import { Action, adjustAction } from "./Action"
import { Entity, Sensor } from "./Entity"
import { GibletEntity } from './GibletEntity'
import { PuddleEntity } from './PuddleEntity'
import { WorldContext } from './World'

export class AnimalEntity extends Entity {
  action?: Action
  puddleTimer: number = 0
  isPlayer: boolean = false
  isMonster: boolean = false
  thinkTimer: number = 0 // Time since last thought, used by AI.
  wanderTimer: number = 0 // Time since last wander.
  nextWander: number = 100 // Time to next wander.
  smelledTarget?: Entity
  seenTarget?: Entity
  killer?: AnimalEntity
  def: AnimalDefinition
  mode: CreatureDefinition
  desiredActions: Action[] = []
  stepSoundElapsed: number = 0
  lastYellElapsed: number = 0
  nextYell: number = 1000
  dead: boolean = false
  shouldGib: boolean = false
  actionCooldown: number = 0
  kills: number = 0

  constructor(def: AnimalDefinition) {
    super(`${def.name}.animal.stand.west.0`)
    this.def = def
    this.mode = def.animal
    this.maxSpeed = def.animal.maxSpeed
    this.acceleration = def.animal.acceleration
    this.turnRate = def.animal.turnRate
  }
  act(actions: Action[]) {
    this.action = actions.sort((a, b) => {
      if (a.priority < b.priority) {
        return 1
      }
      if (a.priority > b.priority) {
        return -1
      }
      return 0
    })[0]
  }
  update(delta: number, ctx: WorldContext) {
    super.update(delta)

    if (this.dead) {
      if (this.shouldGib) {
        this.gib(ctx)
        this.shouldGib = false
      }
      this.shouldRemove = true
      return
    }

    this.lastYellElapsed += delta

    let waterZones = this.zones.filter(v=>v.type==='fluid')
    if (waterZones.length && !this.isMonster) {
      this.puddleTimer += delta
      if (this.puddleTimer >= 600) {
        if (ctx) {
          let p = this.position
          p[0] += ((this.sprite.frame?.width??0)/2??0) - 4
          p[1] += ((this.sprite.frame?.height??0)/2??0)
          ctx.addEntity(new PuddleEntity('effects.water.ripple.small.0'), 'ground', p[0], p[1])

          let v = 1 + Math.floor(Math.random() * 2)
          playSound(`action/splash-v${v}`, 0.25+Math.floor(Math.random()*.25))
        }
        this.puddleTimer = 0
      }
    }
    let shouldMove = false
    let shouldAttack = false
    if (this.action) {
      // FIXME: Use physics.
      switch(this.action.type) {
        case 'west':
          if (this.direction !== 0) {
            if (this.direction < 180) {
              this.direction -= this.mode.turnRate
            } else {
              this.direction += this.mode.turnRate
            }
          }
          shouldMove = true
          break
        case 'east':
          if (this.direction !== 180) {
            if (this.direction < 180) {
              this.direction += this.mode.turnRate
            } else {
              this.direction -= this.mode.turnRate
            }
          }
          shouldMove = true
          break
        case 'north':
          if (this.direction !== 90) {
            if (this.direction < 90 || this.direction > 270) {
              this.direction += this.mode.turnRate
            } else {
              this.direction -= this.mode.turnRate
            }
          }
          shouldMove = true
          break
        case 'south':
          if (this.direction !== 270) {
            if (this.direction > 90 && this.direction < 270) {
              this.direction += this.mode.turnRate
            } else {
              this.direction -= this.mode.turnRate
            }
          }
          shouldMove = true
          break
        case 'attack':
          shouldAttack = true
          break
      }
      if (this.direction > 360) {
        this.direction = 0
      } else if (this.direction < 0) {
        this.direction = 360
      }
    }

    this.actionCooldown += delta
    if (this.actionCooldown >= 750 - 750 * this.mode.cdr) {
      if (shouldAttack) {
        this.attack(ctx)
        this.actionCooldown = 0
      } else if (shouldMove) {
        this.stepSoundElapsed += delta
        let r = this.direction * (Math.PI/180)
        if (Math.abs(this.velocity[0]) < this.mode.maxSpeed) {
          this.velocity[0] -= Math.cos(r) * this.mode.acceleration
        }
        if (Math.abs(this.velocity[1]) < this.mode.maxSpeed) {
          this.velocity[1] -= Math.sin(r) * this.mode.acceleration
        }
        let cardinal = this.getCardinal(this.direction)
        this.sprite.setKey = 'run'
        if (this.sprite.subsetKey !== cardinal || this.sprite.subsetKey !== 'run') {
          this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${cardinal}.${this.sprite.frameIndex}`)
        }
        if (this.stepSoundElapsed >= 200*(this.isMonster?1.5:1)) {
          let v = 1 + Math.floor(Math.random() * 4)
          if (this.isMonster) {
            playSound('action/footstep-big-v'+v, 0.5)
          } else {
            playSound('action/footstep-tiny-v'+v, 0.5)
          }
          this.stepSoundElapsed = 0
        }
        this.sprite.animate = true
        this.sprite.loop = true
      } else {
        this.sprite.animate = false
        this.sprite.loop = true
        this.sprite.setKey = 'stand'
        this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${this.getCardinal(this.direction)}.0`)
      }
    }

    //
    this.velocity[0] *= 0.65
    this.velocity[1] *= 0.65

    // Eh... let's manually handle velocity
    this.body?.setLinearVelocity(planck.Vec2(this.velocity[0], this.velocity[1]))
  }
  getCardinal(direction: number): string {
    const degreesPerDirection = 360 / 4
    const angle = direction + degreesPerDirection / 2

    if (angle >= 0 * degreesPerDirection && angle < 1 * degreesPerDirection) {
      return 'west'
    } else if (angle >= 1 * degreesPerDirection && angle < 2 * degreesPerDirection) {
      return 'north'
    } else if (angle >= 2 * degreesPerDirection && angle < 3 * degreesPerDirection) {
      return 'east'
    }
    return 'south'
  }
  think(delta: number) {
    this.thinkTimer += delta
    while (this.thinkTimer >= 100) {
      if (this.seenTarget && this.isMonster && planck.Vec2.distance(planck.Vec2(this.x, this.y), planck.Vec2(this.seenTarget.x, this.seenTarget.y)) < 30) {
        this.desiredActions = adjustAction([], 'attack', 1)
        if (Math.random() > 1 - this.mode.laziness/4) {
          this.desiredActions = []
        }
      } else if (this.seenTarget) {
        let r = Math.atan2(this.y-(this.seenTarget.y+10), this.x-this.seenTarget.x) // FIXME: 10 ain't right son
        let a = r * (180 / Math.PI)
        if (a < 0) {
          a += 360
        }
        // Invert direction if not a monster.
        if (!this.isMonster) {
          a -= 180
        }
        if (a < 0) {
          a = 360 - a
        }
        this.desiredActions = adjustAction([], this.getCardinal(a), 0.85)
        this.desiredActions = adjustAction(this.desiredActions, this.getCardinal(Math.random()*360), Math.random())
        if (Math.random() > 1 - this.mode.erratic) {
          this.desiredActions = adjustAction(this.desiredActions, this.getCardinal(Math.random()*360), 1)
        }
        this.yell(1)
      } else if (this.smelledTarget) {
        let r = Math.atan2(this.y-this.smelledTarget.y, this.x-this.smelledTarget.x)
        let a = r * (180 / Math.PI)
        if (a < 0) {
          a += 360
        }
        if (!this.isMonster) {
          a -= 180
        }
        if (a < 0) {
          a = 360 - a
        }
        // 5% to walk away, otherwise just chill.
        if (Math.random() > 0.95) {
          this.desiredActions = adjustAction([], this.getCardinal(a), 0.85)
        }
      } else {
        this.wanderTimer += delta
        if (this.wanderTimer >= this.nextWander) {
          this.nextWander = Math.max(10, Math.random() * 100)
          if (Math.random() > 1 - this.mode.laziness) {
            this.desiredActions = []
          } else {
            this.desiredActions = adjustAction([], this.getCardinal(Math.random()*360), 1)
          }
          if (Math.random() > 1 - this.mode.noisiness) {
            this.yell(0.1)
          }
          this.wanderTimer = 0
        }
      }
      this.thinkTimer -= 100
      this.act(this.desiredActions)
    }
  }
  sense(sensor: Sensor, entity: AnimalEntity) {
    if (this.isPlayer) return
    if (sensor.type === 'long') {
      this.smell(entity)
    } else {
      this.see(entity)
    }
  }
  lost(sensor: Sensor, entity: AnimalEntity) {
    if (this.isPlayer) return
    if (sensor.type === 'long') {
      this.unsmell(entity)
    } else {
      this.unsee(entity)
    }
  }
  smell(entity: AnimalEntity) {
    if (entity.isPlayer) {
      this.smelledTarget = entity
    }
  }
  unsmell(entity: AnimalEntity) {
    if (entity.isPlayer) {
      this.smelledTarget = undefined
    }
  }
  see(entity: AnimalEntity) {
    if (entity.isPlayer) {
      this.seenTarget = entity
    }
  }
  unsee(entity: AnimalEntity) {
    if (entity.isPlayer) {
      this.seenTarget = undefined
    }
  }
  turn() {
    this.isMonster = !this.isMonster
    if (this.isMonster) {
      if (this.def.name !== 'deer' && this.def.name !== 'turkey') {
        this.sprite.setCtor(`${Math.random()>0.5?'deer':'turkey'}.monster.${this.sprite.setKey}.${this.sprite.subsetKey}.${this.sprite.frameIndex}`)
      } else {
        this.sprite.setCtor(`${this.sprite.spriteKey}.monster.${this.sprite.setKey}.${this.sprite.subsetKey}.${this.sprite.frameIndex}`)
      }
    } else {
      if (this.sprite.setKey === 'attack') {
        this.sprite.setKey = 'stand'
      }
      this.sprite.setCtor(`${this.def.name}.animal.${this.sprite.setKey}.${this.sprite.subsetKey}.${this.sprite.frameIndex}`)
    }
  }
  attack(ctx: WorldContext) {
    if (this.isMonster) {
      this.sprite.animate = true
      this.sprite.loop = false
      this.sprite.setKey = 'attack'
      this.sprite.frameIndex = 0
      this.sprite.setCtor(`${this.sprite.spriteKey}.${this.sprite.animationKey}.${this.sprite.setKey}.${this.sprite.subsetKey}.${this.sprite.frameIndex}`)

      // Time to get extra lame...
      let c = this.getCardinal(this.direction)

      if (this.body) {
        let p = this.body.getPosition()
        let box = [
          [p.x-10, p.x+10],
          [p.y-10, p.y+10]
        ]
        if (c === 'east') {
          box[0][0] += 20
          box[0][1] += 20
        } else if (c === 'west') {
          box[0][0] -= 20
          box[0][1] -= 20
        } else if (c === 'north') {
          box[1][0] -= 10
          box[1][1] -= 10
        } else {
          box[1][0] += 20
          box[1][1] += 20
        }

        let entities: AnimalEntity[] = ctx.queryEntities([box[0][0], box[1][0]], [box[0][1], box[1][1]]).filter(v=>v instanceof AnimalEntity && v !== this && !v.isMonster && !v.dead) as AnimalEntity[]
        if (entities.length > 0) {
          let v = 1 + Math.floor(Math.random() * 4)
          playSound(`action/hit-v${v}`, 1.0)
          entities[0].dead = true
          entities[0].shouldGib = true
          if (Math.random() > 0.5) {
            this.yell(1)
          }
          entities[0].killer = this
          this.kills++
        } else {
          // growl if there are no targets.
          this.yell(1)
        }
      }
    } else {
      this.yell(1)
    }
  }
  yell(volume: number) {
    if (this.lastYellElapsed > this.nextYell) {
      this.nextYell = 500 + Math.random()*3000
      this.lastYellElapsed = 0
      let v = 1 + Math.floor(Math.random() * 3)
      if (this.isMonster) {
        playSound(`monsters/evil-${this.def.name}-v${v}`, volume)
      } else {
        playSound(`animals/${this.def.name}-v${v}`, volume)
      }
    }
  }
  gib(ctx: WorldContext) {
    let p = this.position
    p[0] += this.sprite.container.width/2
    p[1] += this.sprite.container.height/2
    this.dead = true
    let force = -1 + Math.random() * 2
    let dir = Math.random() * 320

    // 100% random blood splat
    ctx.addEntity(new GibletEntity(`giblets.any.blood${Math.floor(1+Math.random()*4)}.default.0`, dir+Math.random()*10, force+Math.random()*2, 30000), 'gibs', p[0], p[1])
    // 50% extra blood change
    if (Math.random() > 0.5) {
      ctx.addEntity(new GibletEntity(`giblets.any.blood${Math.floor(1+Math.random()*4)}.default.0`, dir+Math.random()*10, force+Math.random()*2, 20000), 'gibs', p[0], p[1])
    }

    // 50% head gib
    if (Math.random() > 0.5) {
      ctx.addEntity(new GibletEntity(`giblets.${this.sprite.spriteKey}.head.default.0`, dir+Math.random()*40, force+Math.random()*10), 'gibs', p[0], p[1])
    }
    // 44% leg gib per leg
    for (let i = 0; i < 4; i++) {
      if (Math.random() > 0.66) {
        // FIXME: We should really query gib sprites for leg/tail/etc., then decide to spawn based on that.
        let type = 'leg'
        if (this.sprite.spriteKey === 'salamander') {
          type = 'tail'
        }
        ctx.addEntity(new GibletEntity(`giblets.${this.sprite.spriteKey}.${type}.default.0`, dir+Math.random()*40, force+Math.random()*10), 'gibs', p[0], p[1])
      }
    }
    // 35% extra gibs per 8
    for (let i = 0; i < 8; i++) {
      if (Math.random() > 0.65) {
        ctx.addEntity(new GibletEntity(`giblets.any.chunk${Math.floor(1+Math.random()*4)}.default.0`, dir+Math.random()*40, force+Math.random()*10), 'gibs', p[0], p[1])
      }
    }
    playSound(`action/splat-v${1+Math.floor(Math.random()*7)}`, 0.5)
  }
}

export function isAnimalEntity(o: any): o is AnimalEntity {
  return o.act
}
