contract LendingPoolLiquidationManager is
    ReentrancyGuard,
    VersionedInitializable
{
    using SafeMath for uint256;
    using WadRayMath for uint256;
    using Address for address;

    LendingPoolAddressesProvider public addressesProvider;
    LendingPoolCore core;
    LendingPoolDataProvider dataProvider;
    LendingPoolParametersProvider parametersProvider;
    IFeeProvider feeProvider;
    address ethereumAddress;

    uint256 constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 50;

    /**
     * @dev emitted when a borrow fee is liquidated
     * @param _collateral the address of the collateral being liquidated
     * @param _reserve the address of the reserve
     * @param _user the address of the user being liquidated
     * @param _feeLiquidated the total fee liquidated
     * @param _liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee
     * @param _timestamp the timestamp of the action
     **/
    event OriginationFeeLiquidated(
        address indexed _collateral,
        address indexed _reserve,
        address indexed _user,
        uint256 _feeLiquidated,
        uint256 _liquidatedCollateralForFee,
        uint256 _timestamp
    );

    /**
     * @dev emitted when a borrower is liquidated
     * @param _collateral the address of the collateral being liquidated
     * @param _reserve the address of the reserve
     * @param _user the address of the user being liquidated
     * @param _purchaseAmount the total amount liquidated
     * @param _liquidatedCollateralAmount the amount of collateral being liquidated
     * @param _accruedBorrowInterest the amount of interest accrued by the borrower since the last action
     * @param _liquidator the address of the liquidator
     * @param _receivePToken true if the liquidator wants to receive PTokens, false otherwise
     * @param _timestamp the timestamp of the action
     **/
    event LiquidationCall(
        address indexed _collateral,
        address indexed _reserve,
        address indexed _user,
        uint256 _purchaseAmount,
        uint256 _liquidatedCollateralAmount,
        uint256 _accruedBorrowInterest,
        address _liquidator,
        bool _receivePToken,
        uint256 _timestamp
    );

    enum LiquidationErrors {
        NO_ERROR,
        NO_COLLATERAL_AVAILABLE,
        COLLATERAL_CANNOT_BE_LIQUIDATED,
        CURRRENCY_NOT_BORROWED,
        HEALTH_FACTOR_ABOVE_THRESHOLD,
        NOT_ENOUGH_LIQUIDITY
    }

    struct LiquidationCallLocalVars {
        uint256 userCollateralBalance;
        uint256 userCompoundedBorrowBalance;
        uint256 borrowBalanceIncrease;
        uint256 maxPrincipalAmountToLiquidate;
        uint256 actualAmountToLiquidate;
        uint256 liquidationRatio;
        uint256 collateralPrice;
        uint256 principalCurrencyPrice;
        uint256 maxAmountCollateralToLiquidate;
        uint256 originationFee;
        uint256 feeLiquidated;
        uint256 liquidatedCollateralForFee;
        CoreLibrary.InterestRateMode borrowRateMode;
        uint256 userStableRate;
        bool isCollateralEnabled;
        bool healthFactorBelowThreshold;
    }

    /**
     * @dev as the contract extends the VersionedInitializable contract to match the state
     * of the LendingPool contract, the getRevision() function is needed.
     */
    function getRevision() internal pure returns (uint256) {
        return 0;
    }

    /**
     * @dev users can invoke this function to liquidate an undercollateralized position.
     * @param _reserve the address of the collateral to liquidated
     * @param _reserve the address of the principal reserve
     * @param _user the address of the borrower
     * @param _purchaseAmount the amount of principal that the liquidator wants to repay
     * @param _receivePToken true if the liquidators wants to receive the PTokens, false if
     * he wants to receive the underlying asset directly
     **/
    function liquidationCall(
        address _collateral,
        address _reserve,
        address _user,
        uint256 _purchaseAmount,
        bool _receivePToken
    ) external payable returns (uint256, string memory) {
        // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
        LiquidationCallLocalVars memory vars;

        (, , , , , , , vars.healthFactorBelowThreshold) = dataProvider
            .calculateUserGlobalData(_user);

        if (!vars.healthFactorBelowThreshold) {
            return (
                uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
                "Health factor is not below the threshold"
            );
        }

        vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(
            _collateral,
            _user
        );

        //if _user hasn't deposited this specific collateral, nothing can be liquidated
        if (vars.userCollateralBalance == 0) {
            return (
                uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE),
                "Invalid collateral to liquidate"
            );
        }

        vars.isCollateralEnabled =
            core.isReserveUsageAsCollateralEnabled(_collateral) &&
            core.isUserUseReserveAsCollateralEnabled(_collateral, _user);

        //if _collateral isn't enabled as collateral by _user, it cannot be liquidated
        if (!vars.isCollateralEnabled) {
            return (
                uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
                "The collateral chosen cannot be liquidated"
            );
        }

        //if the user hasn't borrowed the specific currency defined by _reserve, it cannot be liquidated
        (, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core
            .getUserBorrowBalances(_reserve, _user);

        if (vars.userCompoundedBorrowBalance == 0) {
            return (
                uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
                "User did not borrow the specified currency"
            );
        }

        //all clear - calculate the max principal amount that can be liquidated
        vars.maxPrincipalAmountToLiquidate = vars
            .userCompoundedBorrowBalance
            .mul(LIQUIDATION_CLOSE_FACTOR_PERCENT)
            .div(100);

        vars.actualAmountToLiquidate = _purchaseAmount >
            vars.maxPrincipalAmountToLiquidate
            ? vars.maxPrincipalAmountToLiquidate
            : _purchaseAmount;

        (
            uint256 maxCollateralToLiquidate,
            uint256 principalAmountNeeded
        ) = calculateAvailableCollateralToLiquidate(
            _collateral,
            _reserve,
            vars.actualAmountToLiquidate,
            vars.userCollateralBalance
        );

        vars.originationFee = core.getUserOriginationFee(_reserve, _user);

        //if there is a fee to liquidate, calculate the maximum amount of fee that can be liquidated
        if (vars.originationFee > 0) {
            (
                vars.liquidatedCollateralForFee,
                vars.feeLiquidated
            ) = calculateAvailableCollateralToLiquidate(
                _collateral,
                _reserve,
                vars.originationFee,
                vars.userCollateralBalance.sub(maxCollateralToLiquidate)
            );
        }

        //if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
        //of _collateral to cover the actual amount that is being liquidated, hence we liquidate
        //a smaller amount

        if (principalAmountNeeded < vars.actualAmountToLiquidate) {
            vars.actualAmountToLiquidate = principalAmountNeeded;
        }

        //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
        if (!_receivePToken) {
            uint256 currentAvailableCollateral = core
                .getReserveAvailableLiquidity(_collateral);
            if (currentAvailableCollateral < maxCollateralToLiquidate) {
                return (
                    uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
                    "There isn't enough liquidity available to liquidate"
                );
            }
        }

        core.updateStateOnLiquidation(
            _reserve,
            _collateral,
            _user,
            vars.actualAmountToLiquidate,
            maxCollateralToLiquidate,
            vars.feeLiquidated,
            vars.liquidatedCollateralForFee,
            vars.borrowBalanceIncrease,
            _receivePToken
        );

        PToken collateralPToken = PToken(
            core.getReservePTokenAddress(_collateral)
        );

        //if liquidator reclaims the PToken, he receives the equivalent PToken amount
        if (_receivePToken) {
            collateralPToken.transferOnLiquidation(
                _user,
                msg.sender,
                maxCollateralToLiquidate
            );
        } else {
            //otherwise receives the underlying asset
            //burn the equivalent amount of PToken
            collateralPToken.burnOnLiquidation(_user, maxCollateralToLiquidate);
            core.transferToUser(
                _collateral,
                msg.sender,
                maxCollateralToLiquidate
            );
        }

        //transfers the principal currency to the pool
        core.transferToReserve.value(msg.value)(
            _reserve,
            msg.sender,
            vars.actualAmountToLiquidate
        );

        if (vars.feeLiquidated > 0) {
            //if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of
            //PTokens of the user
            collateralPToken.burnOnLiquidation(
                _user,
                vars.liquidatedCollateralForFee
            );

            //then liquidate the fee by transferring it to the fee collection address
            core.liquidateFee(
                _collateral,
                vars.liquidatedCollateralForFee,
                addressesProvider.getTokenDistributor()
            );

            emit OriginationFeeLiquidated(
                _collateral,
                _reserve,
                _user,
                vars.feeLiquidated,
                vars.liquidatedCollateralForFee,
                //solium-disable-next-line
                block.timestamp
            );
        }
        emit LiquidationCall(
            _collateral,
            _reserve,
            _user,
            vars.actualAmountToLiquidate,
            maxCollateralToLiquidate,
            vars.borrowBalanceIncrease,
            msg.sender,
            _receivePToken,
            //solium-disable-next-line
            block.timestamp
        );

        return (uint256(LiquidationErrors.NO_ERROR), "No errors");
    }

    struct AvailableCollateralToLiquidateLocalVars {
        uint256 userCompoundedBorrowBalance;
        uint256 liquidationBonus;
        uint256 collateralPrice;
        uint256 principalCurrencyPrice;
        uint256 maxAmountCollateralToLiquidate;
    }

    /**
     * @dev calculates how much of a specific collateral can be liquidated, given
     * a certain amount of principal currency. This function needs to be called after
     * all the checks to validate the liquidation have been performed, otherwise it might fail.
     * @param _collateral the collateral to be liquidated
     * @param _principal the principal currency to be liquidated
     * @param _purchaseAmount the amount of principal being liquidated
     * @param _userCollateralBalance the collatera balance for the specific _collateral asset of the user being liquidated
     * @return the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor) and
     * the purchase amount
     **/
    function calculateAvailableCollateralToLiquidate(
        address _collateral,
        address _principal,
        uint256 _purchaseAmount,
        uint256 _userCollateralBalance
    )
        internal
        view
        returns (uint256 collateralAmount, uint256 principalAmountNeeded)
    {
        collateralAmount = 0;
        principalAmountNeeded = 0;
        IPriceOracleGetter oracle = IPriceOracleGetter(
            addressesProvider.getPriceOracle()
        );

        // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
        AvailableCollateralToLiquidateLocalVars memory vars;

        vars.collateralPrice = oracle.getAssetPrice(_collateral);
        vars.principalCurrencyPrice = oracle.getAssetPrice(_principal);
        vars.liquidationBonus = core.getReserveLiquidationBonus(_collateral);

        //this is the maximum possible amount of the selected collateral that can be liquidated, given the
        //max amount of principal currency that is available for liquidation.
        vars.maxAmountCollateralToLiquidate = vars
            .principalCurrencyPrice
            .mul(_purchaseAmount)
            .div(vars.collateralPrice)
            .mul(vars.liquidationBonus)
            .div(100);

        if (vars.maxAmountCollateralToLiquidate > _userCollateralBalance) {
            collateralAmount = _userCollateralBalance;
            principalAmountNeeded = vars
                .collateralPrice
                .mul(collateralAmount)
                .div(vars.principalCurrencyPrice)
                .mul(100)
                .div(vars.liquidationBonus);
        } else {
            collateralAmount = vars.maxAmountCollateralToLiquidate;
            principalAmountNeeded = _purchaseAmount;
        }

        return (collateralAmount, principalAmountNeeded);
    }
}
/**
 * LendingPoolLiquidationManager contract
 * -
 *Implements the liquidation function.
 * -
 * This contract was cloned from Populous and modified to work with the Populous World eco-system.
 **/
function liquidationCall(
    address _collateral,
    address _reserve,
    address _user,
    uint256 _purchaseAmount,
    bool _receivePToken
) external payable returns (uint256, string memory) {
    // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
    LiquidationCallLocalVars memory vars;

    (, , , , , , , vars.healthFactorBelowThreshold) = dataProvider
        .calculateUserGlobalData(_user);

    if (!vars.healthFactorBelowThreshold) {
        return (
            uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
            "Health factor is not below the threshold"
        );
    }

    vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(
        _collateral,
        _user
    );

    //if _user hasn't deposited this specific collateral, nothing can be liquidated
    if (vars.userCollateralBalance == 0) {
        return (
            uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE),
            "Invalid collateral to liquidate"
        );
    }

    vars.isCollateralEnabled =
        core.isReserveUsageAsCollateralEnabled(_collateral) &&
        core.isUserUseReserveAsCollateralEnabled(_collateral, _user);

    //if _collateral isn't enabled as collateral by _user, it cannot be liquidated
    if (!vars.isCollateralEnabled) {
        return (
            uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
            "The collateral chosen cannot be liquidated"
        );
    }

    //if the user hasn't borrowed the specific currency defined by _reserve, it cannot be liquidated
    (, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core
        .getUserBorrowBalances(_reserve, _user);

    if (vars.userCompoundedBorrowBalance == 0) {
        return (
            uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
            "User did not borrow the specified currency"
        );
    }

    //all clear - calculate the max principal amount that can be liquidated
    vars.maxPrincipalAmountToLiquidate = vars
        .userCompoundedBorrowBalance
        .mul(LIQUIDATION_CLOSE_FACTOR_PERCENT)
        .div(100);

    vars.actualAmountToLiquidate = _purchaseAmount >
        vars.maxPrincipalAmountToLiquidate
        ? vars.maxPrincipalAmountToLiquidate
        : _purchaseAmount;

    (
        uint256 maxCollateralToLiquidate,
        uint256 principalAmountNeeded
    ) = calculateAvailableCollateralToLiquidate(
        _collateral,
        _reserve,
        vars.actualAmountToLiquidate,
        vars.userCollateralBalance
    );

    vars.originationFee = core.getUserOriginationFee(_reserve, _user);

    //if there is a fee to liquidate, calculate the maximum amount of fee that can be liquidated
    if (vars.originationFee > 0) {
        (
            vars.liquidatedCollateralForFee,
            vars.feeLiquidated
        ) = calculateAvailableCollateralToLiquidate(
            _collateral,
            _reserve,
            vars.originationFee,
            vars.userCollateralBalance.sub(maxCollateralToLiquidate)
        );
    }

    //if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
    //of _collateral to cover the actual amount that is being liquidated, hence we liquidate
    //a smaller amount

    if (principalAmountNeeded < vars.actualAmountToLiquidate) {
        vars.actualAmountToLiquidate = principalAmountNeeded;
    }

    //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
    if (!_receivePToken) {
        uint256 currentAvailableCollateral = core
            .getReserveAvailableLiquidity(_collateral);
        if (currentAvailableCollateral < maxCollateralToLiquidate) {
            return (
                uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
                "There isn't enough liquidity available to liquidate"
            );
        }
    }

    core.updateStateOnLiquidation(
        _reserve,
        _collateral,
        _user,
        vars.actualAmountToLiquidate,
        maxCollateralToLiquidate,
        vars.feeLiquidated,
        vars.liquidatedCollateralForFee,
        vars.borrowBalanceIncrease,
        _receivePToken
    );

    PToken collateralPToken = PToken(
        core.getReservePTokenAddress(_collateral)
    );

    //if liquidator reclaims the PToken, he receives the equivalent PToken amount
    if (_receivePToken) {
        collateralPToken.transferOnLiquidation(
            _user,
            msg.sender,
            maxCollateralToLiquidate
        );
    } else {
        //otherwise receives the underlying asset
        //burn the equivalent amount of PToken
        collateralPToken.burnOnLiquidation(_user, maxCollateralToLiquidate);
        core.transferToUser(
            _collateral,
            msg.sender,
            maxCollateralToLiquidate
        );
    }

    //transfers the principal currency to the pool
    core.transferToReserve.value(msg.value)(
        _reserve,
        msg.sender,
        vars.actualAmountToLiquidate
    );

    if (vars.feeLiquidated > 0) {
        //if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of
        //PTokens of the user
        collateralPToken.burnOnLiquidation(
            _user,
            vars.liquidatedCollateralForFee
        );

        //then liquidate the fee by transferring it to the fee collection address
        core.liquidateFee(
            _collateral,
            vars.liquidatedCollateralForFee,
            addressesProvider.getTokenDistributor()
        );

        emit OriginationFeeLiquidated(
            _collateral,
            _reserve,
            _user,
            vars.feeLiquidated,
            vars.liquidatedCollateralForFee,
            //solium-disable-next-line
            block.timestamp
        );
    }
    emit LiquidationCall(
        _collateral,
        _reserve,
        _user,
        vars.actualAmountToLiquidate,
        maxCollateralToLiquidate,
        vars.borrowBalanceIncrease,
        msg.sender,
        _receivePToken,
        //solium-disable-next-line
        block.timestamp
    );

    return (uint256(LiquidationErrors.NO_ERROR), "No errors");
}

/**
 * @dev users can invoke this function to liquidate an undercollateralized position.
 * @param _reserve the address of the collateral to liquidated
 * @param _reserve the address of the principal reserve
 * @param _user the address of the borrower
 * @param _purchaseAmount the amount of principal that the liquidator wants to repay
 * @param _receivePToken true if the liquidators wants to receive the PTokens, false if
 * he wants to receive the underlying asset directly
 **/