// File: @openzeppelin/contracts/utils/math/SafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: contracts/BidsBot.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

struct BetTypeWin {
    uint betTypeId;
    bool choice;
}

struct Bet {
    address participant;
    uint256 amount;
    uint betTypeId;
    uint blockNum;
    bool choice;
}

contract BetBox{
    BettingContract[] public bets;
    address payable private owner;

    constructor() public {
        owner = payable(msg.sender);
    }

    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }

    function createBet (
        string memory _title,
        string memory _description,
        string[] calldata types
    ) public onlyOwner {
        BettingContract newBet = new BettingContract(payable(msg.sender), payable(msg.sender), _title, _description, types);
        bets.push(newBet);
    }

    function createBetForUsers (
        string memory _title,
        string memory _description,
        string[] calldata types
    ) public {
        BettingContract newBet = new BettingContract(owner, payable(msg.sender), _title, _description, types);
        bets.push(newBet);
    }

    function returnAllBets() public view returns(BettingContract[] memory){
        return bets;
    }
}

contract BettingContract {
    using SafeMath for uint256;

    address public owner;
    address public creator;
    string public title;
    string public description;

    uint numerator = 990000000000;
    uint denominator = 1000000000000;

    enum State { Default, Running, Stopped, Finalized }
    State public betState = State.Default;

    Bet[] public bets;
    string [] public betTypes;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    modifier bettingActive() {
        require(betState == State.Running);
        _;
    }

    constructor(
        address payable _owner,
        address payable _creator,
        string memory _title,
        string memory _description,
        string[] memory types
    ) {
        owner = _owner;
        creator = _creator;
        title = _title;
        description = _description;

        for (uint256 i = 0; i < types.length; i++) {
            betTypes.push(types[i]);
        }

        if (_owner == _creator) {
            betState = State.Running;
        }
    }

    function getBets() external view returns (Bet[] memory) {
        return bets;
    }

    function getBetTypes() external view returns (string[] memory) {
        return betTypes;
    }

    function placeBet(uint betTypeId, bool choice) external payable bettingActive {
        require(msg.value > 0);
        require(choice == true || choice == false);
        require(betTypeId < betTypes.length);

        Bet memory newBet = Bet({
            participant: msg.sender,
            amount: msg.value,
            choice: choice,
            betTypeId: betTypeId,
            blockNum: block.number
        });

        bets.push(newBet);
    }

    function stopBet() external onlyOwner {
        require(betState == State.Running, "Betting has already been stopped");
        betState = State.Stopped;
    }

    function startBet() external onlyOwner {
        require(betState == State.Default);
        betState = State.Running;
    }

    function distributeWinningPool(BetTypeWin[] calldata betsWin) external onlyOwner {
        require(betState == State.Stopped);

        for (uint256 b = 0; b < betsWin.length; b++) {
            uint256 totalPool = 0;
            uint256 winningPool = 0;
            for (uint256 i = 0; i < bets.length; i++) {
                if (bets[i].betTypeId == betsWin[b].betTypeId) {
                    totalPool += bets[i].amount;
                    if (bets[i].choice == betsWin[b].choice) {
                        winningPool += bets[i].amount;
                    }
                }
            }

            if (winningPool > 0) {
                for (uint256 i = 0; i < bets.length; i++) {
                    if (bets[i].amount > 0 && bets[i].choice == betsWin[b].choice && bets[i].betTypeId == betsWin[b].betTypeId) {
                        uint256 participantShare = bets[i].amount
                            .mul(numerator)
                            .mul(totalPool)
                            .div(winningPool)
                            .div(denominator);

                        (bool success, ) = payable(bets[i].participant).call{value: participantShare}("");
                        require(success, "Transfer failed");
                    }
                }
            }
        }
        if (owner != creator) {
            payable(creator).call{value:  address (this).balance.mul(400).div(1000) }("");
        }
        payable(owner).transfer(address (this).balance);
        betState = State.Finalized;
    }

    function refundBets(BetTypeWin[] calldata betsWin) external onlyOwner {
        for (uint256 b = 0; b < betsWin.length; b++) {
            for (uint256 i = 0; i < bets.length; i++) {
                if (bets[i].betTypeId == betsWin[b].betTypeId) {
                    (bool success, ) = payable(bets[i].participant).call{value: bets[i].amount}("");
                }
            }
        }
    }

    function refundLateBets(uint blockNum) external onlyOwner {
        for (uint256 i = 0; i < bets.length; i++) {
            if (bets[i].blockNum > blockNum) {
                (bool success, ) = payable(bets[i].participant).call{value: bets[i].amount}("");
                if (success) {
                    bets[i].amount = 0;
                }
            }
        }
    }

    function emergencyReturn(address payable owner) public onlyOwner {
        owner.transfer(address(this).balance);
    }
}