contract CallOptionCalculator is AdvancedMath {
    /**
     * @dev sqrt(365*86400) * 10^8
     */
    int256 internal constant SQRT_YEAR_E8 = 561569229926;

    int256 internal constant MIN_ND1_E8 = 0.0001 * 10**8;
    int256 internal constant MAX_ND1_E8 = 0.9999 * 10**8;

    function _calcLbtPrice(
        int256 etherPriceE4,
        int256 strikePriceE4,
        int256 nd1E8,
        int256 nd2E8
    ) public pure returns (int256 lbtPriceE4) {
        int256 lowestPriceE4 = (etherPriceE4 > strikePriceE4) ? etherPriceE4 - strikePriceE4 : 0; // max(etherPriceE8 - strikePriceE8, 0)
        lbtPriceE4 = (etherPriceE4 * (nd1E8) - (strikePriceE4 * nd2E8)) / 10**8; // mutable
        if (lbtPriceE4 < lowestPriceE4) {
            lbtPriceE4 = lowestPriceE4; // max(lbtPriceE8, lowestPriceE8)
        }
    }

    function _calcLbtLeverage(
        uint256 etherPriceE4,
        uint256 lbtPriceE4,
        int256 nd1E8
    ) public pure returns (uint256 lbtLeverageE4) {
        int256 modifiedNd1E8 = nd1E8 < MIN_ND1_E8 ? MIN_ND1_E8 : nd1E8 > MAX_ND1_E8
            ? MAX_ND1_E8
            : nd1E8; // clamp(MIN_ND1, nd1E4, MAX_ND1)
        return
            lbtPriceE4 != 0
                ? (uint256(modifiedNd1E8) * (etherPriceE4)) / (lbtPriceE4) / 10**4
                : 100 * 10**4;
    }

    /**
     * @dev
     * s := v * sqrt(t / (365 * 86400))
     * d1 := log(S/K) / s + s / 2
     * d2 := d1 - s
     * price := S * N(d1) - K * N(d2)
     */
    function calcLbtPriceAndLeverage(
        int256 etherPriceE4,
        int256 strikePriceE4,
        int256 ethVolatilityE8,
        int256 untilMaturity
    )
        public
        pure
        returns (
            uint256 priceE4,
            uint256 leverageE4,
            int256 sigE8
        )
    {
        require(etherPriceE4 > 0, "the price of ETH should be positive");
        require(ethVolatilityE8 > 0, "the volatility of ETH should be positive");
        require(strikePriceE4 > 0, "the strike price should be positive");
        require(untilMaturity >= 0, "LBT should not have expired");
        require(untilMaturity <= 12 weeks, "the maturity of LBT should not be so distant");

        int256 nd1E8;
        {
            int256 spotPerStrikeE4 = (etherPriceE4 * (10**4)) / strikePriceE4;
            sigE8 = (ethVolatilityE8 * (_sqrt(untilMaturity)) * (10**8)) / (SQRT_YEAR_E8);

            int256 logSigE4 = _logTaylor(spotPerStrikeE4);
            int256 d1E4 = ((logSigE4 * 10**8) / sigE8) + (sigE8 / (2 * 10**4));
            nd1E8 = _calcPnorm(d1E4);

            int256 d2E4 = d1E4 - (sigE8 / 10**4);
            int256 nd2E8 = _calcPnorm(d2E4);
            if (nd1E8 > 0.0001 * 10**8 && nd2E8 > 0.0001 * 10**8) {
                int256 lbtPriceE4 = _calcLbtPrice(etherPriceE4, strikePriceE4, nd1E8, nd2E8);
                priceE4 = uint256(lbtPriceE4);
            }
        }

        leverageE4 = _calcLbtLeverage(uint256(etherPriceE4), priceE4, nd1E8);

        return (priceE4, leverageE4, sigE8);
    }
}
function calcLbtPriceAndLeverage(
    int256 etherPriceE4,
    int256 strikePriceE4,
    int256 ethVolatilityE8,
    int256 untilMaturity
)
    public
    pure
    returns (
        uint256 priceE4,
        uint256 leverageE4,
        int256 sigE8
    )
{
    require(etherPriceE4 > 0, "the price of ETH should be positive");
    require(ethVolatilityE8 > 0, "the volatility of ETH should be positive");
    require(strikePriceE4 > 0, "the strike price should be positive");
    require(untilMaturity >= 0, "LBT should not have expired");
    require(untilMaturity <= 12 weeks, "the maturity of LBT should not be so distant");

    int256 nd1E8;
    {
        int256 spotPerStrikeE4 = (etherPriceE4 * (10**4)) / strikePriceE4;
        sigE8 = (ethVolatilityE8 * (_sqrt(untilMaturity)) * (10**8)) / (SQRT_YEAR_E8);

        int256 logSigE4 = _logTaylor(spotPerStrikeE4);
        int256 d1E4 = ((logSigE4 * 10**8) / sigE8) + (sigE8 / (2 * 10**4));
        nd1E8 = _calcPnorm(d1E4);

        int256 d2E4 = d1E4 - (sigE8 / 10**4);
        int256 nd2E8 = _calcPnorm(d2E4);
        if (nd1E8 > 0.0001 * 10**8 && nd2E8 > 0.0001 * 10**8) {
            int256 lbtPriceE4 = _calcLbtPrice(etherPriceE4, strikePriceE4, nd1E8, nd2E8);
            priceE4 = uint256(lbtPriceE4);
        }
    }

    leverageE4 = _calcLbtLeverage(uint256(etherPriceE4), priceE4, nd1E8);

    return (priceE4, leverageE4, sigE8);
}

/**
 * @dev
 * s := v * sqrt(t / (365 * 86400))
 * d1 := log(S/K) / s + s / 2
 * d2 := d1 - s
 * price := S * N(d1) - K * N(d2)
 */