abstract contract VTokenBase is PoolShareToken, Ownable {
    uint256 public vaultNum;
    bytes32 public collateralType;
    uint256 internal constant WAT = 10**16;
    address internal wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    bool internal lockEth = true;
    Controller public controller;

    constructor(
        string memory name,
        string memory symbol,
        bytes32 _collateralType,
        address _token,
        address _controller
    ) public PoolShareToken(name, symbol, _token) {
        require(_controller != address(0), "Controller address is zero");
        collateralType = _collateralType;
        controller = Controller(_controller);
        vaultNum = createVault(collateralType);
    }

    function approveToken() public virtual {
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        IERC20(cm.debtToken()).approve(address(cm), uint256(-1));
        IERC20(sm.token()).approve(address(sm), uint256(-1));
        token.approve(address(sm), uint256(-1));
        token.approve(address(cm), uint256(-1));
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function shutdown() public onlyOwner {
        _shutdown();
    }

    function open() public onlyOwner {
        _open();
    }

    function registerCollateralManager(address _cm) public {
        require(_msgSender() == address(controller), "Not a controller");
        ManagerInterface manager = ManagerInterface(controller.ap().mcdManager());
        //hope and cpdAllow on vat for collateralManager's address
        VatInterface(manager.vat()).hope(_cm);
        manager.cdpAllow(vaultNum, _cm, 1);

        //Register vault with collateral Manager
        ICollateralManager(_cm).registerVault(vaultNum, collateralType);
    }

    function withdrawAll() public onlyOwner {
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        sm.rebalanceEarned(vaultNum);
        uint256 earnBalance = sm.balanceOf(address(this));
        sm.paybackDebt(vaultNum, earnBalance);
        require(poolDebt() == 0, "Debt should be 0");
        cm.withdrawCollateral(vaultNum, tokenLocked());
    }

    function rebalance() public {
        require(!stopEverything || (_msgSender() == owner()), "Not an owner");
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        sm.rebalanceEarned(vaultNum);
        _depositCollateral(cm);
        sm.rebalanceCollateral(vaultNum);
    }

    function rebalanceCollateral() public {
        require(!stopEverything || (_msgSender() == owner()), "Not an owner");
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        _depositCollateral(cm);
        sm.rebalanceCollateral(vaultNum);
    }

    function rebalanceEarned() public {
        require(!stopEverything || (_msgSender() == owner()), "Not an owner");
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        sm.rebalanceEarned(vaultNum);
    }

    /**
     * @dev If pool is underwater this function will get is back to water surface.
     */
    //TODO: what if Maker doesn't allow required collateral withdrawl?
    function resurface() public {
        require(!stopEverything || (_msgSender() == owner()), "Not an owner");
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        address debtToken = cm.debtToken();
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        uint256 earnBalance = sm.balanceOf(address(this));
        uint256 debt = cm.getVaultDebt(vaultNum);
        if (debt > earnBalance) {
            // Pool is underwater
            uint256 shortAmount = debt.sub(earnBalance);

            IUniswapV2Router02 uniswapRouter = IUniswapV2Router02(
                controller.ap().uniswapRouterV2()
            );
            address[] memory path;
            if (address(token) == wethAddress) {
                path = new address[](2);
                path[0] = address(token);
                path[1] = debtToken;
            } else {
                path = new address[](3);
                path[0] = address(token);
                path[1] = wethAddress;
                path[2] = debtToken;
            }
            uint256 tokenNeeded = uniswapRouter.getAmountsIn(shortAmount, path)[0];

            uint256 balanceHere = tokensHere();
            if (balanceHere < tokenNeeded) {
                cm.withdrawCollateral(vaultNum, tokenNeeded.sub(balanceHere));
            }

            token.approve(address(uniswapRouter), tokenNeeded);
            uniswapRouter.swapExactTokensForTokens(tokenNeeded, 1, path, address(this), now + 30);
            uint256 debtTokenBalance = IERC20(debtToken).balanceOf(address(this));
            cm.payback(vaultNum, debtTokenBalance);
        }
    }

    function tokenLocked() public view returns (uint256) {
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        return cm.getVaultBalance(vaultNum);
    }

    function poolDebt() public view returns (uint256) {
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        return cm.getVaultDebt(vaultNum);
    }

    function isUnderwater() public view returns (bool) {
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        return sm.isUnderwater(vaultNum);
    }

    function tokensHere() public override view returns (uint256) {
        return token.balanceOf(address(this));
    }

    function totalValue() public override view returns (uint256) {
        return tokenLocked().add(convertTo18(tokensHere()));
    }

    function _getFee() internal override view returns (uint256) {
        return controller.fee(address(this));
    }

    function _getFeeCollector() internal override view returns (address) {
        return controller.feeCollector(address(this));
    }

    function _depositCollateral(ICollateralManager cm) internal {
        uint256 balance = tokensHere();
        if (balance > 0) {
            cm.depositCollateral(vaultNum, balance);
        }
    }

    function _withdrawCollateral(uint256 amount) internal {
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        require(!sm.isUnderwater(vaultNum), "Pool is underwater");

        uint256 balanceHere = tokensHere();
        if (balanceHere < amount) {
            uint256 amountNeeded = amount.sub(balanceHere);
            (
                uint256 collateralLocked,
                uint256 debt,
                uint256 collateralUsdRate,
                uint256 collateralRatio,
                uint256 minimumDebt
            ) = cm.whatWouldWithdrawDo(vaultNum, amountNeeded);
            if (debt > 0) {
                if (collateralRatio < controller.lowWater(address(this))) {
                    // If this withdraw results in Low Water scenario.
                    uint256 maxDebt = (collateralLocked.mul(collateralUsdRate)).div(
                        controller.highWater(address(this))
                    );
                    if (maxDebt < minimumDebt) {
                        // This is Dusting scenario
                        sm.paybackDebt(vaultNum, debt);
                    } else if (maxDebt < debt) {
                        sm.paybackDebt(vaultNum, debt.sub(maxDebt));
                    }
                }
            }
            cm.withdrawCollateral(vaultNum, amountNeeded);
        }
    }

    function createVault(bytes32 _collateralType) internal returns (uint256 vaultId) {
        ManagerInterface manager = ManagerInterface(controller.ap().mcdManager());
        vaultId = manager.open(_collateralType, address(this));
        manager.cdpAllow(vaultId, address(this), 1);
    }

    function _sweepErc20(address from) internal {
        ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
        StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
        require(
            from != address(token) &&
                from != address(this) &&
                from != cm.debtToken() &&
                from != sm.token(),
            "Not allowed to sweep"
        );
        IUniswapV2Router02 uniswapRouter = IUniswapV2Router02(controller.ap().uniswapRouterV2());
        IERC20 fromToken = IERC20(from);
        uint256 amt = fromToken.balanceOf(address(this));
        fromToken.approve(address(uniswapRouter), amt);
        address[] memory path;
        if (address(token) == wethAddress) {
            path = new address[](2);
            path[0] = from;
            path[1] = address(token);
        } else {
            path = new address[](3);
            path[0] = from;
            path[1] = wethAddress;
            path[2] = address(token);
        }
        uniswapRouter.swapExactTokensForTokens(amt, 1, path, address(this), now + 30);
    }
}
function resurface() public {
    require(!stopEverything || (_msgSender() == owner()), "Not an owner");
    ICollateralManager cm = ICollateralManager(controller.poolCollateralManager(address(this)));
    address debtToken = cm.debtToken();
    StrategyManager sm = StrategyManager(controller.poolStrategy(address(this)));
    uint256 earnBalance = sm.balanceOf(address(this));
    uint256 debt = cm.getVaultDebt(vaultNum);
    if (debt > earnBalance) {
        // Pool is underwater
        uint256 shortAmount = debt.sub(earnBalance);

        IUniswapV2Router02 uniswapRouter = IUniswapV2Router02(
            controller.ap().uniswapRouterV2()
        );
        address[] memory path;
        if (address(token) == wethAddress) {
            path = new address[](2);
            path[0] = address(token);
            path[1] = debtToken;
        } else {
            path = new address[](3);
            path[0] = address(token);
            path[1] = wethAddress;
            path[2] = debtToken;
        }
        uint256 tokenNeeded = uniswapRouter.getAmountsIn(shortAmount, path)[0];

        uint256 balanceHere = tokensHere();
        if (balanceHere < tokenNeeded) {
            cm.withdrawCollateral(vaultNum, tokenNeeded.sub(balanceHere));
        }

        token.approve(address(uniswapRouter), tokenNeeded);
        uniswapRouter.swapExactTokensForTokens(tokenNeeded, 1, path, address(this), now + 30);
        uint256 debtTokenBalance = IERC20(debtToken).balanceOf(address(this));
        cm.payback(vaultNum, debtTokenBalance);
    }
}

//TODO: what if Maker doesn't allow required collateral withdrawl?