// one way of solving state async would be to maintain compnonent state to track party and monster stats parallel to the redux state, and update redux state at round's end

import database from '../services/database'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { randomIntegerInRange, delay } from '../utility/helperFunctions'
import { compose, onlyUpdateForKeys } from 'recompose'
import {
  setVariableParamsSync as setVariableEncounterParamsSync,
  setMonsterParamSync,
} from '../redux/actions/encounters'
import { setAsync as setCombatAsync } from '../redux/actions/combat'
import { createAsync as createLogAsync } from '../redux/actions/logs'

import { setPartyMemberParamSync } from '../redux/actions/partyAgent'
import Main from './Main'
import EventBus from 'eventing-bus'

let returnValue, params

class CombatControls extends React.Component {
  state = {
    roundIsOn: false,
    encounterData: this.props.encounters[this.props.partyAgent.coords.dungeon][
      this.props.combat.encounterId
    ],
    monstersAlive: this.props.encounters[this.props.partyAgent.coords.dungeon][
      this.props.combat.encounterId
    ].monsters,
    encounteredMonstersDead: this.props.encounters[this.props.partyAgent.coords.dungeon][
      this.props.combat.encounterId
    ].monstersDead,
    party: this.props.partyAgent.party,
  }
  componentDidUpdate(prevProps) {
    //console.log(`did update`)
    //console.log(this.props)
    const {
      combat: { encounterId },
      partyAgent: {
        coords: { dungeon },
        party,
      },
      encounters,
    } = this.props
    if (prevProps.encounters !== encounters) {
      this.setState({
        encounterData: encounters[dungeon][encounterId],
        monstersAlive: encounters[dungeon][encounterId].monsters,
        encounteredMonstersDead: encounters[dungeon][encounterId].monstersDead,
      })
    }
    if (prevProps.partyAgent.party !== party) {
      this.setState({
        party,
      })
    }
  }
  listener = async e => {
    const {
      partyAgent: { party, coords },
      encounters,
      items,
      createLogAsync,
      combat: { initiative, encounterId, round },
      setVariableEncounterParamsSync,
      setMonsterParamSync,
      setCombatAsync,
      targets,
      setPartyMemberParamSync,
      setTarget,
    } = this.props

    const agentIdsSortedByInitiative = Object.entries(initiative)
      .sort((a, b) => a[1] > b[1])
      .map(entry => entry[0])

    const startRound = async ({ onTheMove }) => {
      //console.log(agentIdsSortedByInitiative)

      await createLogAsync({ date: Date.now(), type: `combat`, message: `Round ${round}.` })

      await setCombatAsync({
        round: round + 1,
      })

      if (
        Object.values(this.state.monstersAlive).filter(value => value.currentRow === 0).length === 0
      ) {
        if (
          Object.values(this.state.monstersAlive).filter(value => value.currentRow === 1).length > 0
        ) {
          await setVariableEncounterParamsSync({
            ...this.state.encounterData,
            dungeon: coords.dungeon,
            id: encounterId,
            monsters: Object.keys(this.state.monstersAlive).reduce((acc, key, i) => {
              acc[key] = { ...Object.values(this.state.monstersAlive)[i], currentRow: 0 }
              return acc
            }, {}),
          })
          await this.forceUpdate()
          console.log(`no one left at the front! the back row now stands on the front lines`)
        }
      }

      for (let agentId of agentIdsSortedByInitiative) {
        if (/^\d{1}/.test(agentId) && onTheMove === false) {
          //await createLogAsync({
          //	date: Date.now(),
          //	type: `combat`,
          //	message: `${agentId}'s turn to act.`,
          //})
          const { party, monstersAlive } = this.state

          const monsterIds = monstersAlive ? Object.keys(monstersAlive) : []
          const actingPartyMember = party[agentId]
          const currentTarget = targets[agentId]
          const canReach = monsterId => {
            const weaponReach = items.weapons[actingPartyMember.slot00].reach
            const monsterRow = monstersAlive[monsterId].currentRow
            return (
              (monsterRow === 0 && (weaponReach === 1 || weaponReach === 0)) ||
              (monsterRow === 1 && weaponReach === 1)
            )
          }

          let targetMonsterId

          if (actingPartyMember.defaultAction === `meleeClosest`) {
            if (currentTarget && currentTarget.length > 0) {
              if (monsterIds.includes(currentTarget)) {
                targetMonsterId = currentTarget
              } else {
                for (let monsterId of monsterIds) {
                  if (canReach(monsterId)) {
                    targetMonsterId = monsterId
                    await setTarget(x => ({
                      ...x,
                      [agentId]: monsterId,
                    }))
                    break
                  } else continue
                }
              }
            } else {
              for (let monsterId of monsterIds) {
                if (canReach(monsterId)) {
                  targetMonsterId = monsterId
                  await setTarget(x => ({
                    ...x,
                    [agentId]: monsterId,
                  }))
                  break
                } else continue
              }
            }
          }

          const targetMonster = monstersAlive[targetMonsterId]

          const weapon = items.weapons[actingPartyMember.slot00]

          const attackRoll =
            actingPartyMember.attackRating + Math.floor(20 * Math.random() + 1) + 100
          const defenseRoll = targetMonster.defenseRating + Math.floor(20 * Math.random() + 1)

          if (typeof targetMonster !== undefined) {
            if (attackRoll >= defenseRoll) {
              const damageRoll = randomIntegerInRange(weapon.minDamage, weapon.maxDamage)
              if (targetMonster.currentHP > damageRoll) {
                console.log(`monsterTakeDamage`)
                EventBus.publish(`monsterTakeDamage`, { mid: `${encounterId}-${targetMonsterId}` })
              }
              await setMonsterParamSync({
                dungeon: coords.dungeon,
                encounterId,
                monsterId: targetMonsterId,
                paramName: `currentHp`,
                operation: `decrement`,
                paramValue: damageRoll,
              })
              await this.forceUpdate()
              await createLogAsync({
                date: Date.now(),
                type: `combat`,
                message: `${agentId} hit ${targetMonsterId} with ${
                  weapon.name
                }! for ${damageRoll} | ${agentId}'s attack roll: ${attackRoll} vs ${targetMonsterId}'s defense roll: ${defenseRoll} [round: ${round}]
              ${targetMonsterId}'s hp is now ${
                  this.state.monstersAlive[targetMonsterId].currentHp
                }`,
              })
            } else {
              await console.log(
                `${agentId} missed! ${agentId}'s attack roll: ${attackRoll} vs ${targetMonsterId}'s defense roll: ${defenseRoll} [round: ${round}]`
              )
            }
          } else {
            console.log(`all monsters are out of ${agentId}'s reach!'`)
          }

          if (
            this.state.monstersAlive[targetMonsterId].currentHp <= 0 ||
            isNaN(this.state.monstersAlive[targetMonsterId].currentHp)
          ) {
            EventBus.publish(`despawnMonster`, { mid: `${encounterId}-${targetMonsterId}` })
            await setTarget(x => ({ ...x, [agentId]: `` }))
            await setVariableEncounterParamsSync({
              ...this.state.encounterData,
              dungeon: coords.dungeon,
              id: encounterId,
              monstersDead: {
                ...this.state.encounteredMonstersDead,
                [targetMonsterId]: {
                  ...targetMonster,
                  currentHp: this.state.monstersAlive[targetMonsterId].currentHp,
                },
              },
              monsters: monsterIds
                .filter(monsterId => monsterId !== targetMonsterId)
                .reduce((acc, monsterId) => {
                  acc[monsterId] = this.state.monstersAlive[monsterId]
                  return acc
                }, {}),
            })
            await this.forceUpdate()
          } else {
            //
          }

          console.log(`${agentId}'s turn ends'`)
          console.log(`_____________________`)
        } else {
          if (typeof this.state.monstersAlive[agentId] !== `undefined`) {
            console.log(`${agentId}'s turn starts'`)
            let targetAllyId
            const allyIds = Object.keys(party)
            if (this.state.monstersAlive[agentId].defaultAction === `meleeClosest`) {
              if (targets[agentId] && targets[agentId].length > 0) {
                if (allyIds.includes(targets[agentId])) {
                  console.log(`0`)
                  targetAllyId = targets[agentId]
                } else {
                  console.log(`1`)
                  targetAllyId = allyIds[Math.floor(Math.random() * allyIds.length)]
                  await setTarget(x => ({
                    ...x,
                    [agentId]: targetAllyId,
                  }))
                }
              } else {
                if (this.state.monstersAlive[agentId].reach === 0) {
                  console.log(`2`)
                  const frontRowAllyIds = allyIds.filter(
                    allyId => this.state.party[allyId].currentRow === 0
                  )
                  targetAllyId = frontRowAllyIds[Math.floor(Math.random() * frontRowAllyIds.length)]
                  await setTarget(x => ({
                    ...x,
                    [agentId]: targetAllyId,
                  }))
                } else {
                  console.log(`3`)
                  targetAllyId = allyIds[Math.floor(Math.random() * allyIds.length)]
                  await setTarget(x => ({
                    ...x,
                    [agentId]: targetAllyId,
                  }))
                }
                // for (let allyId of allyIds) {
                // 	if (
                // 		(this.state.party[allyId].currentRow === 0 &&
                // 			(this.state.monstersAlive[agentId].reach === 1 || this.state.monstersAlive[agentId].reach === 0)) ||
                // 		(this.state.party[allyId].currentRow === 0 && this.state.monstersAlive[agentId].reach === 1)
                // 	) {
                // 		targetAllyId = allyId
                // 		await setTarget((x) => ({
                // 			...x,
                // 			[agentId]: allyId,
                // 		}))
                // 		break
                // 	} else continue
                // }
              }
            }

            const attackRoll =
              this.state.monstersAlive[agentId].attackRating +
              Math.floor(20 * Math.random() + 1) +
              100
            const defenseRoll =
              this.state.party[targetAllyId].attackRating + Math.floor(20 * Math.random() + 1)
            EventBus.publish(`monsterAttacksInMelee`, { mid: `${encounterId}-${agentId}` })
            if (attackRoll >= defenseRoll) {
              const damageRoll = randomIntegerInRange(
                this.state.monstersAlive[agentId].minDamage,
                this.state.monstersAlive[agentId].maxDamage
              )

              await setPartyMemberParamSync({
                partyMemberId: targetAllyId,
                paramName: `currentHp`,
                operation: `decrement`,
                paramValue: damageRoll,
              })
              await console.log(
                `${agentId} hit ${targetAllyId} for ${damageRoll} | ${agentId}'s attack roll: ${attackRoll} vs ${targetAllyId}'s defense roll: ${defenseRoll} [round: ${round}] | 
                ${targetAllyId}'s hp is now ${this.state.party[targetAllyId].currentHp}`
              )
            } else {
              await console.log(
                `${agentId} missed! ${agentId}'s attack roll: ${attackRoll} vs ${targetAllyId}'s defense roll: ${defenseRoll} [round: ${round}] | 
                ${targetAllyId}'s hp is now ${this.state.party[targetAllyId].currentHp}`
              )
            }

            console.log(`${agentId}'s turn ends'`)
            console.log(`_____________________`)
          }
        }
        console.log(this.state.monstersAlive)
        if (
          Object.values(this.state.monstersAlive).filter(value => value.currentRow === 0).length ===
          0
        ) {
          if (
            Object.values(this.state.monstersAlive).filter(value => value.currentRow === 1).length >
            0
          ) {
            await setVariableEncounterParamsSync({
              ...this.state.encounterData,
              dungeon: coords.dungeon,
              id: encounterId,
              monsters: Object.keys(this.state.monstersAlive).reduce((acc, key, i) => {
                acc[key] = { ...Object.values(this.state.monstersAlive)[i], currentRow: 0 }
                return acc
              }, {}),
            })
            await this.forceUpdate()
            console.log(`no one left at the front! the back row now stands on the front lines`)
          }
        }

        if (this.state.monstersAlive && Object.keys(this.state.monstersAlive).length === 0) {
          await console.log(`combat ends with all monsters dead`)
          await setTarget({})
          await setVariableEncounterParamsSync({
            ...this.state.encounterData,
            dungeon: coords.dungeon,
            id: encounterId,
            vanquishedDate: Date.now(),
          })
          await setCombatAsync({ isOn: false, round: null, type: null, initiative: {} })
          break
        }
      }
    }
    if (e.keyCode === 66) {
      startTestRound
    }

    if (this.props.combatContext.combatRoundIsOn === false) {
      if (e.keyCode === 65 || e.keyCode === 38) {
        await this.props.combatContext.setCombatRoundIsOnTo(true)
        await startRound({ onTheMove: e.keyCode === 38 ? true : false })
        //await database.partyAgent.set({ party: this.state.party })
        //await database.encounters.setVariableParams({
        //	dungeon: coords.dungeon,
        //	id: encounterId,
        //	...this.state.encounterData,
        //})
        await this.props.combatContext.setCombatRoundIsOnTo(false)
      }
    }
  }
  componentDidMount() {
    console.log(`mounted combatcontrols`)
    document.addEventListener(`keydown`, this.listener)
    const { setTarget } = this.props
    setTarget({})
  }
  componentWillUnmount() {
    console.log(`unmounting combatcontrols`)
    document.removeEventListener(`keydown`, this.listener)
    const { setTarget } = this.props
    setTarget({})
  }
  render() {
    return (
      <div>{this.props.combatContext.combatRoundIsOn ? `combat round ON` : `combat round OFF`}</div>
    )
  }
}

CombatControls.propTypes = {}

const mapStateToProps = ({ items, encounters, combat, partyAgent }) => ({
  items,
  encounters,
  combat,
  partyAgent,
})

const enhance = compose(
  connect(
    mapStateToProps,
    {
      setVariableEncounterParamsSync,
      setMonsterParamSync,
      setCombatAsync,
      setPartyMemberParamSync,
      createLogAsync,
    }
  )
)
//  withState(`target`, `setTarget`, {})
//  onlyUpdateForKeys([ `this.state.monstersAlive`, `combat`, `partyAgent` ])
// connect(mapStateToProps, {})

export default enhance(CombatControls)
