contract Curve {
    using SafeMath for uint256;
    using Decimal for Decimal.D256;

    function calculateCouponPremium(
        uint256 totalSupply,
        uint256 totalDebt,
        uint256 amount
    ) internal pure returns (uint256) {
        return effectivePremium(totalSupply, totalDebt, amount).mul(amount).asUint256();
    }

    function effectivePremium(
        uint256 totalSupply,
        uint256 totalDebt,
        uint256 amount
    ) private pure returns (Decimal.D256 memory) {
        Decimal.D256 memory debtRatio = Decimal.ratio(totalDebt, totalSupply);
        Decimal.D256 memory debtRatioUpperBound = Constants.getDebtRatioCap();

        uint256 totalSupplyEnd = totalSupply.sub(amount);
        uint256 totalDebtEnd = totalDebt.sub(amount);
        Decimal.D256 memory debtRatioEnd = Decimal.ratio(totalDebtEnd, totalSupplyEnd);

        if (debtRatio.greaterThan(debtRatioUpperBound)) {
            if (debtRatioEnd.greaterThan(debtRatioUpperBound)) {
                return curve(debtRatioUpperBound);
            }

            Decimal.D256 memory premiumCurve = curveMean(debtRatioEnd, debtRatioUpperBound);
            Decimal.D256 memory premiumCurveDelta = debtRatioUpperBound.sub(debtRatioEnd);
            Decimal.D256 memory premiumFlat = curve(debtRatioUpperBound);
            Decimal.D256 memory premiumFlatDelta = debtRatio.sub(debtRatioUpperBound);
            return (premiumCurve.mul(premiumCurveDelta)).add(premiumFlat.mul(premiumFlatDelta))
                .div(premiumCurveDelta.add(premiumFlatDelta));
        }

        return curveMean(debtRatioEnd, debtRatio);
    }

    // 1/(3(1-R)^2)-1/3
    function curve(Decimal.D256 memory debtRatio) private pure returns (Decimal.D256 memory) {
        return Decimal.one().div(
            Decimal.from(3).mul((Decimal.one().sub(debtRatio)).pow(2))
        ).sub(Decimal.ratio(1, 3));
    }

    // 1/(3(1-R)(1-R'))-1/3
    function curveMean(
        Decimal.D256 memory lower,
        Decimal.D256 memory upper
    ) private pure returns (Decimal.D256 memory) {
        if (lower.equals(upper)) {
            return curve(lower);
        }

        return Decimal.one().div(
            Decimal.from(3).mul(Decimal.one().sub(upper)).mul(Decimal.one().sub(lower))
        ).sub(Decimal.ratio(1, 3));
    }
}
// pragma solidity ^0.5.17;
// import "@openzeppelin/contracts/math/SafeMath.sol";
// import "contracts/external/Decimal.sol";
// import "contracts/Constants.sol";
function curveMean(
    Decimal.D256 memory lower,
    Decimal.D256 memory upper
) private pure returns (Decimal.D256 memory) {
    if (lower.equals(upper)) {
        return curve(lower);
    }

    return Decimal.one().div(
        Decimal.from(3).mul(Decimal.one().sub(upper)).mul(Decimal.one().sub(lower))
    ).sub(Decimal.ratio(1, 3));
}

// 1/(3(1-R)(1-R'))-1/3