contract BethingWorldCup is Ownable, usingOraclize {
    using SafeMath for uint256;

    // ============ Events ============

    event BetPlaced(address indexed better, uint256 betAmount);

    event PayoutClaimed(address indexed better, uint256 payoutAmount);

    event RefundClaimed(address indexed better, uint256 refundAmount);

    event CommissionPaid(address indexed bookie, uint256 commissionAmount);

    event Donated(address indexed charity, uint256 donatedAmount);

    event WinningTeamQuerySent(string query);

    event WinningTeamDetermined(bytes32 id, string winningTeam, bytes proof);

    // ============ Constants ============

    // GiveDirectly charity address (https://givedirectly.org/give-now?crypto=eth)
    address public constant CHARITY = 0xc7464dbcA260A8faF033460622B23467Df5AEA42;    

    // Minimum bet amount
    uint256 public constant MIN_BET_AMOUNT = 0.01 ether;

    // Commission rate to be paid to the bookie
    uint256 public constant COMMISSION_RATE = 4; // 4%

    // Percentage to be donated to charity
    uint256 public constant DONATION_RATE = 1; // 1%

    // Time from which betting is disabled (5 mins before kick-off)
    uint256 public BETS_CLOSING_TIME = 1531666500; // 15 Jul 2018 - 14:55 UTC

    // Interval before we try to query Wolfram Alpha to determine the winner of the event
    uint256 public PAYOUT_DELAY_INTERVAL = 24 hours;

    // Time from which payouts are enabled provided the winning team has been determined
    uint256 public PAYOUT_TIME = BETS_CLOSING_TIME + PAYOUT_DELAY_INTERVAL;

    // Time from which refunds are disabled provided the winning team has NOT been determined
    uint256 public REFUND_TIME = BETS_CLOSING_TIME + 7 days; // 22 Jul 2018 - 14:55 UTC

    // Total number of teams
    uint256 public constant TOTAL_TEAMS = 2;

    // Teams participating in the competition
    string[TOTAL_TEAMS] public TEAMS = [
        "France",
        "Croatia"
    ];

    // ============ State Variables ============    

    // Total number of bets
    uint256 public totalBets;

    // Total amount of bets placed on the betting pool
    uint256 public totalBetAmount;

    // Total amount of bets placed on each team
    uint256[TOTAL_TEAMS] public teamTotalBetAmount;

    // Bet amounts for a given player
    mapping(address => uint256[TOTAL_TEAMS]) public betterBetAmounts;

    // Mapping to determine if a payout has already been claimed for a given address to prevent reentrancy
    mapping(address => bool) public payoutClaimed;

    // Mapping to determine if a refund has already been claimed for a given address to prevent reentrancy
    mapping(address => bool) public refundClaimed;

    // Indicates if refunds are enable which can happen if it is not possible to determine the winning team
    bool private refundsEnabled;

    // Winning team
    uint256 private winningTeam;

    // Winning team name
    string public winningTeamName;

    // Indicates if the winning team has been correctly determined
    bool private winningTeamDetermined;

    // Total payout amount for bets on the winning team
    uint256 public totalPayoutAmount;

    // ============ Modifiers ============

    /**
     * @dev Reverts if not in betting time range.
     */
    modifier whenNotClosed() {
        require(!hasClosed());
        _;
    }

    /**
     * @dev Reverts if it is not possible for the sender to refund the original bet.
     */
    modifier whenCanClaimRefund() {
        require(canClaimRefund(msg.sender));
        _;
    }

    /**
     * @dev Reverts if it is not possible for the sender to claim the payout.
     */
    modifier whenCanClaimPayout() {
        require(canClaimPayout(msg.sender));
        _;
    }

    /**
     * @dev Reverts if team is not valid.
     */
    modifier isValidTeam(uint256 team) {
        require(team <= TOTAL_TEAMS);
        _;
    }

    // ============ Constructor ============

    /**
     * @dev Constructor.
     */
    function BethingWorldCup() public payable {
        queryWinningTeam(PAYOUT_TIME - now);
    }

    /**
     * @dev fallback function.
     */
    function () external payable onlyOwner {}

    // ============ State-Changing Functions ============

    /**
     * @dev Places a bet for a given team.
     */
    function bet(uint256 team) external payable whenNotClosed isValidTeam(team) {
        address better = msg.sender;
        uint256 betAmount = msg.value;
        require(betAmount >= MIN_BET_AMOUNT);

        betterBetAmounts[better][team] = betterBetAmounts[better][team].add(betAmount);
        totalBetAmount = totalBetAmount.add(betAmount);
        teamTotalBetAmount[team] = teamTotalBetAmount[team].add(betAmount);
        totalBets++;

        BetPlaced(better, betAmount);
    }

    /**
     * @dev Places a bet for a given team.
     */
    function claimPayout() external whenCanClaimPayout {
        address better = msg.sender;
        uint256 betterWinningTeamBetAmount = betterBetAmounts[better][winningTeam];

        uint256 winningTeamTotalBetAmount = teamTotalBetAmount[winningTeam];
        uint256 betterPayoutAmount = betterWinningTeamBetAmount.mul(totalPayoutAmount).div(winningTeamTotalBetAmount);
        payoutClaimed[better] = true;
        better.transfer(betterPayoutAmount);

        PayoutClaimed(better, betterPayoutAmount);
    }

    /**
     * @dev Refunds total bet amount iff it was not possible to determine the winner.
     */
    function claimRefund() external whenCanClaimRefund {
        address better = msg.sender;
        uint256 refundAmount = 0;

        for (uint256 i = 0; i < TEAMS.length; i++) {
            refundAmount = refundAmount.add(betterBetAmounts[better][i]);
        }

        assert(refundAmount > 0);
        refundClaimed[better] = true;
        better.transfer(refundAmount);

        RefundClaimed(better, refundAmount);
    }

    /**
     * @dev Schedules a WolframAlpha query to Oraclize to determine the winner of the competition.
     */
    function queryWinningTeam(uint256 delay) private {
        oraclize_query(delay, "WolframAlpha", "FIFA World Cup 2018 Winner");
        WinningTeamQuerySent("FIFA World Cup 2018 Winner");
    }

    /**
     * @dev Callback from Oraclize with the name of the winning team.
     */
    function __callback(bytes32 id, string result, bytes proof) public {
        require(msg.sender == oraclize_cbAddress());
        require(!winningTeamDetermined);
        require(!refundsEnabled);

        // Determine the winning team based on the query result from Wolfram Alpha
        for (uint256 i = 0; i < TEAMS.length; i++) {
            if (keccak256(TEAMS[i]) == keccak256(result)) {
                winningTeamDetermined = true;
                winningTeamName = result;
                winningTeam = i;
                break;
            }
        }

        if (winningTeamDetermined) {
            // If a winning team is determined, calculate total payout amount to allow betters to claim their payout
            calculateTotalPayoutAmountAndCommission();
            WinningTeamDetermined(id, winningTeamName, proof);
        } else {
            if (now >= REFUND_TIME) {
                // If we are past the refund time, we allow betters to ask for a refund of their original bets
                refundsEnabled = true;
            } else {
                // Otherwise, reschedule another attempt to determine the winner for the next day
                queryWinningTeam(PAYOUT_DELAY_INTERVAL);
            }
        }
    }

    /**
     * @dev Allow owner to suicide the contract after a year.
     */
    function kill() public onlyOwner {
        require(now > (BETS_CLOSING_TIME + 1 years));
        selfdestruct(owner);
    }

    // ============ Public Constant Functions ============

    /**
     * @dev Checks whether the period in which the betting is open has already elapsed.
     * @return Whether betting period has elapsed
     */
    function hasClosed() public view returns (bool) {
        return now > BETS_CLOSING_TIME;
    }

    /**
     * @dev Checks whether the sender is eligible for claiming a refund.
     * @return Whether sender is eligible to claim a refund
     */
    function canClaimRefund(address sender) public view returns (bool) {
        return (
            refundsEnabled &&
            !refundClaimed[sender]
        );
    } 

    /**
     * @dev Checks whether the sender is eligible for claiming a payout.
     * @return Whether sender is eligible to claim a payout
     */
    function canClaimPayout(address sender) public view returns (bool) {
        return (
            winningTeamDetermined &&
            !payoutClaimed[sender] &&
            betterBetAmounts[sender][winningTeam] > 0
        );
    }

    // ============ Private Functions ============

    /*
     * @dev Calculates the total payout amount, commission, and donation amounts.
     */
    function calculateTotalPayoutAmountAndCommission() private {
        uint256 commissionAmount = totalBetAmount.mul(COMMISSION_RATE).div(100);
        uint256 donationAmount = totalBetAmount.mul(DONATION_RATE).div(100);

        totalPayoutAmount = totalBetAmount.sub(commissionAmount).sub(donationAmount);

        owner.transfer(commissionAmount);
        CommissionPaid(owner, commissionAmount);

        CHARITY.transfer(donationAmount);
        Donated(CHARITY, donationAmount);
    }
}
/**
 * @title BethingWorldCup
 * @author Bething.io - info@bething.io
 *
 * @dev A parimutuel betting pool for the FIFA World Cup 2018. Players can place multiple bets on one or more teams,
 * and the total pool amount is then shared amongst all winning bets. Outcome is determine via a Wolfram Alpha Oracle.
 */
function claimPayout() external whenCanClaimPayout {
    address better = msg.sender;
    uint256 betterWinningTeamBetAmount = betterBetAmounts[better][winningTeam];

    uint256 winningTeamTotalBetAmount = teamTotalBetAmount[winningTeam];
    uint256 betterPayoutAmount = betterWinningTeamBetAmount.mul(totalPayoutAmount).div(winningTeamTotalBetAmount);
    payoutClaimed[better] = true;
    better.transfer(betterPayoutAmount);

    PayoutClaimed(better, betterPayoutAmount);
}

/**
 * @dev Places a bet for a given team.
 */