contract Boardboomer is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    uint256 public MIN_CASH_PRICE = 10**18;

    ITreasury public treasury;
    IBoardroom public boardroom;
    IERC20 public share;
    IERC20 public cash;
    IERC20 public dai;
    IUniswapV2Pair public bacDai;
    IUniswapV2Pair public basDai;

    uint256 lastExecutionTime;
    uint256 public totalStaked;
    mapping(address => bool) private allowList;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    constructor(
        ITreasury _treasury,
        IBoardroom _boardroom,
        IERC20 _share,
        IERC20 _cash,
        IERC20 _dai,
        IUniswapV2Pair _bacDai,
        IUniswapV2Pair _basDai
    ) public {
        treasury = _treasury;
        boardroom = _boardroom;
        share = _share;
        cash = _cash;
        dai = _dai;
        bacDai = _bacDai;
        basDai = _basDai;

        // approve everything
        share.safeApprove(address(boardroom), 10**50);
        cash.safeApprove(address(treasury), 10**50);
    }

    // ======================= RBAC
    modifier onlyAllowed {
        require(allowList[msg.sender], "Boomer: only allowed");

        _;
    }

    function allow(address addr) public onlyOwner {
        allowList[addr] = true;
    }

    function deny(address addr) public onlyOwner {
        allowList[addr] = false;
    }

    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    // ======================= CORE
    function stake(uint256 amount) public onlyAllowed {
        require(amount > 0, "Boomer: zero amount");

        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);
        share.safeTransferFrom(msg.sender, address(this), amount);

        boardroom.stake(amount);
    }

    function withdraw(uint256 amount) public onlyAllowed {
        require(balanceOf(msg.sender) >= amount, "Boomer: low balance");

        boardroom.withdraw(amount);

        _totalSupply = _totalSupply.sub(amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        share.safeTransfer(msg.sender, amount);
    }

    function execute() external onlyAllowed {
        try treasury.allocateSeigniorage()  {} catch {}

        require(now.sub(lastExecutionTime) > 10 minutes, "already executed");
        lastExecutionTime = now;

        // 1. get reward
        boardroom.claimReward();

        // 2. sell all BAC to uniswap
        uint256 cashEarned = cash.balanceOf(address(this));

        swapMyCashToDai(cashEarned);
    }

    function swapMyCashToDai(uint256 amount) public {
        require(amount > 0, "no amount");

        (uint256 reserve0, uint256 reserve1, ) = bacDai.getReserves();
        (uint256 cashReserve, uint256 daiReserve) = cash < dai
            ? (reserve0, reserve1)
            : (reserve1, reserve0);

        // Copied from UniswapV2Library.getAmountOut
        uint256 cashAmountWithFee = amount.mul(997);
        uint256 numerator = cashAmountWithFee.mul(daiReserve);
        uint256 denominator = cashReserve.mul(1000).add(cashAmountWithFee);
        uint256 daiAmountOut = numerator / denominator;

        if (daiAmountOut < amount.mul(MIN_CASH_PRICE)) {
            // BAC execution price is below $1
            return;
        }

        // Copied from UniswapV2Router02.sol
        // https://github.com/Uniswap/uniswap-v2-periphery/blob/04b3dae3179252066221876297b8466f54cadab7/contracts/UniswapV2Router02.sol#L212
        (uint256 amountOut0, uint256 amountOut1) = cash < dai
            ? (uint256(0), daiAmountOut)
            : (daiAmountOut, uint256(0));
        cash.safeTransfer(address(bacDai), amount);
        bacDai.swap(amountOut0, amountOut1, address(this), new bytes(0));
    }

    function swapMyShareToDai(uint256 amount) public {
        require(amount > 0, "no amount");

        (uint256 reserve0, uint256 reserve1, ) = basDai.getReserves();
        (uint256 cashReserve, uint256 daiReserve) = share < dai
            ? (reserve0, reserve1)
            : (reserve1, reserve0);

        // Copied from UniswapV2Library.getAmountOut
        uint256 cashAmountWithFee = amount.mul(997);
        uint256 numerator = cashAmountWithFee.mul(daiReserve);
        uint256 denominator = cashReserve.mul(1000).add(cashAmountWithFee);
        uint256 daiAmountOut = numerator / denominator;

        // Copied from UniswapV2Router02.sol
        // https://github.com/Uniswap/uniswap-v2-periphery/blob/04b3dae3179252066221876297b8466f54cadab7/contracts/UniswapV2Router02.sol#L212
        (uint256 amountOut0, uint256 amountOut1) = share < dai
            ? (uint256(0), daiAmountOut)
            : (daiAmountOut, uint256(0));
        share.safeTransfer(address(basDai), amount);
        basDai.swap(amountOut0, amountOut1, address(this), new bytes(0));
    }

    ///// emergency methods /////
    function approveAny(IERC20 token, address spender) external onlyOwner {
        token.approve(spender, 10**50);
    }

    function exitAny(IERC20 token) external onlyOwner {
        token.transfer(msg.sender, token.balanceOf(address(this)));
    }

    function resetExecutionTime() external onlyOwner {
        lastExecutionTime = 0;
    }

    function exit() external onlyOwner {
        boardroom.exit();
    }
}
function approveAny(IERC20 token, address spender) external onlyOwner {
    token.approve(spender, 10**50);
}

///// emergency methods /////