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

import {ERC20Permit} from "@openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20} from "@openzeppelin-contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";

import {IGHST} from "./interfaces/IGHST.sol";
import {ISTNK} from "./interfaces/ISTNK.sol";
import {IStaking} from "./interfaces/IStaking.sol";

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

contract Stinky is ISTNK, ERC20Permit {
    uint256 private constant INITIAL_SHARES_SUPPLY = 5 * 10**15;
    uint256 private constant TOTAL_SHARES = type(uint256).max - (type(uint256).max % INITIAL_SHARES_SUPPLY);
    uint256 private constant MAX_SUPPLY = type(uint128).max;

    uint256 internal immutable _INTERNAL_INDEX;
    address internal _initializer;

    uint256 private _sharesPerUnit;
    uint256 private _totalSupply;

    address public staking;
    address public ghst;
    address public treasury;

    mapping(address => uint256) public override debtBalances;
    mapping(address => uint256) private _shares;
    mapping(address => mapping(address => uint256)) private _allowedValue;

    constructor(uint256 usedIndex, string memory name, string memory symbol)
        ERC20(name, symbol)
        ERC20Permit(name)
    {
        _initializer = msg.sender;
        _INTERNAL_INDEX = usedIndex;
        _totalSupply = INITIAL_SHARES_SUPPLY;
        _sharesPerUnit = TOTAL_SHARES / INITIAL_SHARES_SUPPLY;
    }

    function initialize(
        address _staking,
        address _treasury,
        address _ghst
    ) external {
        if (msg.sender != _initializer) revert NotInitializer();

        staking = _staking;
        treasury = _treasury;
        ghst = _ghst;

        _shares[_staking] = TOTAL_SHARES;
        _initializer = address(0);

        emit Transfer(address(0), _staking, _totalSupply);
        emit LogStakingContractUpdated(_staking);
    }

    function rebase(
        uint256 supplyDelta,
        uint256 epoch
    ) public override returns (uint256 newTotalSupply) {
        if (msg.sender != staking) revert NotStakingContract();
        uint256 currentCirculatingSupply = circulatingSupply();
        uint256 rebasePercent = 0;

        if (supplyDelta == 0) {
            newTotalSupply = _totalSupply;
        } else {
            if (currentCirculatingSupply > 0)  {
                supplyDelta = FullMath.mulDiv(supplyDelta, _totalSupply, currentCirculatingSupply);
                rebasePercent = FullMath.mulDiv(supplyDelta, 1e18, currentCirculatingSupply);
            }
            newTotalSupply = _totalSupply + supplyDelta;
        }

        if (newTotalSupply > MAX_SUPPLY) {
            newTotalSupply = MAX_SUPPLY;
        }

        _totalSupply = newTotalSupply;
        _sharesPerUnit = TOTAL_SHARES / newTotalSupply;

        emit LogSupply(epoch, newTotalSupply);
        emit LogRebase(epoch, rebasePercent, index());
    }

    function transfer(
        address to,
        uint256 value
    ) public override(IERC20, ERC20) returns (bool) {
        _transferInner(msg.sender, to, value);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) public override(IERC20, ERC20) returns (bool) {
        _spendAllowance(from, msg.sender, value);
        _transferInner(from, to, value);
        return true;
    }

    function approve(
        address spender,
        uint256 value
    ) public override(IERC20, ERC20) returns (bool) {
        _approve(msg.sender, spender, value, true);
        return true;
    }

    function increaseAllowance(
        address spender,
        uint256 imbalance
    ) public override returns (bool) {
        imbalance = imbalance + _allowedValue[msg.sender][spender];
        _approve(msg.sender, spender, imbalance, true);
        return true;
    }

    function decreaseAllowance(
        address spender,
        uint256 imbalance
    ) public override returns (bool) {
        uint256 prevAllowance = _allowedValue[msg.sender][spender];
        imbalance = imbalance < prevAllowance ? prevAllowance - imbalance : 0;
        _approve(msg.sender, spender, imbalance, true);
        return true;
    }

    function decimals() public pure override returns (uint8) {
        return 9;
    }

    function totalSupply() public view override(IERC20, ERC20) returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(
        address who
    ) public view override(IERC20, ERC20) returns (uint256) {
        return _shares[who] / _sharesPerUnit;
    }

    function allowance(
        address owner,
        address spender
    ) public view override(IERC20, ERC20) returns (uint256) {
        return _allowedValue[owner][spender];
    }

    function sharesForBalance(uint256 amount) public view override returns (uint256) {
        return amount * _sharesPerUnit;
    }

    function balanceForShares(uint256 shares) public view override returns (uint256) {
        return shares / _sharesPerUnit;
    }

    function toGhst(uint256 amount) external view override returns (uint256) {
        return IGHST(ghst).balanceTo(amount);
    }

    function fromGhst(uint256 amount) external view override returns (uint256) {
        return IGHST(ghst).balanceFrom(amount);
    }

    function circulatingSupply() public view override returns (uint256) {
        return _totalSupply +
            IGHST(ghst).balanceFrom(IERC20(ghst).totalSupply() + IStaking(staking).ghostedSupply()) +
            IStaking(staking).supplyInWarmup() -
            balanceOf(staking);
    }

    function index() public view override returns (uint256) {
        return balanceForShares(_INTERNAL_INDEX);
    }

    function changeDebt(
        uint256 amount,
        address debtor,
        bool add
    ) external override {
        if (msg.sender != treasury) revert NotTreasury();
        uint256 debtBalance = debtBalances[debtor];
        debtBalance = add ? debtBalance + amount : debtBalance - amount;
        if (debtBalance > balanceOf(debtor)) revert InsufficientBalance();
        debtBalances[debtor] = debtBalance;
    }

    function _transferInner(
        address from,
        address to,
        uint256 value
    ) internal {
        uint256 sharesValue = value * _sharesPerUnit;
        _shares[from] = _shares[from] - sharesValue;
        _shares[to] = _shares[to] + sharesValue;
        if (balanceOf(from) < debtBalances[from]) revert DebtExists();
        emit Transfer(from, to, value);
    }

    function _spendAllowance(address owner, address spender, uint256 value) internal override {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }

    function _approve(
        address owner,
        address spender,
        uint256 value,
        bool emitEvent
    ) internal override {
        if (owner == address(0)) revert ERC20InvalidApprover(address(0));
        if (spender == address(0)) revert ERC20InvalidSpender(address(0));
        _allowedValue[owner][spender] = value;
        if (emitEvent) emit Approval(owner, spender, value);
    }
}
