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

library LendingMath {
    uint256 internal constant FACTOR_SCALE = 1e18;
    uint256 internal constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;

    function principalToBalance(int104 principal, uint256 index) internal pure returns (int256) {
        return int256(principal) * int256(index) / int256(FACTOR_SCALE);
    }
    
    function balanceToPrincipal(int256 balance, uint256 index) internal pure returns (int104) {
        return int104((balance * int256(FACTOR_SCALE)) / int256(index));
    }
    
    function repayAndSupplyAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) {
        if (newPrincipal < oldPrincipal) return (0, 0);
        
        if (newPrincipal <= 0) {
            return (uint104(newPrincipal - oldPrincipal), 0);
        } else if (oldPrincipal >= 0) {
            return (0, uint104(newPrincipal - oldPrincipal));
        } else {
            return (uint104(-oldPrincipal), uint104(newPrincipal));
        }
    }
    
    function withdrawAndBorrowAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) {
        if (newPrincipal > oldPrincipal) return (0, 0);
        
        if (newPrincipal >= 0) {
            return (uint104(oldPrincipal - newPrincipal), 0);
        } else if (oldPrincipal <= 0) {
            return (0, uint104(oldPrincipal - newPrincipal));
        } else {
            return (uint104(oldPrincipal), uint104(-newPrincipal));
        }
    }

    function getUtilization(uint256 totalSupply, uint256 totalBorrow) internal pure returns (uint64) {
        if (totalSupply == 0) return 0;
        return uint64((totalBorrow * FACTOR_SCALE) / totalSupply);
    }

    function getSupplyRate(
        uint256 utilization,
        uint64 supplyKink,
        uint64 supplyPerSecondInterestRateSlopeLow,
        uint64 supplyPerSecondInterestRateSlopeHigh,
        uint64 supplyPerSecondInterestRateBase
    ) internal pure returns (uint64) {
        if (utilization <= supplyKink) {
            return supplyPerSecondInterestRateBase + uint64((utilization * supplyPerSecondInterestRateSlopeLow) / FACTOR_SCALE);
        } else {
            uint256 excessUtil = utilization - supplyKink;
            return supplyPerSecondInterestRateBase + supplyPerSecondInterestRateSlopeLow + 
                   uint64((excessUtil * supplyPerSecondInterestRateSlopeHigh) / FACTOR_SCALE);
        }
    }

    function getBorrowRate(
        uint256 utilization,
        uint64 borrowKink,
        uint64 borrowPerSecondInterestRateSlopeLow,
        uint64 borrowPerSecondInterestRateSlopeHigh,
        uint64 borrowPerSecondInterestRateBase
    ) internal pure returns (uint64) {
        if (utilization <= borrowKink) {
            return borrowPerSecondInterestRateBase + uint64((utilization * borrowPerSecondInterestRateSlopeLow) / FACTOR_SCALE);
        } else {
            uint256 excessUtil = utilization - borrowKink;
            return borrowPerSecondInterestRateBase + borrowPerSecondInterestRateSlopeLow + 
                   uint64((excessUtil * borrowPerSecondInterestRateSlopeHigh) / FACTOR_SCALE);
        }
    }

    function accrueInterest(
        uint256 index,
        uint64 interestRatePerSecond,
        uint256 timeElapsed
    ) internal pure returns (uint256) {
        uint256 interestAccrued = (index * interestRatePerSecond * timeElapsed) / FACTOR_SCALE;
        return index + interestAccrued;
    }

    function getCollateralValue(
        uint256 collateralAmount,
        uint256 collateralPrice,
        uint8 collateralDecimals
    ) internal pure returns (uint256) {
        return (collateralAmount * collateralPrice) / (10 ** collateralDecimals);
    }

    function getBorrowCapacity(
        uint256 collateralValue,
        uint64 borrowCollateralFactor
    ) internal pure returns (uint256) {
        return (collateralValue * borrowCollateralFactor) / FACTOR_SCALE;
    }
}