// Contract: MegaMilionsGangstarApes
// Target function: function releaseAll() public onlyAdmin
}


pragma solidity ^0.8.0;



/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}


// OpenZeppelin Contracts v4.3.2 (finance/PaymentSplitter.sol)

pragma solidity ^0.8.0;




/**
 * @title PaymentSplitter
 * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware
 * that the Ether will be split in this way, since it is handled transparently by the contract.
 *
 * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each
 * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim
 * an amount proportional to the percentage of total shares they were assigned.
 *
 * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the
 * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release}
 * function.
 *
 * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and
 * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you
 * to run tests before sending real value to this contract.
 */
contract PaymentSplitter is Context {
    event PayeeAdded(address account, uint256 shares);
    event PaymentReleased(address to, uint256 amount);
    event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount);
    event PaymentReceived(address from, uint256 amount);
    event Warning(string message);

    uint256 private _totalShares = 50000;
    uint256 private _totalReleased;

    mapping(address => uint256) private _shares;
    mapping(address => uint256) private _released;
    address[] private _payees;

    mapping(IERC20 => uint256) private _erc20TotalReleased;
    mapping(IERC20 => mapping(address => uint256)) private _erc20Released;

    /**
     * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at
     * the matching position in the `shares` array.
     *
     * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no
     * duplicates in `payees`.
     */
    constructor(address[] memory payees, uint256[] memory shares_) payable {
        require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch");
        require(payees.length > 0, "PaymentSplitter: no payees");

        for (uint256 i = 0; i < payees.length; i++) {
            _addPayee(payees[i], shares_[i]);
        }
    }

    /**
     * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully
     * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the
     * reliability of the events, and not the actual splitting of Ether.
     *
     * To learn more about this see the Solidity documentation for
     * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback
     * functions].
     */
    receive() external payable virtual {
        emit PaymentReceived(_msgSender(), msg.value);
    }
    
    function addToReleased(uint256 amount) internal {
        _totalReleased += amount;
    }

    /**
     * @dev Getter for the total shares held by payees.
     */
    function totalShares() public view returns (uint256) {
        return _totalShares;
    }

    /**
     * @dev Getter for the total amount of Ether already released.
     */
    function totalReleased() public view returns (uint256) {
        return _totalReleased;
    }

    /**
     * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20
     * contract.
     */
    function totalReleased(IERC20 token) public view returns (uint256) {
        return _erc20TotalReleased[token];
    }

    /**
     * @dev Getter for the amount of shares held by an account.
     */
    function shares(address account) public view returns (uint256) {
        return _shares[account];
    }

    /**
     * @dev Getter for the amount of Ether already released to a payee.
     */
    function released(address account) public view returns (uint256) {
        return _released[account];
    }

    /**
     * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an
     * IERC20 contract.
     */
    function released(IERC20 token, address account) public view returns (uint256) {
        return _erc20Released[token][account];
    }

    /**
     * @dev Getter for the address of the payee number `index`.
     */
    function payee(uint256 index) public view returns (address) {
        return _payees[index];
    }

    /**
     * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
     * total shares and their previous withdrawals.
     */
    function release(address payable account) public virtual {
        require(_shares[account] > 0, "PaymentSplitter: account has no shares");

        uint256 totalReceived = address(this).balance + totalReleased();
        uint256 payment = _pendingPayment(account, totalReceived, released(account));

        if (payment != 0) {
            _released[account] += payment;
            _totalReleased += payment;

            Address.sendValue(account, payment);
            emit PaymentReleased(account, payment);
        } else {
            emit Warning("PaymentSplitter: account is not due payment");
        }
    }

    /**
     * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their
     * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20
     * contract.
     */
    function release(IERC20 token, address account) public virtual {
        require(_shares[account] > 0, "PaymentSplitter: account has no shares");

        uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
        uint256 payment = _pendingPayment(account, totalReceived, released(token, account));

        if (payment != 0) {
            _erc20Released[token][account] += payment;
            _erc20TotalReleased[token] += payment;

            SafeERC20.safeTransfer(token, account, payment);
            emit ERC20PaymentReleased(token, account, payment);
        } else {
            emit Warning("PaymentSplitter: account is not due payment");
        }
    }

    /**
     * @dev internal logic for computing the pending payment of an `account` given the token historical balances and
     * already released amounts.
     */
    function _pendingPayment(
        address account,
        uint256 totalReceived,
        uint256 alreadyReleased
    ) private view returns (uint256) {
        return (totalReceived * _shares[account]) / _totalShares - alreadyReleased;
    }

    /**
     * @dev Add a new payee to the contract.
     * @param account The address of the payee to add.
     * @param shares_ The number of shares owned by the payee.
     */
    function _addPayee(address account, uint256 shares_) private {
        require(account != address(0), "PaymentSplitter: account is the zero address");
        require(shares_ > 0, "PaymentSplitter: shares are 0");
        require(_shares[account] == 0, "PaymentSplitter: account already has shares");

        _payees.push(account);
        _shares[account] = shares_;
        _totalShares = _totalShares + shares_;
        emit PayeeAdded(account, shares_);
    }
}

pragma solidity ^0.8.7;


contract MegaMilionsGangstarApes is ERC721, ERC721Enumerable, ERC721URIStorage, ERC2981ContractWideRoyalties, Ownable, PaymentSplitter {
    using Counters for Counters.Counter;
    using Strings for uint;
	using Math for uint;
	using SafeMath for uint;
    
    enum PriceType{ PROMO, FULL }   //{ 0, 1 }
    struct Promo {
        uint startTime;
        uint duration;
    }
    
    // Mappings
    mapping(address => bool) public whitelisted;
    mapping(address => uint) public promos;
    mapping(address => bool) public admins;
    
    // Default values
    uint internal constant MILLIETHER = 1e15; //0.001 ETHER = 1,000,000,000,000,000 WEI
    uint internal promoPrice = 0.1 ether;
    uint internal fullPrice = 0.3 ether;
    uint internal maxSupply = 10000;
    uint internal maxMint = 10;
    uint internal maxDiscountedMintPerUser = 2;
    
    // Vars
    string private baseURI;
    bool private paused = false;
    Promo private promo;
    
    // Wallet shares - 50% of revenues shared to team
	address private DP = 0xcd70Dc892F240D50b7D46CE120dc75c3243E72ab;
	address private RG = 0x39c34674EEC3AE5F1E9a6Ee899CB85f11a55D990;
	address private JH = 0xD484E2745d27FA62E6ab0396fd46e0A3E3A00eFc;
	address private LH = 0xB2CEe0977Bbb0446B358E0Cb3de69e10EdC5eDA3;
	address private EA = 0x3a4FF805cD9C063f06a0263EEa99F531FAa13B91;
	address private AG = 0x79f6E63B4D593F3794019A4FEFB52196C602E037;
	address private JP = 0xd7EE8aF7a902bb208878E50863220e2872b73992;
	address private MR = 0xaFBC38a36Cb173d75735f3f781e9133939e99888;
	address private AF = 0x7a2DD031a02Fd595C7BCb0A278A4518daB31AF8d;
	address private MA = 0xEe3642cb451F6f4ec5C6dd6699fFE69F2D07921E;
	
    uint[] private _shares = [
        675,
        675,
        11250,
        6750,
        9865,
        6750,
        7865,
        270,
        675,
        5225
    ];
    address[] private _team = [
        DP,
        RG,
        JH,
        LH,
        EA,
        AG,
        JP,
        MR,
        AF,
        MA
    ];
    
    // Game
    struct RoadmapStep { 
        uint64 supply;
        uint64 winnersCount;
        uint64 prizeValue;
        uint64 winnersToDraw;
    }
    uint private drawIndex = 0;
    RoadmapStep[9] private roadmap;
    mapping(uint => bool) internal isPrizeGiven;
    
    // Modifiers
    modifier onlyAdmin() {
        require(address(this) == _msgSender() || owner() == _msgSender() || admins[msg.sender], "Not admin");
        _;
    }
    
    // Emitters
    event BaseURIChanged(string tokenBaseURI);
    event TicketPriceChanged(PriceType priceType, uint price);
    event MaxMintAmountChanged(uint amount);
    event MaxDiscountedMintPerUser(uint mintPerUser);
    event Paused(bool paused);
    event PromoChanged(uint startTime, uint duration);
    event Airdropped(address to, uint tokenId);
    event Winner(uint token, address winner, uint value);
    event Log(string error);

    constructor()
        ERC721("Mega Millions Gangstar Apes", "MMGA")
        PaymentSplitter(_team, _shares)
    {
        //set default admins
        admins[DP] = true;
        admins[RG] = true;
        
        initRoadmap();
        
        //if ERC2981 supported
        _setRoyalties(address(this), 1000);  //10%
    }
    
    // ***************************
    //  Overrides
    // ***************************
    function _beforeTokenTransfer(address from, address to, uint tokenId)
        internal
        override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable, ERC2981Base)
        returns (bool) {
        return super.supportsInterface(interfaceId);
    }
    
    function _burn(uint _tokenId) 
        internal
        override(ERC721, ERC721URIStorage) {
        super._burn(_tokenId);
    }
    
    function burn(uint _tokenId) external onlyAdmin {
        _burn(_tokenId);
        maxSupply = maxSupply - 1;
    }

    // ***************************
    //  Admins management by owner
    // ***************************
    function enableAdmin(address addr) external onlyOwner {
        admins[addr] = true;
    }

    function disableAdmin(address addr) external onlyOwner {
        require(addr != owner(), "Can't disable owner");
        admins[addr] = false;
    }