contract BKeeper {
    address public masterCopy;
    ArbChecker immutable public arbChecker;
    Arb immutable public arb;
    uint maxEthQty; // = 1000 ether;
    uint minQty; // = 1e10;
    uint minProfitInBps; // = 100;

    address public admin;
    address[] public bamms;

    event KeepOperation(bool succ);

    constructor(Arb _arb, ArbChecker _arbChecker) public {
        arbChecker = ArbChecker(_arbChecker);
        arb = _arb;
    }

    function findSmallestQty() public returns(uint, address) {
        for(uint i = 0 ; i < bamms.length ; i++) {
            address bamm = bamms[i];
            for(uint qty = maxEthQty ; qty > minQty ; qty = qty / 2) {
                uint minProfit = qty * minProfitInBps / 10000;
                try arbChecker.checkProfitableArb(qty, minProfit, bamm) {
                    return (qty, bamm);
                } catch {

                }
            }
        }

        return (0, address(0));
    }

    function checkUpkeep(bytes calldata /*checkData*/) external returns (bool upkeepNeeded, bytes memory performData) {
        uint[] memory balances = new uint[](bamms.length);
        for(uint i = 0 ; i < bamms.length ; i++) {
            balances[i] = bamms[i].balance;
        }

        (uint qty, address bamm) = findSmallestQty();

        uint bammBalance;
        for(uint i = 0 ; i < bamms.length ; i++) {
            if(bamms[i] == bamm) bammBalance = balances[i];
        }

        upkeepNeeded = qty > 0;
        performData = abi.encode(qty, bamm, bammBalance);
    }
    
    function performUpkeep(bytes calldata performData) external {
        (uint qty, address bamm, uint bammBalance) = abi.decode(performData, (uint, address, uint));
        require(bammBalance == bamm.balance, "performUpkeep: front runned");
        require(qty > 0, "0 qty");
        arb.swap(qty, bamm);
        
        emit KeepOperation(true);        
    }

    function performUpkeepSafe(bytes calldata performData) external {
        try this.performUpkeep(performData) {
            emit KeepOperation(true);
        }
        catch {
            emit KeepOperation(false);
        }
    }    

    receive() external payable {}

    // admin stuff
    function transferAdmin(address newAdmin) external {
        require(msg.sender == admin, "!admin");
        admin = newAdmin;
    }

    function initParams(uint _maxEthQty, uint _minEthQty, uint _minProfit) external {
        require(admin == address(0), "already init");
        maxEthQty = _maxEthQty;
        minQty = _minEthQty;
        minProfitInBps = _minProfit;

        admin = msg.sender;        
    }

    function setMaxEthQty(uint newVal) external {
        require(msg.sender == admin, "!admin");
        maxEthQty = newVal;        
    }

    function setMinEthQty(uint newVal) external {
        require(msg.sender == admin, "!admin");
        minQty = newVal;        
    }
    
    function setMinProfit(uint newVal) external {
        require(msg.sender == admin, "!admin");
        minProfitInBps = newVal;        
    }

    function addBamm(address newBamm) external {
        require(msg.sender == admin, "!admin");        
        bamms.push(newBamm);
    }

    function removeBamm(address bamm) external {
        require(msg.sender == admin, "!admin");
        for(uint i = 0 ; i < bamms.length ; i++) {
            if(bamms[i] == bamm) {
                bamms[i] = bamms[bamms.length - 1];
                bamms.pop();

                return;
            }
        }

        revert("bamm does not exist");
    }

    function withdrawEth() external {
        require(msg.sender == admin, "!admin");
        msg.sender.transfer(address(this).balance);        
    }

    function upgrade(address newMaster) public {
        require(msg.sender == admin, "!admin");
        masterCopy = newMaster;        
    }
}
function transferAdmin(address newAdmin) external {
    require(msg.sender == admin, "!admin");
    admin = newAdmin;
}

// admin stuff