contract DungeonChallenge is DungeonBase {

    /**
     * @dev The DungeonChallenged event is fired when user finished a dungeon challenge.
     */
    event DungeonChallenged(uint timestamp, address indexed playerAddress, uint indexed dungeonId, uint heroGenes, uint floorNumber, uint floorGenes, bool success, uint newFloorGenes, uint successRewards, uint masterRewards);

    /**
     * @notice The actual fee contribution required to call challenge() is calculated by this feeMultiplier,
     *  times the dungeon difficulty. The payment is accumulated to the dungeon rewards,
     *  and a large proportion will be claimed by whoever successfully challenged the floor.
     *  1 finney = 0.001 ether
     */
    uint256 public challengeFeeMultiplier = 1 finney;

    /**
     * @dev The percentage for which successful challenger be rewarded of the dungeons' accumulated rewards.
     *  The remaining rewards subtracted by developer fee will be used as the base rewards for new floor.
     */
    uint public challengeRewardsPercent = 64;

    /**
     * @dev The developer fee for owner
     *  Note that when Ether Dungeon becomes truly decentralised, contract ownership will be ejected,
     *  and the master rewards will be rewarded to the dungeon owner (Dungeon Masters).
     */
    uint public masterRewardsPercent = 8;

    /**
     * @dev The main public function to call when a player challenge a dungeon,
     *  it determines whether if the player successfully challenged the current floor.
     *  Will generate a DungeonChallenged event.
     */
    function challenge(uint _dungeonId) external payable whenNotPaused canChallenge(_dungeonId) {
        // Get the dungeon details from the token contract.
        uint difficulty;
        uint seedGenes;
        (,,difficulty,,,,seedGenes,) = dungeonTokenContract.dungeons(_dungeonId);

        // Checks for payment, any exceeding funds will be transferred back to the player.
        uint requiredFee = difficulty * challengeFeeMultiplier;
        require(msg.value >= requiredFee);

        // ** STORAGE UPDATE **
        // Increment the accumulated rewards for the dungeon.
        dungeonTokenContract.addDungeonRewards(_dungeonId, requiredFee);

        // Calculate any excess funds and make it available to be withdrawed by the player.
        asyncSend(msg.sender, msg.value - requiredFee);

        // Split the challenge function into multiple parts because of stack too deep error.
        _challengePart2(_dungeonId, requiredFee);
    }

    /**
     * Split the challenge function into multiple parts because of stack too deep error.
     */
    function _challengePart2(uint _dungeonId, uint _requiredFee) private {
        uint floorNumber;
        uint rewards;
        uint floorGenes;
        (,,,floorNumber,,rewards,,floorGenes) = dungeonTokenContract.dungeons(_dungeonId);

        // The original dungeon rewards plus the required fee of this callenge call.
        uint _addedRewards = rewards + uint128(_requiredFee);

        // Get the first hero gene, or initialize first hero with current dungeon's seed genes.
        // TODO: implement multiple heroes in next phase
        uint heroGenes = _getFirstHeroGenesAndInitialize(_dungeonId);

        bool success = _getChallengeSuccess(heroGenes, floorGenes);

        uint newFloorGenes;
        uint successRewards;
        uint masterRewards;

        // Whether a challenge is success or not is determined by a simple comparison between hero power and floor power.
        if (success) {
            newFloorGenes = _getNewFloorGene(_dungeonId);
            successRewards = _addedRewards * challengeRewardsPercent / 100;
            masterRewards = _addedRewards * masterRewardsPercent / 100;

            // The dungeon rewards for new floor as total rewards - challenge rewards - devleoper fee.
            uint newRewards = _addedRewards * (100 - challengeRewardsPercent - masterRewardsPercent) / 100;

            // ** STORAGE UPDATE **
            // Add new floor with the new floor genes and new rewards.
            dungeonTokenContract.addDungeonNewFloor(_dungeonId, newRewards, newFloorGenes);

            // Mark the challenge rewards available to be withdrawed by the player.
            asyncSend(msg.sender, _addedRewards * challengeRewardsPercent / 100);

            // Mark the master rewards available to be withdrawed by the dungeon master.
            asyncSend(owner, _addedRewards * masterRewardsPercent / 100);
        }

        // Emit the DungeonChallenged event.
        DungeonChallenged(now, msg.sender, _dungeonId, heroGenes, floorNumber, floorGenes, success, newFloorGenes, successRewards, masterRewards);
    }

    /**
     * Split the challenge function into multiple parts because of stack too deep error.
     */
    function _getFirstHeroGenesAndInitialize(uint _dungeonId) private returns (uint heroGenes) {
        uint seedGenes;
        (,,,,,,seedGenes,) = dungeonTokenContract.dungeons(_dungeonId);

        // Get the first hero of the player.
        uint heroId;

        if (heroTokenContract.balanceOf(msg.sender) == 0) {
            // Assign the first hero using the seed genes of the dungeon for new player.
            heroId = heroTokenContract.createHero(seedGenes, msg.sender);
        } else {
            heroId = heroTokenContract.ownerTokens(msg.sender, 0);
        }

        // Get the hero genes from token storage.
        (,heroGenes) = heroTokenContract.heroes(heroId);
    }

    /**
     * Split the challenge function into multiple parts because of stack too deep error.
     */
    function _getChallengeSuccess(uint heroGenes, uint floorGenes) private pure returns (bool) {
        // Determine if the player challenge successfuly the dungeon or not, and the new floor genes.
        uint heroPower = _getGenesPower(heroGenes);
        uint floorPower = _getGenesPower(floorGenes);

        return heroPower > floorPower;
    }

    /**
     * Split the challenge function into multiple parts because of stack too deep error.
     */
    function _getNewFloorGene(uint _dungeonId) private view returns (uint) {
        uint seedGenes;
        uint floorGenes;
        (,,,,,seedGenes,floorGenes) = dungeonTokenContract.dungeons(_dungeonId);

        // Calculate the new floor gene.
        uint floorPower = _getGenesPower(floorGenes);
        uint newFloorGenes = challengeScienceContract.mixGenes(floorGenes, seedGenes);
        uint newFloorPower = _getGenesPower(newFloorGenes);

        // If the power decreased, rollback to the current floor genes.
        if (newFloorPower < floorPower) {
            newFloorGenes = floorGenes;
        }

        return newFloorGenes;
    }


    /* ======== SETTER FUNCTIONS ======== */

    /**
     * @dev Updates the fee contribution multiplier required for calling challenge().
     */
    function setChallengeFeeMultiplier(uint _newChallengeFeeMultiplier) external onlyOwner {
        challengeFeeMultiplier = _newChallengeFeeMultiplier;
    }

    /**
     * @dev Updates the challenge rewards pecentage.
     */
    function setChallengeRewardsPercent(uint _newChallengeRewardsPercent) onlyOwner external {
        challengeRewardsPercent = _newChallengeRewardsPercent;
    }

    /**
     * @dev Updates the master rewards percentage.
     */
    function setMasterRewardsPercent(uint _newMasterRewardsPercent) onlyOwner external {
        masterRewardsPercent = _newMasterRewardsPercent;
    }

}
function _getNewFloorGene(uint _dungeonId) private view returns (uint) {
    uint seedGenes;
    uint floorGenes;
    (,,,,,seedGenes,floorGenes) = dungeonTokenContract.dungeons(_dungeonId);

    // Calculate the new floor gene.
    uint floorPower = _getGenesPower(floorGenes);
    uint newFloorGenes = challengeScienceContract.mixGenes(floorGenes, seedGenes);
    uint newFloorPower = _getGenesPower(newFloorGenes);

    // If the power decreased, rollback to the current floor genes.
    if (newFloorPower < floorPower) {
        newFloorGenes = floorGenes;
    }

    return newFloorGenes;
}

/**
 * Split the challenge function into multiple parts because of stack too deep error.
 */