contract BWService {
    using SafeMath for uint256;
    address private owner;
    address private bw;
    address private bwMarket;
    BWData private bwData;
    uint private seed = 42;
    uint private WITHDRAW_FEE = 5; // 5%
    uint private ATTACK_FEE = 5; // 5%
    uint private ATTACK_BOOST_CAP = 300; // 300%
    uint private DEFEND_BOOST_CAP = 300; // 300%
    uint private ATTACK_BOOST_MULTIPLIER = 100; // 100%
    uint private DEFEND_BOOST_MULTIPLIER = 100; // 100%
    mapping (uint16 => address) private localGames;
    
    modifier isOwner {
        if (msg.sender != owner) {
            revert();
        }
        _;
    }  

    modifier isValidCaller {
        if (msg.sender != bw && msg.sender != bwMarket) {
            revert();
        }
        _;
    }

    event TileClaimed(uint16 tileId, address newClaimer, uint priceInWei, uint creationTime);
    event TileFortified(uint16 tileId, address claimer, uint addedValueInWei, uint priceInWei, uint fortifyTime); // Sent when a user fortifies an existing claim by bumping its value.
    event TileAttackedSuccessfully(uint16 tileId, address attacker, uint attackAmount, uint totalAttackAmount, address defender, uint defendAmount, uint totalDefendAmount, uint attackRoll, uint attackTime); // Sent when a user successfully attacks a tile.    
    event TileDefendedSuccessfully(uint16 tileId, address attacker, uint attackAmount, uint totalAttackAmount, address defender, uint defendAmount, uint totalDefendAmount, uint attackRoll, uint defendTime); // Sent when a user successfully defends a tile when attacked.    
    event BlockValueMoved(uint16 sourceTileId, uint16 destTileId, address owner, uint movedBlockValue, uint postSourceValue, uint postDestValue, uint moveTime); // Sent when a user buys a tile from another user, by accepting a tile offer
    event UserBattleValueUpdated(address userAddress, uint battleValue, bool isWithdraw);

    // Constructor.
    constructor(address _bwData) public {
        bwData = BWData(_bwData);
        owner = msg.sender;
    }

    // Can't send funds straight to this contract. Avoid people sending by mistake.
    function () payable public {
        revert();
    }

    // OWNER-ONLY FUNCTIONS
    function kill() public isOwner {
        selfdestruct(owner);
    }

    function setValidBwCaller(address _bw) public isOwner {
        bw = _bw;
    }
    
    function setValidBwMarketCaller(address _bwMarket) public isOwner {
        bwMarket = _bwMarket;
    }

    function setWithdrawFee(uint _feePercentage) public isOwner {
        WITHDRAW_FEE = _feePercentage;
    }

    function setAttackFee(uint _feePercentage) public isOwner {
        ATTACK_FEE = _feePercentage;
    }

    function setAttackBoostMultipler(uint _multiplierPercentage) public isOwner {
        ATTACK_BOOST_MULTIPLIER = _multiplierPercentage;
    }

    function setDefendBoostMultiplier(uint _multiplierPercentage) public isOwner {
        DEFEND_BOOST_MULTIPLIER = _multiplierPercentage;
    }

    function setAttackBoostCap(uint _capPercentage) public isOwner {
        ATTACK_BOOST_CAP = _capPercentage;
    }

    function setDefendBoostCap(uint _capPercentage) public isOwner {
        DEFEND_BOOST_CAP = _capPercentage;
    }

    // TILE-RELATED FUNCTIONS
    // This function claims multiple previously unclaimed tiles in a single transaction.
    // The value assigned to each tile is the msg.value divided by the number of tiles claimed.
    // The msg.value is required to be an even multiple of the number of tiles claimed.
    function storeInitialClaim(address _msgSender, uint16[] _claimedTileIds, uint _claimAmount, bool _useBattleValue) public isValidCaller {
        uint tileCount = _claimedTileIds.length;
        require(tileCount > 0);
        require(_claimAmount >= 1 finney * tileCount); // ensure enough funds paid for all tiles
        require(_claimAmount % tileCount == 0); // ensure payment is an even multiple of number of tiles claimed

        uint valuePerBlockInWei = _claimAmount.div(tileCount); // Due to requires above this is guaranteed to be an even number
        require(valuePerBlockInWei >= 5 finney);

        if (_useBattleValue) {
            subUserBattleValue(_msgSender, _claimAmount, false);  
        }

        addGlobalBlockValueBalance(_claimAmount);

        uint16 tileId;
        bool isNewTile;
        for (uint16 i = 0; i < tileCount; i++) {
            tileId = _claimedTileIds[i];
            isNewTile = bwData.isNewTile(tileId); // Is length 0 if first time purchased
            require(isNewTile); // Can only claim previously unclaimed tiles.

            // Send claim event
            emit TileClaimed(tileId, _msgSender, valuePerBlockInWei, block.timestamp);

            // Update contract state with new tile ownership.
            bwData.storeClaim(tileId, _msgSender, valuePerBlockInWei);
        }
    }

    function fortifyClaims(address _msgSender, uint16[] _claimedTileIds, uint _fortifyAmount, bool _useBattleValue) public isValidCaller {
        uint tileCount = _claimedTileIds.length;
        require(tileCount > 0);

        address(this).balance.add(_fortifyAmount); // prevent overflow with SafeMath
        require(_fortifyAmount % tileCount == 0); // ensure payment is an even multiple of number of tiles fortified
        uint addedValuePerTileInWei = _fortifyAmount.div(tileCount); // Due to requires above this is guaranteed to be an even number
        require(_fortifyAmount >= 1 finney * tileCount); // ensure enough funds paid for all tiles

        address claimer;
        uint blockValue;
        for (uint16 i = 0; i < tileCount; i++) {
            (claimer, blockValue) = bwData.getTileClaimerAndBlockValue(_claimedTileIds[i]);
            require(claimer != 0); // Can't do this on never-owned tiles
            require(claimer == _msgSender); // Only current claimer can fortify claim

            if (_useBattleValue) {
                subUserBattleValue(_msgSender, addedValuePerTileInWei, false);
            }
            
            fortifyClaim(_msgSender, _claimedTileIds[i], addedValuePerTileInWei);
        }
    }

    function fortifyClaim(address _msgSender, uint16 _claimedTileId, uint _fortifyAmount) private {
        uint blockValue;
        uint sellPrice;
        (blockValue, sellPrice) = bwData.getCurrentBlockValueAndSellPriceForTile(_claimedTileId);
        uint updatedBlockValue = blockValue.add(_fortifyAmount);
        // Send fortify event
        emit TileFortified(_claimedTileId, _msgSender, _fortifyAmount, updatedBlockValue, block.timestamp);
        
        // Update tile value. The tile has been fortified by bumping up its value.
        bwData.updateTileBlockValue(_claimedTileId, updatedBlockValue);

        // Track addition to global block value
        addGlobalBlockValueBalance(_fortifyAmount);
    }

    // Return a pseudo random number between lower and upper bounds
    // given the number of previous blocks it should hash.
    // Random function copied from https://github.com/axiomzen/eth-random/blob/master/contracts/Random.sol.
    // Changed sha3 to keccak256, then modified.
    // Changed random range from uint64 to uint (=uint256).
    function random(uint _upper) private returns (uint)  {
        seed = uint(keccak256(blockhash(block.number - 1), block.coinbase, block.timestamp, seed, address(0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE).balance));
        return seed % _upper;
    }

    // A user tries to claim a tile that's already owned by another user. A battle ensues.
    // A random roll is done with % based on attacking vs defending amounts.
    function attackTile(address _msgSender, uint16 _tileId, uint _attackAmount, bool _useBattleValue) public isValidCaller {
        require(_attackAmount >= 1 finney);         // Don't allow attacking with less than one base tile price.
        require(_attackAmount % 1 finney == 0);

        address claimer;
        uint blockValue;
        (claimer, blockValue) = bwData.getTileClaimerAndBlockValue(_tileId);
        
        require(claimer != 0); // Can't do this on never-owned tiles
        require(claimer != _msgSender); // Can't attack one's own tiles
        require(claimer != owner); // Can't attack owner's tiles because it is used for raffle.

        // Calculate boosted amounts for attacker and defender
        // The base attack amount is sent in the by the user.
        // The base defend amount is the attacked tile's current blockValue.
        uint attackBoost;
        uint defendBoost;
        (attackBoost, defendBoost) = bwData.calculateBattleBoost(_tileId, _msgSender, claimer);

        // Adjust boost to optimize game strategy
        attackBoost = attackBoost.mul(ATTACK_BOOST_MULTIPLIER).div(100);
        defendBoost = defendBoost.mul(DEFEND_BOOST_MULTIPLIER).div(100);
        
        // Cap the boost to minimize its impact (prevents whales somehow)
        if (attackBoost > _attackAmount.mul(ATTACK_BOOST_CAP).div(100)) {
            attackBoost = _attackAmount.mul(ATTACK_BOOST_CAP).div(100);
        }
        if (defendBoost > blockValue.mul(DEFEND_BOOST_CAP).div(100)) {
            defendBoost = blockValue.mul(DEFEND_BOOST_CAP).div(100);
        }

        uint totalAttackAmount = _attackAmount.add(attackBoost);
        uint totalDefendAmount = blockValue.add(defendBoost);

        // Verify that attack odds are within allowed range.
        require(totalAttackAmount.div(10) <= totalDefendAmount); // Disallow attacks with more than 1000% of defendAmount
        require(totalAttackAmount >= totalDefendAmount.div(10)); // Disallow attacks with less than 10% of defendAmount

        uint attackFeeAmount = _attackAmount.mul(ATTACK_FEE).div(100);
        uint attackAmountAfterFee = _attackAmount.sub(attackFeeAmount);
        
        updateFeeBalance(attackFeeAmount);

        // The battle considers boosts.
        uint attackRoll = random(totalAttackAmount.add(totalDefendAmount)); // This is where the excitement happens!

        //gas cost of attack branch is higher than denfense branch solving MSB1
        if (attackRoll > totalDefendAmount) {
            // Change block owner but keep same block value (attacker got battlevalue instead)
            bwData.setClaimerForTile(_tileId, _msgSender);

            // Tile successfully attacked!
            if (_useBattleValue) {
                // Withdraw followed by deposit of same amount to prevent MSB1
                addUserBattleValue(_msgSender, attackAmountAfterFee); // Don't include boost here!
                subUserBattleValue(_msgSender, attackAmountAfterFee, false);
            } else {
                addUserBattleValue(_msgSender, attackAmountAfterFee); // Don't include boost here!
            }
            addUserBattleValue(claimer, 0);

            bwData.updateTileTimeStamp(_tileId);
            // Send update event
            emit TileAttackedSuccessfully(_tileId, _msgSender, attackAmountAfterFee, totalAttackAmount, claimer, blockValue, totalDefendAmount, attackRoll, block.timestamp);
        } else {
            bwData.setClaimerForTile(_tileId, claimer); //should be old owner
            // Tile successfully defended!
            if (_useBattleValue) {
                subUserBattleValue(_msgSender, attackAmountAfterFee, false); // Don't include boost here!
            }
            addUserBattleValue(claimer, attackAmountAfterFee); // Don't include boost here!
            
            // Send update event
            emit TileDefendedSuccessfully(_tileId, _msgSender, attackAmountAfterFee, totalAttackAmount, claimer, blockValue, totalDefendAmount, attackRoll, block.timestamp);
        }
    }

    function updateFeeBalance(uint attackFeeAmount) private {
        uint feeBalance = bwData.getFeeBalance();
        feeBalance = feeBalance.add(attackFeeAmount);
        bwData.setFeeBalance(feeBalance);
    }

    function moveBlockValue(address _msgSender, uint8 _xSource, uint8 _ySource, uint8 _xDest, uint8 _yDest, uint _moveAmount) public isValidCaller {
        uint16 sourceTileId = BWUtility.toTileId(_xSource, _ySource);
        uint16 destTileId = BWUtility.toTileId(_xDest, _yDest);

        address sourceTileClaimer;
        address destTileClaimer;
        uint sourceTileBlockValue;
        uint destTileBlockValue;
        (sourceTileClaimer, sourceTileBlockValue) = bwData.getTileClaimerAndBlockValue(sourceTileId);
        (destTileClaimer, destTileBlockValue) = bwData.getTileClaimerAndBlockValue(destTileId);

        uint newBlockValue = sourceTileBlockValue.sub(_moveAmount);
        // Must transfer the entire block value or leave at least 5
        require(newBlockValue == 0 || newBlockValue >= 5 finney);

        require(sourceTileClaimer == _msgSender);
        require(destTileClaimer == _msgSender);
        require(_moveAmount >= 1 finney); // Can't be less
        require(_moveAmount % 1 finney == 0); // Move amount must be in multiples of 1 finney
        // require(sourceTile.blockValue - _moveAmount >= BASE_TILE_PRICE_WEI); // Must always leave some at source
        
        require(BWUtility.isAdjacent(_xSource, _ySource, _xDest, _yDest));

        sourceTileBlockValue = sourceTileBlockValue.sub(_moveAmount);
        destTileBlockValue = destTileBlockValue.add(_moveAmount);

        // If ALL block value was moved away from the source tile, we lose our claim to it. It becomes ownerless.
        if (sourceTileBlockValue == 0) {
            bwData.deleteTile(sourceTileId);
        } else {
            bwData.updateTileBlockValue(sourceTileId, sourceTileBlockValue);
            bwData.deleteOffer(sourceTileId); // Offer invalid since block value has changed
        }

        bwData.updateTileBlockValue(destTileId, destTileBlockValue);
        bwData.deleteOffer(destTileId);   // Offer invalid since block value has changed
        emit BlockValueMoved(sourceTileId, destTileId, _msgSender, _moveAmount, sourceTileBlockValue, destTileBlockValue, block.timestamp);        
    }

    function verifyAmount(address _msgSender, uint _msgValue, uint _amount, bool _useBattleValue) view public isValidCaller {
        if (_useBattleValue) {
            require(_msgValue == 0);
            require(bwData.getUserBattleValue(_msgSender) >= _amount);
        } else {
            require(_amount == _msgValue);
        }
    }

    function setLocalGame(uint16 _tileId, address localGameAddress) public isOwner {
        localGames[_tileId] = localGameAddress;
    }

    function getLocalGame(uint16 _tileId) view public isValidCaller returns (address) {
        return localGames[_tileId];
    }

    // BATTLE VALUE FUNCTIONS
    function withdrawBattleValue(address msgSender, uint _battleValueInWei) public isValidCaller returns (uint) {
        //require(_battleValueInWei % 1 finney == 0); // Must be divisible by 1 finney
        uint fee = _battleValueInWei.mul(WITHDRAW_FEE).div(100); // Since we divide by 20 we can never create infinite fractions, so we'll always count in whole wei amounts.
        uint amountToWithdraw = _battleValueInWei.sub(fee);
        uint feeBalance = bwData.getFeeBalance();
        feeBalance = feeBalance.add(fee);
        bwData.setFeeBalance(feeBalance);
        subUserBattleValue(msgSender, _battleValueInWei, true);
        return amountToWithdraw;
    }

    function addUserBattleValue(address _userId, uint _amount) public isValidCaller {
        uint userBattleValue = bwData.getUserBattleValue(_userId);
        uint newBattleValue = userBattleValue.add(_amount);
        bwData.setUserBattleValue(_userId, newBattleValue); // Don't include boost here!
        emit UserBattleValueUpdated(_userId, newBattleValue, false);
    }
    
    function subUserBattleValue(address _userId, uint _amount, bool _isWithdraw) public isValidCaller {
        uint userBattleValue = bwData.getUserBattleValue(_userId);
        require(_amount <= userBattleValue); // Must be less than user's battle value - also implicitly checks that underflow isn't possible
        uint newBattleValue = userBattleValue.sub(_amount);
        bwData.setUserBattleValue(_userId, newBattleValue); // Don't include boost here!
        emit UserBattleValueUpdated(_userId, newBattleValue, _isWithdraw);
    }

    function addGlobalBlockValueBalance(uint _amount) public isValidCaller {
        // Track addition to global block value.
        uint blockValueBalance = bwData.getBlockValueBalance();
        bwData.setBlockValueBalance(blockValueBalance.add(_amount));
    }

    function subGlobalBlockValueBalance(uint _amount) public isValidCaller {
        // Track addition to global block value.
        uint blockValueBalance = bwData.getBlockValueBalance();
        bwData.setBlockValueBalance(blockValueBalance.sub(_amount));
    }

    // Allow us to transfer out airdropped tokens if we ever receive any
    function transferTokens(address _tokenAddress, address _recipient) public isOwner {
        ERC20I token = ERC20I(_tokenAddress);
        require(token.transfer(_recipient, token.balanceOf(this)));
    }
}
function attackTile(address _msgSender, uint16 _tileId, uint _attackAmount, bool _useBattleValue) public isValidCaller {
    require(_attackAmount >= 1 finney);         // Don't allow attacking with less than one base tile price.
    require(_attackAmount % 1 finney == 0);

    address claimer;
    uint blockValue;
    (claimer, blockValue) = bwData.getTileClaimerAndBlockValue(_tileId);
        
    require(claimer != 0); // Can't do this on never-owned tiles
    require(claimer != _msgSender); // Can't attack one's own tiles
    require(claimer != owner); // Can't attack owner's tiles because it is used for raffle.

    // Calculate boosted amounts for attacker and defender
    // The base attack amount is sent in the by the user.
    // The base defend amount is the attacked tile's current blockValue.
    uint attackBoost;
    uint defendBoost;
    (attackBoost, defendBoost) = bwData.calculateBattleBoost(_tileId, _msgSender, claimer);

    // Adjust boost to optimize game strategy
    attackBoost = attackBoost.mul(ATTACK_BOOST_MULTIPLIER).div(100);
    defendBoost = defendBoost.mul(DEFEND_BOOST_MULTIPLIER).div(100);
        
    // Cap the boost to minimize its impact (prevents whales somehow)
    if (attackBoost > _attackAmount.mul(ATTACK_BOOST_CAP).div(100)) {
        attackBoost = _attackAmount.mul(ATTACK_BOOST_CAP).div(100);
    }
    if (defendBoost > blockValue.mul(DEFEND_BOOST_CAP).div(100)) {
        defendBoost = blockValue.mul(DEFEND_BOOST_CAP).div(100);
    }

    uint totalAttackAmount = _attackAmount.add(attackBoost);
    uint totalDefendAmount = blockValue.add(defendBoost);

    // Verify that attack odds are within allowed range.
    require(totalAttackAmount.div(10) <= totalDefendAmount); // Disallow attacks with more than 1000% of defendAmount
    require(totalAttackAmount >= totalDefendAmount.div(10)); // Disallow attacks with less than 10% of defendAmount

    uint attackFeeAmount = _attackAmount.mul(ATTACK_FEE).div(100);
    uint attackAmountAfterFee = _attackAmount.sub(attackFeeAmount);
        
    updateFeeBalance(attackFeeAmount);

    // The battle considers boosts.
    uint attackRoll = random(totalAttackAmount.add(totalDefendAmount)); // This is where the excitement happens!

    //gas cost of attack branch is higher than denfense branch solving MSB1
    if (attackRoll > totalDefendAmount) {
        // Change block owner but keep same block value (attacker got battlevalue instead)
        bwData.setClaimerForTile(_tileId, _msgSender);

        // Tile successfully attacked!
        if (_useBattleValue) {
            // Withdraw followed by deposit of same amount to prevent MSB1
            addUserBattleValue(_msgSender, attackAmountAfterFee); // Don't include boost here!
            subUserBattleValue(_msgSender, attackAmountAfterFee, false);
        } else {
            addUserBattleValue(_msgSender, attackAmountAfterFee); // Don't include boost here!
        }
        addUserBattleValue(claimer, 0);

        bwData.updateTileTimeStamp(_tileId);
        // Send update event
        emit TileAttackedSuccessfully(_tileId, _msgSender, attackAmountAfterFee, totalAttackAmount, claimer, blockValue, totalDefendAmount, attackRoll, block.timestamp);
    } else {
        bwData.setClaimerForTile(_tileId, claimer); //should be old owner
        // Tile successfully defended!
        if (_useBattleValue) {
            subUserBattleValue(_msgSender, attackAmountAfterFee, false); // Don't include boost here!
        }
        addUserBattleValue(claimer, attackAmountAfterFee); // Don't include boost here!
            
        // Send update event
        emit TileDefendedSuccessfully(_tileId, _msgSender, attackAmountAfterFee, totalAttackAmount, claimer, blockValue, totalDefendAmount, attackRoll, block.timestamp);
    }
}

// A user tries to claim a tile that's already owned by another user. A battle ensues.
// A random roll is done with % based on attacking vs defending amounts.