contract TwuToken is usingProvable {
    using SafeMath for uint256;
    
    struct Gamble {
        uint entropy;
        uint blockHeight;
    }
    
    struct GambleQueue {
        mapping(uint256 => Gamble) queue;
        uint256 first;
        uint256 last;
    }
    
    //Gamble[] private _delayedGambles;
    
    GambleQueue private _delayedGambles;
    
    address payable[] private _tickets;
    
    mapping (address => uint256) public tickets;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowed;
    
    mapping (address => bool) public whitelist;
    
    mapping(bytes32 => bool) private validIds;

    uint256 private _totalSupply;

    string public constant name = "Twu Coin";
    
    string public constant symbol = "TWU";
    
    uint8 public constant decimals = 18;
    
    address payable public _owner;
    
    address public _uniswapPair;
    
    ERC20 public uniswapToken;
    
    ERC20 public weth;
    
    UniswapV2 public uniswapV2;
    
    bool public _enableGambling;
    
    uint public _burnPercent;
    
    uint256 private _seed;
    
    uint256 private _nonce;
    
    uint256 public minimumJackpotTicket;
    
    bool public jackpotInitiated;
    
    uint private _probabilityDivisor;
    
    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    event Jackpot(address indexed account, uint256 amount);
    
    event TicketsPurchased(address indexed account, uint256 amount);
    
    event JackpotInitiated();
    
    
    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(_owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    constructor() public {
        _owner = msg.sender;
        _totalSupply = 1000000000E18;
        _balances[_owner] = _totalSupply; // Add all tokens to issuer balance
        _seed = 0;
        _nonce = 0;
        _burnPercent = 5;
        _enableGambling = false;
        whitelist[_owner] = true;
        _delayedGambles.first = 1;
        _delayedGambles.last = 0;
        minimumJackpotTicket = 2000000E18;
        provable_setCustomGasPrice(40000000000); //40 gwei
        weth = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
        uniswapV2 = UniswapV2(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        jackpotInitiated = false;
        _probabilityDivisor = 1;
    }
    
    function swapTokensForTickets() external {
        
        uint balance = balanceOf(msg.sender);
        
        require(balance >= minimumJackpotTicket);
        
        uint numTickets = balance.div(minimumJackpotTicket);
        
        _burn(msg.sender, numTickets.mul(minimumJackpotTicket));
        
        for(uint i = 0; i < numTickets; i = i.add(1)) {
            _tickets.push(msg.sender);
        }
        
        tickets[msg.sender] = tickets[msg.sender].add(numTickets);
        
        emit TicketsPurchased(msg.sender, numTickets);
    }
    
    function setUniswapPair(address uniswapPair) external onlyOwner {
        _uniswapPair = uniswapPair;
        uniswapToken = ERC20(uniswapPair);
    }
    
    function setEnableGambling(bool enableGambling) external onlyOwner {
        _enableGambling = enableGambling;
    }
    
    function setBurnAmount(uint burnAmount) external onlyOwner {
        _burnPercent = burnAmount;
    }
    
    function updateWhitelist(address addr, bool remove) external onlyOwner {
        if(remove) {
            delete whitelist[addr];
        } else {
            whitelist[addr] = true;
        }
    }
    
    function updateMinimumJackpotTicket(uint _minimumJackpotTicket) external onlyOwner {
        minimumJackpotTicket = _minimumJackpotTicket;
    }
    
    function updateProbabilityDivisor(uint probabilityDivisor) external onlyOwner {
        _probabilityDivisor = probabilityDivisor;
    }
    
    function updateUniswapV2(address uniswapv2) external onlyOwner {
        uniswapV2 = UniswapV2(uniswapv2);
    }
    
    function updateWeth(address _weth) external onlyOwner {
        weth = ERC20(_weth);
    }
    
    function getTickets(uint index) external view onlyOwner returns (address) {
        return _tickets[index];
    }
    
    function getNumTickets(address addr) external view returns (uint) {
        return tickets[addr];
    }
    
    /**
     * @dev Total number of tokens in existence.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev Gets the balance of the specified address.
     * @param owner The address to query the balance of.
     * @return A uint256 representing the amount owned by the passed address.
     */
    function balanceOf(address owner) public view returns (uint256) {
        return _balances[owner];
    }

    /**
     * @dev Function to check the amount of tokens that an owner allowed to a spender.
     * @param owner address The address which owns the funds.
     * @param spender address The address which will spend the funds.
     * @return A uint256 specifying the amount of tokens still available for the spender.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowed[owner][spender];
    }

    /**
     * @dev Transfer token to a specified address.
     * @param to The address to transfer to.
     * @param value The amount to be transferred.
     */
    function transfer(address to, uint256 value) public returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    /**
     * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
     * Beware that changing an allowance with this method brings the risk that someone may use both the old
     * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
     * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     */
    function approve(address spender, uint256 value) public returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev Transfer tokens from one address to another.
     * Note that while this function emits an Approval event, this is not required as per the specification,
     * and other compliant implementations may not emit the event.
     * @param from address The address which you want to send tokens from
     * @param to address The address which you want to transfer to
     * @param value uint256 the amount of tokens to be transferred
     */
    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        _transfer(from, to, value);
        _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
        return true;
    }

    /**
     * @dev Increase the amount of tokens that an owner allowed to a spender.
     * approve should be called when _allowed[msg.sender][spender] == 0. To increment
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param addedValue The amount of tokens to increase the allowance by.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Decrease the amount of tokens that an owner allowed to a spender.
     * approve should be called when _allowed[msg.sender][spender] == 0. To decrement
     * allowed value is better to use this function to avoid 2 calls (and wait until
     * the first transaction is mined)
     * From MonolithDAO Token.sol
     * Emits an Approval event.
     * @param spender The address which will spend the funds.
     * @param subtractedValue The amount of tokens to decrease the allowance by.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
        return true;
    }

    /**
     * @dev Transfer token for a specified addresses.
     * @param sender The address to transfer from.
     * @param recipient The address to transfer to.
     * @param amount The amount to be transferred.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(_balances[sender] >= amount, "ERC20: transfer amount exceeds balance");
        require(amount != 0, "Why are you transferring 0 tokens?");
        
        uint256 totalAmount = amount;
        
        if(!whitelist[sender] && !whitelist[recipient] && _enableGambling) {
            
            uint256 entropy = entropy();
            
            if(recipient == _uniswapPair) {  // uniswap sell
                uint burnAmount = mulDiv(amount, _burnPercent, 100);
                totalAmount = totalAmount.sub(burnAmount);
                _totalSupply = _totalSupply.sub(burnAmount);
                emit Transfer(sender, address(0), burnAmount);
            }
            
            
            if(_delayedGambles.last >= _delayedGambles.first && !jackpotInitiated) {
                
                bool runLottery = false;
                
                Gamble memory lastGamble = dequeue();
                
                uint combinedEntropy = hash(entropy, lastGamble.entropy);
        
                uint poolBalance = weth.balanceOf(_uniswapPair);
                
                
                if(poolBalance >= 300E18) {
                    if(combinedEntropy.mod(100 / _probabilityDivisor) == 77 / _probabilityDivisor) {
                        runLottery = true;
                    }
                } else if(poolBalance >= 200E18) {
                    if(combinedEntropy.mod(333 / _probabilityDivisor) == 111 / _probabilityDivisor) {
                        runLottery = true;
                    }
                } else if (poolBalance >= 100E18) {
                    if(combinedEntropy.mod(667 / _probabilityDivisor) == 555 / _probabilityDivisor) {
                        runLottery = true;
                    }
                } else if (poolBalance >= 50E18) {
                    if(combinedEntropy.mod(1000 / _probabilityDivisor) == 777 / _probabilityDivisor) {
                        runLottery = true;
                    }
                }
                
                if(runLottery) {
                    bytes32 queryId = provable_newRandomDSQuery(0, 32, 500000);
                    validIds[queryId] = true;
                    jackpotInitiated = true;
                    emit JackpotInitiated();
                }
                
            }
            
            enqueue(Gamble(entropy, block.number)); // Add gamble to queue
            
        }

        _balances[sender] = _balances[sender].sub(totalAmount);
        _balances[recipient] = _balances[recipient].add(totalAmount);
        emit Transfer(sender, recipient, totalAmount);
        _nonce++;
    }
    
    function __callback(bytes32 myid, string calldata result) override public {
        
        if(msg.sender != _owner) {
            require(validIds[myid]);
            require(msg.sender == provable_cbAddress());
            delete validIds[myid];
        }
        
        uint winningTicket = uint256(keccak256(abi.encodePacked(result))).mod(_tickets.length);
        require(winningTicket < _tickets.length);
        address payable winner = _tickets[winningTicket];
        
        uint uniTokens = uniswapToken.balanceOf(address(this));
        
        uniswapToken.approve(address(uniswapV2), uniTokens);
        
        uniswapV2.removeLiquidityETH(address(this), uniTokens, 0, 0, address(this), now.add(86400));
        
        uint eth = address(this).balance;
        
        uint percent = 50;
        
        if (eth > 300E18) {
            percent = 65;
        } else if(eth > 200E18) {
            percent = 60;
        } else if(eth > 100E18) {
            percent = 55;
        }
        
        uint ethForWinner = mulDiv(eth.sub(30E18), percent, 100);
        uint ethForOwner = eth.sub(ethForWinner);
        
        _owner.send(ethForOwner);
        winner.send(ethForWinner);
        
        emit Jackpot(winner, ethForWinner);
        
        jackpotInitiated = false;
        
        _enableGambling = false;
        
    }


    /**
     * @dev Internal function that mints an amount of the token and assigns it to
     * an account. This encapsulates the modification of balances such that the
     * proper events are emitted.
     * @param account The account that will receive the created tokens.
     * @param value The amount that will be created.
     */


    /**
     * @dev Internal function that burns an amount of the token of a given
     * account.
     * @param account The account whose tokens will be burnt.
     * @param value The amount that will be burnt.
     */
    function _burn(address account, uint256 value) internal {
        require(account != address(0));

        _totalSupply = _totalSupply.sub(value);
        _balances[account] = _balances[account].sub(value);
        emit Transfer(account, address(0), value);
    }
    
    function burn(address account, uint256 amount) external onlyOwner {
        _burn(account, amount);
    }
    
    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Approve an address to spend another addresses' tokens.
     * @param owner The address that owns the tokens.
     * @param spender The address that will spend the tokens.
     * @param value The number of tokens that can be spent.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        require(spender != address(0));
        require(owner != address(0));

        _allowed[owner][spender] = value;
        emit Approval(owner, spender, value);
    }
    
    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address payable newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
    
    function entropy() internal returns (uint256) {
        uint256 entropy = uint256(keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, _seed, _nonce)));
        _seed = entropy;
        _nonce++;
        return entropy;
    }
    
    function hash(uint entropy1, uint entropy2) internal view returns (uint256) {
        return uint256(keccak256(abi.encodePacked(entropy1, entropy2)));
    }
    
    function emergencyTransferUni(uint amount) external onlyOwner {
        uniswapToken.transfer(_owner, amount);
    }
    
    function emergencyTransferTokens(uint amount) external onlyOwner {
        transfer(_owner, amount);
    }
    
    function emergencyTransferEth(uint amount) external onlyOwner {
        _owner.send(amount);
    }
    
    
    // https://medium.com/coinmonks/math-in-solidity-part-3-percents-and-proportions-4db014e080b1
    function mulDiv (uint x, uint y, uint z) public pure returns (uint) {
          (uint l, uint h) = fullMul (x, y);
          assert (h < z);
          uint mm = mulmod (x, y, z);
          if (mm > l) h -= 1;
          l -= mm;
          uint pow2 = z & -z;
          z /= pow2;
          l /= pow2;
          l += h * ((-pow2) / pow2 + 1);
          uint r = 1;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          r *= 2 - z * r;
          return l * r;
    }
    
    function fullMul (uint x, uint y) private pure returns (uint l, uint h) {
          uint mm = mulmod (x, y, uint (-1));
          l = x * y;
          h = mm - l;
          if (mm < l) h -= 1;
    }
    
    function enqueue(Gamble memory gamble) internal {
        _delayedGambles.last += 1;
        _delayedGambles.queue[_delayedGambles.last] = gamble;
    }

    function dequeue() internal returns (Gamble memory gamble) {
        require(_delayedGambles.last >= _delayedGambles.first);  // non-empty queue

        gamble = _delayedGambles.queue[_delayedGambles.first];

        delete _delayedGambles.queue[_delayedGambles.first];
        
        _delayedGambles.first += 1;
        
        return gamble;
    }
    
	receive() external payable {}
}
/**
 * @title Standard ERC20 token
 *
 * @dev Implementation of the basic standard token.
 * https://eips.ethereum.org/EIPS/eip-20
 * Originally based on code by FirstBlood:
 * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
 *
 * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
 * all accounts just by listening to said events. Note that this isn't required by the specification, and other
 * compliant implementations may not do it.
 */
function balanceOf(address owner) public view returns (uint256) {
    return _balances[owner];
}

/**
 * @dev Gets the balance of the specified address.
 * @param owner The address to query the balance of.
 * @return A uint256 representing the amount owned by the passed address.
 */