contract DungeonTraining is DungeonChallenge {

    /// @dev The HeroTrained event is fired when user finished a training.
    event HeroTrained(uint timestamp, address indexed playerAddress, uint indexed dungeonId, uint heroGenes, uint floorNumber, uint floorGenes, bool success, uint newHeroGenes);

    /// @notice The actual fee contribution required to call trainX() is calculated by this feeMultiplier,
    ///  times the dungeon difficulty, times X. 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 trainingFeeMultiplier = 2 finney;

    /// @dev Updates the fee contribution multiplier required for calling trainX().
    function setTrainingFeeMultiplier(uint _newTrainingFeeMultiplier) external onlyOwner {
        trainingFeeMultiplier = _newTrainingFeeMultiplier;
    }

    /// @dev The public function to call when a hero train with a dungeon,
    ///  it determines whether whether a training is successfully, and the resulting genes.
    ///  Will generate a DungeonChallenged event.
    function train1(uint _dungeonId) external payable whenNotPaused canTrain(_dungeonId) {
        _train(_dungeonId, 1);
    }

    function train2(uint _dungeonId) external payable whenNotPaused canTrain(_dungeonId) {
        _train(_dungeonId, 2);
    }

    function train3(uint _dungeonId) external payable whenNotPaused canTrain(_dungeonId) {
        _train(_dungeonId, 3);
    }

    /// @dev An internal function of a hero train with dungeon,
    ///  it determines whether whether a training is successfully, and the resulting genes.
    ///  Will generate a DungeonChallenged event.
    function _train(uint _dungeonId, uint _trainingTimes) private {
        // Get the dungeon details from the token contract.
        uint difficulty;
        uint floorNumber;
        uint rewards;
        uint seedGenes;
        uint floorGenes;
        (,,difficulty,floorNumber,,rewards,seedGenes,floorGenes) = dungeonTokenContract.dungeons(_dungeonId);

        // Check for _trainingTimes abnormality, we probably won't have any feature that train a hero 10 times with a single call.
        require(_trainingTimes < 10);

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

        // Get the first hero of the player.
        // TODO: implement multiple heroes in next phase
        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);
        }

        // ** 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 _train function into multiple parts because of stack too deep error.
        _trainPart2(_dungeonId, _trainingTimes, heroId);
    }

    /**
     * Split the _train function into multiple parts because of stack too deep error.
     */
    function _trainPart2(uint _dungeonId, uint _trainingTimes, uint _heroId) private {
        // Get the dungeon details from the token contract.
        uint floorNumber;
        uint floorGenes;
        (,,,floorNumber,,,,floorGenes) = dungeonTokenContract.dungeons(_dungeonId);

        uint heroGenes;
        (,heroGenes) = heroTokenContract.heroes(_heroId);

        // Determine if the hero training is successful or not, and the resulting genes.
        uint heroPower = _getGenesPower(heroGenes);

        uint newHeroGenes = heroGenes;
        uint newHeroPower = heroPower;

        // Train the hero multiple times according to _trainingTimes,
        // each time if the resulting power is larger, update new hero power.
        for (uint i = 0; i < _trainingTimes; i++) {
            uint tmpHeroGenes = trainingScienceContract.mixGenes(newHeroGenes, floorGenes);
            uint tmpHeroPower = _getGenesPower(tmpHeroGenes);

            if (tmpHeroPower > newHeroPower) {
                newHeroGenes = tmpHeroGenes;
                newHeroPower = tmpHeroPower;
            }
        }

        // Prevent reduced power.
        bool success = newHeroPower > heroPower;

        if (success) {
            // ** STORAGE UPDATE **
            // Set the upgraded hero genes.
            heroTokenContract.setHeroGenes(_heroId, newHeroGenes);
        }

        // Emit the HeroTrained event.
        HeroTrained(now, msg.sender, _dungeonId, heroGenes, floorNumber, floorGenes, success, newHeroGenes);
    }

}
function setTrainingFeeMultiplier(uint _newTrainingFeeMultiplier) external onlyOwner {
    trainingFeeMultiplier = _newTrainingFeeMultiplier;
}

/// @dev Updates the fee contribution multiplier required for calling trainX().