// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {FixedPoint} from "./libraries/FixedPoint.sol";

import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol";

import {IBondingCalculator} from "./interfaces/IBondingCalculator.sol";
import {FullMath} from "./libraries/FullMath.sol";

contract GhostBondingCalculator is IBondingCalculator {
    using FixedPoint for *;

    uint256 public override immutable fraction;     // forge-lint: disable-line(screaming-snake-case-immutable)
    address internal immutable ftso;                // forge-lint: disable-line(screaming-snake-case-immutable)

    constructor(address _ftso, uint256 _numerator, uint256 _denominator) {
        ftso = _ftso;
        fraction = FixedPoint.fraction(_numerator, _denominator).decode112with18();
    }

    function getKValue(address pair, bool isCoefficient) public view returns (uint256 k) {
        uint256 token0 = IERC20Metadata(IUniswapV2Pair(pair).token0()).decimals();
        uint256 token1 = IERC20Metadata(IUniswapV2Pair(pair).token1()).decimals();
        uint256 decimals = token0 + token1 - IERC20Metadata(pair).decimals();

        (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pair).getReserves();
        k = FullMath.mulDiv(reserve0, reserve1, 10**decimals);

        if (isCoefficient) {
            k = FullMath.mulDiv(k, 1e18, fraction);
        }
    }

    function getTotalValue(address pair, bool isCoefficient) public view returns (uint256) {
        return _sqrt(getKValue(pair, isCoefficient)) * 2;
    }

    function valuation(address pair, uint256 amount) external view override returns (uint256 value) {
        uint256 totalValue = getTotalValue(pair, true);
        uint256 totalSupply = IUniswapV2Pair(pair).totalSupply();

        value = FullMath.mulDiv(totalValue, FixedPoint.fraction(amount, totalSupply).decode112with18(), 1e18);
        value = FullMath.mulDiv(value, fraction, 1e18);
    }

    function markdown(address pair) external view override returns (uint256 reserve) {
        (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pair).getReserves();

        if (IUniswapV2Pair(pair).token0() == ftso) {
            reserve = reserve1;
        } else {
            if (IUniswapV2Pair(pair).token1() != ftso) revert InvalidPair();
            reserve = reserve0;
        }

        reserve = FullMath.mulDiv(reserve, 2 * 1e9, getTotalValue(pair, false));
        reserve = FullMath.mulDiv(reserve, _sqrt(fraction), 1e9);
    }

    function _sqrt(uint256 a) private pure returns (uint256 c) {
        if (a > 3) {
            c = a;
            uint256 b = (a / 2) + 1;
            while (b < c) {
                c = b;
                b = ((a / b) + b) / 2;
            }
        } else if (a != 0) {
            c = 1;
        }
    }
}
