import Cell from '../components/Cell'
import { connect } from 'react-redux'
import { compose, lifecycle, onlyUpdateForKeys, withState, withHandlers } from 'recompose'
import {
  triggerAsync as triggerEncounterAsync,
  setVariableParamsAsync as setVariableEncounterParamsAsync,
} from '../redux/actions/encounters'
import { setAsync as setWallsAsync } from '../redux/actions/walls'
import { setAsync as setCombatAsync } from '../redux/actions/combat'
import {
  setCoordsSync as setPartyCoordsSync,
  setExploredSync as setPartyExploredSync,
} from '../redux/actions/partyAgent'
import {
  cardinalToX,
  cardinalToY,
  getCardinalOpposite,
  getInitiative,
  initializeMonsters,
  getMonsterTypeQuantityPairs,
} from '../utility/helperFunctions'
import EventBus from 'eventing-bus'

import { cardinalDirections } from '../shared/settings'

import DungeonMapWindow from './DungeonMapWindow'
import DungeonMap from './DungeonMap'

class Dungeon extends React.Component {
  componentDidMount() {
    // const { setPartyCoordsSync } = this.props
    // const { coords } = this.props.partyAgent

    document.addEventListener(`keydown`, this.eventListener)
    console.log(`mounted`)
  }
  componentWillUnmount() {
    document.removeEventListener(`keydown`, this.eventListener)
    console.log(`unmounted`)
  }
  eventListener = async e => {
    //console.log(props)
    const { coords } = this.props.partyAgent

    const { party } = this.props.partyAgent
    const {
      combatContext: { setCombatRoundIsOnTo, combatRoundIsOn },
    } = this.props
    const {
      setMouseLookIsOnTo,
      mouseLookIsOn,
      partyAgent,
      combat,
      toggleMap,
      movementIsDisabled,
      setMovementIsDisabledTo,
      monsters,
      setPartyCoordsSync,
      setPartyExploredSync,
      setVariableEncounterParamsAsync,
      triggerEncounterAsync,
      teleporter,
      walls,
      floor,
      dungeons,
      encounters,
      setWallsAsync,
      setCombatAsync,
      isUnderwater,
      setIsUnderwaterTo,
    } = this.props
    let newFacing
    if (e.keyCode === 40) {
      if (coords.facing === `n`) newFacing = `s`
      if (coords.facing === `s`) newFacing = `n`
      if (coords.facing === `e`) newFacing = `w`
      if (coords.facing === `w`) newFacing = `e`
      setPartyCoordsSync({ ...coords, facing: newFacing })
    }
    if (e.keyCode === 39) {
      if (coords.facing === `n`) newFacing = `e`
      if (coords.facing === `e`) newFacing = `s`
      if (coords.facing === `s`) newFacing = `w`
      if (coords.facing === `w`) newFacing = `n`
      setPartyCoordsSync({ ...coords, facing: newFacing })
    }
    if (e.keyCode === 37) {
      if (coords.facing === `n`) newFacing = `w`
      if (coords.facing === `w`) newFacing = `s`
      if (coords.facing === `s`) newFacing = `e`
      if (coords.facing === `e`) newFacing = `n`
      setPartyCoordsSync({
        ...coords,
        facing: newFacing,
      })
    }

    //autocombat

    const moveParty = async () => {
      let newX, newY, newZ, newDungeon, oldDungeon
      newX = coords.x
      newY = coords.y
      newZ = coords.z
      oldDungeon = coords.dungeon
      newDungeon = coords.dungeon

      const wallCoords = walls[coords.dungeon][`x${coords.x}y${coords.y}z${coords.z}`]

      const flagAsExplored = ({ x, y, z }) => {
        const { explored } = this.props.partyAgent
        if (!explored[coords.dungeon] || !explored[coords.dungeon][`x${x}y${y}z${z}`]) {
          setPartyExploredSync({
            [`${coords.dungeon}`]: { ...explored[coords.dungeon], [`x${x}y${y}z${z}`]: true },
          })
        }
      }
      const cardinalToAxis = cardinal => {
        switch (cardinal) {
          case `n`:
            return `x${coords.x}y${coords.y + 1}z${coords.z}`
          case `s`:
            return `x${coords.x}y${coords.y - 1}z${coords.z}`
          case `e`:
            return `x${coords.x + 1}y${coords.y}z${coords.z}`
          case `w`:
            return `x${coords.x - 1}y${coords.y}z${coords.z}`
          default:
            console.error(`cardinal '${cardinal}' not found`)
            break
        }
      }

      const unobstructedIn = cardinal => {
        return (
          !(wallCoords && wallCoords[cardinal] === 1) &&
          !(floor[coords.dungeon][`${cardinalToAxis(cardinal)}`] === 1)
        )
      }

      const handleUnlockedDoor = cardinal => {
        if (wallCoords && wallCoords[cardinal] === 3) {
          EventBus.publish(`doorOpening`, {
            x: coords.x,
            y: coords.y,
            z: coords.z,
            dungeon: coords.dungeon,
            cardinal,
          })
          EventBus.publish(`doorOpening`, {
            x: coords.x + cardinalToX(cardinal),
            y: coords.y + cardinalToY(cardinal),
            z: coords.z,
            dungeon: coords.dungeon,
            cardinal: getCardinalOpposite(cardinal),
          })
        }
      }
      const hasReachedMapLimitAt = cardinal => {
        switch (cardinal) {
          case `n`:
            return coords.y === dungeons[coords.dungeon].dimensions.y - 1
          case `s`:
            return coords.y === 0
          case `e`:
            return coords.x === dungeons[coords.dungeon].dimensions.x - 1
          case `w`:
            return coords.x === 0
          default:
            break
        }
      }
      cardinalDirections.forEach(async cardinal => {
        if (coords.facing === cardinal && !hasReachedMapLimitAt(cardinal)) {
          if (teleporter[coords.dungeon][`${cardinalToAxis(cardinal)}`]) {
            const { x, y, z, dungeon } = teleporter[coords.dungeon][
              `${cardinalToAxis(cardinal)}`
            ].destination
            newX = x
            newY = y
            newZ = z
            newDungeon = dungeon
          } else if (wallCoords && wallCoords[cardinal] === 2) {
            newX = coords.x + cardinalToX(cardinal)
            newY = coords.y + cardinalToY(cardinal)
            newZ = coords.z + 1
            flagAsExplored({
              x: newX - cardinalToX(cardinal),
              y: newY - cardinalToY(cardinal),
              z: newZ,
            })
          } else if (floor[coords.dungeon][`${cardinalToAxis(cardinal)}`] === 2) {
            newX = coords.x + cardinalToX(cardinal)
            newY = coords.y + cardinalToY(cardinal)
            newZ = coords.z - 1
            flagAsExplored({ x: newX, y: newY, z: coords.z })
          } else if (floor[coords.dungeon][`${cardinalToAxis(cardinal)}`] === 6) {
            newX = coords.x + cardinalToX(cardinal)
            newY = coords.y + cardinalToY(cardinal)
            newZ = coords.z - 1
            flagAsExplored({ x: newX, y: newY, z: coords.z })
          } else if (unobstructedIn(cardinal)) {
            newX = coords.x + cardinalToX(cardinal)
            newY = coords.y + cardinalToY(cardinal)
            newZ = coords.z
          }
          handleUnlockedDoor(cardinal)
        }
      })

      if (oldDungeon !== newDungeon) {
        EventBus.publish(`toggleMouseLook`, true)
        setMovementIsDisabledTo(true)
        await window.location.reload(true)
      }
      await setPartyCoordsSync({ ...coords, x: newX, y: newY, z: newZ, dungeon: newDungeon })
      await flagAsExplored({ x: newX, y: newY, z: newZ })

      const encounterAtCoords = encounters[coords.dungeon][`x${newX}y${newY}z${newZ}`]
      const floorAtCoords = floor[coords.dungeon][`x${newX}y${newY}z${newZ}`]

      if (floorAtCoords === 5) {
        setIsUnderwaterTo(true)
        EventBus.publish(`setAmbientLight`, { color: `rgb(0,50,255)`, intensity: 1 })
      } else {
        console.log(isUnderwater)
        if (isUnderwater === true) {
          setIsUnderwaterTo(false)
          EventBus.publish(`setAmbientLight`, {
            color: `0xcccccc`,
            intensity: 0.2,
          })
        }
      }

      if (encounterAtCoords) {
        console.log(`encounter tile!`)

        const { id: encounterId, type: encounterType } = encounterAtCoords

        const spawnedEncounter = encounters[coords.dungeon][encounterId]

        const repositionMonstersForCombat = () => {
          const mids = Object.keys(spawnedEncounter.monsters)
          const frontRow = mids
            .filter(mid => spawnedEncounter.monsters[mid].currentRow === 0)
            .map(mid => `${encounterId}-${mid}`)
          const backRow = mids
            .filter(mid => spawnedEncounter.monsters[mid].currentRow === 1)
            .map(mid => `${encounterId}-${mid}`)

          EventBus.publish(`repositionMonsterForCombat`, { frontRow, backRow })
        }

        const resumeEncounterAndStartCombat = async () => {
          repositionMonstersForCombat()
          await setCombatAsync({
            isOn: true,
            encounterId,
            encounterType,
            round: 1,
            initiative: getInitiative({
              party: partyAgent.party,
              monsters: spawnedEncounter.monsters,
            }),
          })
          console.log(
            `no rest for the wicked!`,
            `we missed you... new spawn in ${30000 - Date.now() + spawnedEncounter.spawnedAt}`
          )
        }
        const spawnEncounterAndStartCombat = async () => {
          console.log(`fresh meat!`)

          const monsterTypeQuantityPair = getMonsterTypeQuantityPairs({
            probabilityTable: encounters.__types[encounterType],
          })
          console.log(`monsterTypeQuantityPair: `, monsterTypeQuantityPair)
          const monsterParams = {
            monstersDead: {},
            monsters: Object.keys(monsterTypeQuantityPair).reduce((acc, monsterType, j) => {
              const quantity = Object.values(monsterTypeQuantityPair)[j]
              for (let i = 0; i < quantity; i++) {
                acc[`${monsterType}${i}`] = monsters[monsterType]
              }
              return acc
            }, {}),
          }

          await setVariableEncounterParamsAsync({
            dungeon: coords.dungeon,
            id: encounterId,
            spawnedAt: Date.now(),
            ...monsterParams,
          })

          await setCombatAsync({
            isOn: true,
            encounterId,
            encounterType,
            round: 1,
            initiative: getInitiative({
              party: partyAgent.party,
              monsters: monsterParams.monsters,
            }),
          })
        }

        if (
          // cell cleared of monsters that have not respawned yet
          spawnedEncounter &&
          (typeof spawnedEncounter.monsters === `undefined` ||
            Object.keys(spawnedEncounter.monsters).length === 0)
          //&& Date.now() < spawnedEncounter.spawnedAt + 30000
        ) {
          if (combat.isOn) {
            console.log(`you escaped!`)
            await setCombatAsync({
              encounterId: null,
              isOn: false,
              round: null,
              encounterType: null,
            })
          }
          console.log(
            `only bodies here... come back in ${30000 - Date.now() + spawnedEncounter.spawnedAt}`
          )
        } else if (
          // monsters from old respawn are still there
          spawnedEncounter &&
          (typeof spawnedEncounter.monsters !== `undefined` &&
            Object.keys(spawnedEncounter.monsters).length > 0) &&
          Date.now() < spawnedEncounter.spawnedAt + 30000
        ) {
          if (combat.isOn && combat.encounterId !== encounterId) {
            await setCombatAsync({
              isOn: false,
            })

            await resumeEncounterAndStartCombat()
          } else if (combat.isOn && combat.encounterId === encounterId) {
            repositionMonstersForCombat()
            console.log(`the show must go on!`)
          } else if (!combat.isOn) {
            await resumeEncounterAndStartCombat()
          }
        } else {
          if (combat.isOn && combat.encounterId !== encounterId) {
            await setCombatAsync({
              isOn: false,
            })
            console.log(`no rest for the wicked!`)
            // controls whether entering an unfinished encounter could respawn it

            repositionMonstersForCombat()

            await resumeEncounterAndStartCombat()
            //await spawnEncounterAndStartCombat()
          } else if (combat.isOn && combat.encounterId === encounterId) {
            repositionMonstersForCombat()
            console.log(`the show must go on!`)
          } else if (!combat.isOn) {
            // controls whether entering an unfinished encounter could respawn it
            await resumeEncounterAndStartCombat()
            //await spawnEncounterAndStartCombat()
          }
        }
      } else {
        if (combat.isOn) {
          console.log(`you escaped!`)
          await setCombatAsync({
            encounterId: null,
            isOn: false,
            round: null,
            encounterType: null,
          })
        }
      }
    }
    const getChanceToGetBlocked = () => {
      return 0
      //return Math.random() > 0.5 ? true : false
    }
    const respawnEncounters = async ({ monstersTable }) => {
      console.log(`checking respawn`)
      const encounterData = this.props.encounters[partyAgent.coords.dungeon]

      const encounterSchemaKeys = Object.keys(encounterData).filter(key => key[0] === `x`)

      const encounterSchemas = encounterSchemaKeys.reduce((acc, x) => {
        acc[x] = encounterData[x]
        return acc
      }, {})

      let uniqueEids = []
      const uniqueEncounterSchemaValues = Object.values(encounterSchemas).filter(schema => {
        if (!uniqueEids.includes(schema.id)) {
          uniqueEids.push(schema.id)
          return true
        } else {
          return false
        }
      })

      console.log(uniqueEncounterSchemaValues)

      for (let encounterSchemaValue of uniqueEncounterSchemaValues) {
        //console.log(`monsterTypeQuantityPairs: `, monsterTypeQuantityPairs)

        if (
          !encounterData[encounterSchemaValue.id] ||
          (encounterData[encounterSchemaValue.id].vanquishedDate &&
            encounterData[encounterSchemaValue.id].vanquishedDate < Date.now() - 3000)
        ) {
          const monsterParams = {
            monstersDead: {},
            monsters: initializeMonsters({
              probabilityTable: encounters.__types[encounterSchemaValue.type],
              monstersTable,
            }),
          }
          await setVariableEncounterParamsAsync({
            dungeon: partyAgent.coords.dungeon,
            id: encounterSchemaValue.id,
            spawnedAt: Date.now(),
            ...monsterParams,
          })
          const encounterCoords = encounterSchemaKeys.filter(
            key => encounterSchemas[key].id === encounterSchemaValue.id
          )
          EventBus.publish(`spawnEncounter`, {
            coords: encounterCoords,
            monsters: Object.keys(monsterParams.monsters),
            eid: encounterSchemaValue.id,
          })
          console.log(`spawned ${encounterSchemaValue.id}`)
        }
      }
    }
    if (e.keyCode === 38) {
      if (!movementIsDisabled) {
        await setMovementIsDisabledTo(true)
        if (this.props.combatContext.combatRoundIsOn === false) {
          if (combat.isOn === false) {
            moveParty()
            respawnEncounters({ monstersTable: monsters })
          } else {
            const gotBlocked = getChanceToGetBlocked()
            if (gotBlocked) {
              console.log(`the monsters blocked your path!`)
            } else {
              moveParty()
            }
          }
        } else {
          console.log(`can't move during combat round!`)
        }
        await setMovementIsDisabledTo(false)
      } else {
        console.log(`movement is disabled`)
      }
    }
    if (e.keyCode === 89) {
      respawnEncounters()
    }
    if (e.keyCode === 77) {
      toggleMap()
    }
    if (e.keyCode === 88) {
      //if (combat.isOn === false) {
      setMovementIsDisabledTo(x => !x)
      setMouseLookIsOnTo(x => !x)
      EventBus.publish(`toggleMouseLook`, !mouseLookIsOn)
      //}
    }
  }

  render() {
    const { showMap } = this.props
    return (
      <div {...{ style: { display: `flex`, flexDirection: `column` } }}>
        {showMap && <DungeonMapWindow />}
        <div>
          {this.props.combatContext.combatRoundIsOn ? `combat round is ON` : `combat round is OFF`}
        </div>
        <DungeonMap />
      </div>
    )
  }
}
const mapStateToProps = state => ({
  walls: state.walls,
  floor: state.floor,
  monsters: state.monsters,
  encounters: state.encounters,
  dungeons: state.dungeons,
  combat: state.combat,
  teleporter: state.teleporter,
  partyAgent: state.partyAgent,
})

const enhance = compose(
  withState(`movementIsDisabled`, `setMovementIsDisabledTo`, false),
  withState(`mouseLookIsOn`, `setMouseLookIsOnTo`, false),
  withState(`isUnderwater`, `setIsUnderwaterTo`, false),
  withState(`showMap`, `setShowMap`, false),
  withHandlers({
    closeMap: ({ setShowMap }) => () => {
      setShowMap(false)
    },
    toggleMap: ({ setShowMap }) => () => {
      setShowMap(x => !x)
    },
  }),
  connect(
    mapStateToProps,
    {
      setWallsAsync,
      setPartyCoordsSync,
      setPartyExploredSync,
      triggerEncounterAsync,
      setCombatAsync,
      setVariableEncounterParamsAsync,
    }
  )
  //onlyUpdateForKeys([ `partyAgent` ])
)

export default enhance(Dungeon)
