contract CryptoWarriorPVE is WarriorTokenImpl {
    uint256 internal constant MINER_PERK = 1;
    uint256 internal constant SUMMONING_SICKENESS = 12;
    
    uint256 internal constant PVE_COOLDOWN = 1 hours;
    uint256 internal constant PVE_DURATION = 15 minutes;
    
    
    /// @notice The payment required to use startPVEBattle().
    uint256 public pveBattleFee = 10 finney;
    uint256 public constant PVE_COMPENSATION = 2 finney;
    
	/// @dev The address of the sibling contract that is used to implement warrior generation algorithm.
    SanctuaryInterface public sanctuary;

    /** @dev PVEStarted event. Emitted every time a warrior enters pve battle
     *  @param owner Warrior owner
     *  @param dungeonIndex Started dungeon index 
     *  @param warriorId Warrior ID that started PVE dungeon
     *  @param battleEndBlock Block number, when started PVE dungeon will be completed
     */
    event PVEStarted(address owner, uint256 dungeonIndex, uint256 warriorId, uint256 battleEndBlock);

    /** @dev PVEFinished event. Emitted every time a warrior finishes pve battle
     *  @param owner Warrior owner
     *  @param dungeonIndex Finished dungeon index
     *  @param warriorId Warrior ID that completed dungeon
     *  @param cooldownEndBlock Block number, when cooldown on PVE battle entrance will be over
     *  @param rewardId Warrior ID which was granted to the owner as battle reward
     */
    event PVEFinished(address owner, uint256 dungeonIndex, uint256 warriorId, uint256 cooldownEndBlock, uint256 rewardId);

	/// @dev Update the address of the sanctuary contract, can only be called by the Admin.
    /// @param _address An address of a sanctuary contract instance to be used from this point forward.
    function setSanctuaryAddress(address _address) external onlyAdmin {
        SanctuaryInterface candidateContract = SanctuaryInterface(_address);

        // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117
        require(candidateContract.isSanctuary());

        // Set the new contract address
        sanctuary = candidateContract;
    }
    
    function areUnique(uint256[] memory _warriorIds) internal pure returns(bool) {
   	    uint256 length = _warriorIds.length;
   	    uint256 j;
        for(uint256 i = 0; i < length; i++) {
	        for(j = i + 1; j < length; j++) {
	            if (_warriorIds[i] == _warriorIds[j]) return false;
	        }
        }
        return true; 
   	}

    /// @dev Updates the minimum payment required for calling startPVE(). Can only
    ///  be called by the COO address.
    function setPVEBattleFee(uint256 _pveBattleFee) external onlyAdmin {
        require(_pveBattleFee > PVE_COMPENSATION);
        pveBattleFee = _pveBattleFee;
    }
    
    /** @dev Returns PVE cooldown, after each battle, the warrior receives a 
     *  cooldown on the next entrance to the battle, cooldown depends on current warrior level,
     *  which is multiplied by 1h. Special case: after receiving 25 lv, the cooldwon will be 14 days.
     *  @param _levelPoints warrior level */
    function getPVECooldown(uint256 _levelPoints) public pure returns (uint256) {
        uint256 level = CryptoUtils._getLevel(_levelPoints);
        if (level >= MAX_LEVEL) return (14 * 24 * PVE_COOLDOWN);//14 days
        return (PVE_COOLDOWN * level);
    }

    /** @dev Returns PVE duration, each battle have a duration, which depends on current warrior level,
     *  which is multiplied by 15 min. At the end of the duration, warrior is becoming eligible to receive
     *  battle reward (new warrior in shiny armor)
     *  @param _levelPoints warrior level points 
     */
    function getPVEDuration(uint256 _levelPoints) public pure returns (uint256) {
        return CryptoUtils._getLevel(_levelPoints) * PVE_DURATION;
    }
    
    /// @dev Checks that a given warrior can participate in PVE battle. Requires that the
    ///  current cooldown is finished and also checks that warrior is idle (does not participate in any action)
    ///  and dungeon level requirement is satisfied
    function _isReadyToPVE(DataTypes.Warrior _warrior) internal view returns (bool) {
        return (_warrior.action == IDLE) && //is idle
        (_warrior.cooldownEndBlock <= uint64(block.number)) && //no cooldown
        (_warrior.level >= dungeonRequirements[_warrior.dungeonIndex]);//dungeon level requirement is satisfied
    }
    
    /// @dev Internal utility function to initiate pve battle, assumes that all battle
    ///  requirements have been checked.
    function _triggerPVEStart(uint256 _warriorId) internal {
        // Grab a reference to the warrior from storage.
        DataTypes.Warrior storage warrior = warriors[_warriorId];
        // Set warrior current action to pve battle
        warrior.action = uint16(PVE_BATTLE);
        // Set battle duration
        warrior.cooldownEndBlock = uint64((getPVEDuration(warrior.level) / secondsPerBlock) + block.number);
        // Emit the pve battle start event.
        PVEStarted(msg.sender, warrior.dungeonIndex, _warriorId, warrior.cooldownEndBlock);
    }
    
    /// @dev Starts PVE battle for specified warrior, 
    /// after battle, warrior owner will receive reward (Warrior) 
    /// @param _warriorId A Warrior ready to PVE battle.
    function startPVE(uint256 _warriorId) external payable whenNotPaused {
		// Checks for payment.
        require(msg.value >= pveBattleFee);
		
		// Caller must own the warrior.
        require(_ownerApproved(msg.sender, _warriorId));

        // Grab a reference to the warrior in storage.
        DataTypes.Warrior storage warrior = warriors[_warriorId];

        // Check that the warrior exists.
        require(warrior.identity != 0);

        // Check that the warrior is ready to battle
        require(_isReadyToPVE(warrior));
        
        // All checks passed, let the battle begin!
        _triggerPVEStart(_warriorId);
        
        // Calculate any excess funds included in msg.value. If the excess
        // is anything worth worrying about, transfer it back to message owner.
        // NOTE: We checked above that the msg.value is greater than or
        // equal to the price so this cannot underflow.
        uint256 feeExcess = msg.value - pveBattleFee;

        // Return the funds. This is not susceptible 
        // to a re-entry attack because of _isReadyToPVE check
        // will fail
        msg.sender.transfer(feeExcess);
        //send battle fee to beneficiary
        bankAddress.transfer(pveBattleFee - PVE_COMPENSATION);
    }
    
    function _ariseWarrior(address _owner, DataTypes.Warrior storage _warrior) internal returns(uint256) {
        uint256 identity = sanctuary.generateWarrior(_warrior.identity, CryptoUtils._getLevel(_warrior.level), _warrior.cooldownEndBlock - 1, 0);
        return _createWarrior(identity, _owner, block.number + (PVE_COOLDOWN * SUMMONING_SICKENESS / secondsPerBlock), 10, 100, 0);
    }

	/// @dev Internal utility function to finish pve battle, assumes that all battle
    ///  finish requirements have been checked.
    function _triggerPVEFinish(uint256 _warriorId) internal {
        // Grab a reference to the warrior in storage.
        DataTypes.Warrior storage warrior = warriors[_warriorId];
        
        // Set warrior current action to idle
        warrior.action = uint16(IDLE);
        
        // Compute an estimation of the cooldown time in blocks (based on current level).
        // and miner perc also reduces cooldown time by 4 times
        warrior.cooldownEndBlock = uint64((getPVECooldown(warrior.level) / 
            CryptoUtils._getBonus(warrior.identity) / secondsPerBlock) + block.number);
        
        // cash completed dungeon index before increment
        uint256 dungeonIndex = warrior.dungeonIndex;
        // Increment the dungeon index, clamping it at 5, which is the length of the
        // dungeonRequirements array. We could check the array size dynamically, but hard-coding
        // this as a constant saves gas.
        if (dungeonIndex < 5) {
            warrior.dungeonIndex += 1;
        }
        
        address owner = warriorToOwner[_warriorId];
        // generate reward
        uint256 arisenWarriorId = _ariseWarrior(owner, warrior);
        //Emit event
        PVEFinished(owner, dungeonIndex, _warriorId, warrior.cooldownEndBlock, arisenWarriorId);
    }
    
    /**
     * @dev finishPVE can be called after battle time is over,
     * if checks are passed then battle result is computed,
     * and new warrior is awarded to owner of specified _warriord ID.
     * NB anyone can call this method, if they willing to pay the gas price
     */
    function finishPVE(uint256 _warriorId) external whenNotPaused {
        // Grab a reference to the warrior in storage.
        DataTypes.Warrior storage warrior = warriors[_warriorId];
        
        // Check that the warrior exists.
        require(warrior.identity != 0);
        
        // Check that warrior participated in PVE battle action
        require(warrior.action == PVE_BATTLE);
        
        // And the battle time is over
        require(warrior.cooldownEndBlock <= uint64(block.number));
        
        // When the all checks done, calculate actual battle result
        _triggerPVEFinish(_warriorId);
        
        //not susceptible to reetrance attack because of require(warrior.action == PVE_BATTLE)
        //and require(warrior.cooldownEndBlock <= uint64(block.number));
        msg.sender.transfer(PVE_COMPENSATION);
    }
    
    /**
     * @dev finishPVEBatch same as finishPVE but for multiple warrior ids.
     * NB anyone can call this method, if they willing to pay the gas price
     */
    function finishPVEBatch(uint256[] _warriorIds) external whenNotPaused {
        uint256 length = _warriorIds.length;
        //check max number of bach finish pve
        require(length <= 20);
        uint256 blockNumber = block.number;
        uint256 index;
        //all warrior ids must be unique
        require(areUnique(_warriorIds));
        //check prerequisites
        for(index = 0; index < length; index ++) {
            DataTypes.Warrior storage warrior = warriors[_warriorIds[index]];
			require(
		        // Check that the warrior exists.
			    warrior.identity != 0 &&
		        // Check that warrior participated in PVE battle action
			    warrior.action == PVE_BATTLE &&
		        // And the battle time is over
			    warrior.cooldownEndBlock <= blockNumber
			);
        }
        // When the all checks done, calculate actual battle result
        for(index = 0; index < length; index ++) {
            _triggerPVEFinish(_warriorIds[index]);
        }
        
        //not susceptible to reetrance attack because of require(warrior.action == PVE_BATTLE)
        //and require(warrior.cooldownEndBlock <= uint64(block.number));
        msg.sender.transfer(PVE_COMPENSATION * length);
    }
}
function finishPVEBatch(uint256[] _warriorIds) external whenNotPaused {
    uint256 length = _warriorIds.length;
    //check max number of bach finish pve
    require(length <= 20);
    uint256 blockNumber = block.number;
    uint256 index;
    //all warrior ids must be unique
    require(areUnique(_warriorIds));
    //check prerequisites
    for(index = 0; index < length; index ++) {
        DataTypes.Warrior storage warrior = warriors[_warriorIds[index]];
equire(
      // Check that the warrior exists.
   warrior.identity != 0 &&
      // Check that warrior participated in PVE battle action
   warrior.action == PVE_BATTLE &&
      // And the battle time is over
   warrior.cooldownEndBlock <= blockNumber
;
    }
    // When the all checks done, calculate actual battle result
    for(index = 0; index < length; index ++) {
        _triggerPVEFinish(_warriorIds[index]);
    }
        
    //not susceptible to reetrance attack because of require(warrior.action == PVE_BATTLE)
    //and require(warrior.cooldownEndBlock <= uint64(block.number));
    msg.sender.transfer(PVE_COMPENSATION * length);
}

/**
 * @dev finishPVEBatch same as finishPVE but for multiple warrior ids.
 * NB anyone can call this method, if they willing to pay the gas price
 */