// File: src/DepositContract.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {EnumerableSet} from "./EnumerableSet.sol";
import {Ownable2Step} from "./Ownable2Step.sol";
import {Pausable} from "./Pausable.sol";
import {IDepositContract} from "./IDepositContract.sol";
import {CheckContractAddress} from "./CheckContractAddress.sol";

contract DepositContract is
Ownable2Step,
Pausable,
IDepositContract,
CheckContractAddress
{
    uint256 constant SUBSCRIPTION_DURATION = 180 days;
    using EnumerableSet for EnumerableSet.AddressSet;

    // Whitelisting
    EnumerableSet.AddressSet private whitelist;
    mapping(address => EnumerableSet.AddressSet) private contractWhitelisted;

    // Subscriptions
    mapping(address => Subscription) private subscriptionPeriod;

    struct Subscription {
        uint256 startDate;
        uint256 endDate;
        bool isSnap;
    }

    // Funds management
    mapping(address => uint256) private minBalanceLimitClient;
    mapping(address => uint256) private clientFund;
    uint256 private supraFund;
    uint256 private minBalanceLimitSupra;

    // Miscellaneous
    address public generator;
    address public router;
    address public coldWallet;
    address public _tempWallet;
    address public approver;
    address public developer;
    bool public adminFeelsOK;

    /// @dev Emitted when a client is whitelisted.
    /// @param _clientAddress Address of the client that has been whitelisted.
    /// @param _startTime The timestamp representing the start time of the subscription period.
    /// @param _endTime The timestamp representing the end time of the subscription period.
    /// @param _isSnap A boolean flag indicating if the client is a part of the SNAP program or not.
    event ClientWhitelisted(
        address _clientAddress,
        uint256 _startTime,
        uint256 _endTime,
        bool _isSnap
    );

    /// @dev Emitted when a client is removed from the whitelist.
    /// @param _clientAddress Address of the client that has been removed from the whitelist.
    /// @param _removedTime The timestamp representing the time at which the client was removed.
    event ClientRemovedFromWhitelist(
        address _clientAddress,
        uint256 _removedTime
    );

    /// @dev Emitted when contracts are removed from the whitelist for a client
    /// @param _clientAddress The address of the client whose contracts were removed
    /// @param _removedTime The timestamp when the contracts were removed
    event ContractsDeletedFromWhitelist(
        address _clientAddress,
        uint256 _removedTime
    );

    /// @dev Emitted when a contract is whitelisted for a client.
    /// @param _clientAddress Address of the client whose contract has been whitelisted.
    /// @param _contractAddress Address of the contract that has been whitelisted.
    /// @param _timeStamp The timestamp representing the time at which the contract was whitelisted.
    event ContractWhitelisted(
        address _clientAddress,
        address _contractAddress,
        uint256 _timeStamp
    );

    /// @dev Emitted when a client deposits funds into their account.
    /// @param _depositer The address of the client who deposited funds.
    /// @param amount The amount of funds that were deposited.
    event ClientDeposited(address _depositer, uint256 amount);

    /// @dev Emitted when a client withdraws funds from their account.
    /// @param _withdrawer The address of the client who withdrew funds.
    /// @param amount The amount of funds that were withdrawn.
    event ClientWithdrwal(address _withdrawer, uint256 amount);

    /// @dev Emitted when Supra collects funds from a client's account.
    /// @param _fromClient The address of the client from whom funds were collected.
    /// @param amount The amount of funds that were collected.
    event SupraCollected(address _fromClient, uint256 amount);

    /// @dev Emitted when Supra refunds funds to a client's account.
    /// @param _toClient The address of the client to whom funds were refunded.
    /// @param amount The amount of funds that were refunded.
    event SupraRefunded(address _toClient, uint256 amount);

    /// @dev Emitted when the approver confirms the new cold wallet address
    /// @param _coldWalletAddress The new address of the cold wallet
    event ColdWalletConfirmed(address _coldWalletAddress);

    /// @dev Emitted when the client set the minimum balance limit to hold in wallet
    /// @param  _clientAddress The client wallet address through which and for which the limit is to be set
    /// @param limit The value which client wants to be a minimum limit for the specified wallet
    event MinBalanceClientSet(address _clientAddress, uint256 limit);

    /// @dev Constructor to initialize contract with provided parameters.
    /// @param _approver Address of the approver who will approve changes.
    /// @param _developer Address of the developer who will manage the contract.
    /// @param _newGenerator Address of the new generator contract.
    /// @param _newRouter Address of the new router contract.
    /// @param _minBalanceLimitSupra Minimum balance limit to execute supra transactions.
    /// The value must be greater than or equal to zero.
    constructor(
        address _approver,
        address _developer,
        address _newGenerator,
        address _newRouter,
        uint256 _minBalanceLimitSupra
    ) {
        require(_approver != msg.sender, "Admin cannot be the approver");
        require(
            _developer != address(0) &&
            _approver != address(0) &&
            _newGenerator != address(0) &&
            _newRouter != address(0),
            "Address cannot be a zero address"
        );
        require(_minBalanceLimitSupra != 0, "Invalid Minimum balance limit");

        developer = _developer;
        approver = _approver;
        generator = _newGenerator;
        router = _newRouter;
        minBalanceLimitSupra = _minBalanceLimitSupra;
    }

    modifier checkClientWhitelisted(address _clientAddress) {
        require(
            isClientWhitelisted(_clientAddress),
            "Client address not whitelisted"
        );
        _;
    }

    /**
        #######################################################################################
            :::::::::::::::::::::::: SUPRA ADMIN OPERATIONS ::::::::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Allows SupraAdmin to add a client to the whitelist.
    /// @param _clientAddress The address of the client being added.
    /// @param _isSnap A boolean value indicating whether the client is a Snap user or not.
    function addClientToWhitelist(address _clientAddress, bool _isSnap)
    external
    onlyOwner
    {
        require(
            !isClientWhitelisted(_clientAddress),
            "Client is already whitelisted"
        );
        whitelist.add(_clientAddress);
        addSubscriptionInfoByClient(
            _clientAddress,
            block.timestamp,
            block.timestamp + SUBSCRIPTION_DURATION,
            _isSnap
        );
        emit ClientWhitelisted(
            _clientAddress,
            block.timestamp,
            block.timestamp + SUBSCRIPTION_DURATION,
            _isSnap
        );
    }

    /// @dev Update the end time of a client's subscription
    /// @param _clientAddress The address of the client
    /// @param _newEndTime The new end time for the subscription
    /// - require The client is whitelisted
    /// - require The new end time is in the future
    function updateSubscription(address _clientAddress, uint256 _newEndTime)
    external
    onlyOwner
    checkClientWhitelisted(_clientAddress)
    {
        require(_newEndTime > block.timestamp + 2 days, "Invalid End Time");
        subscriptionPeriod[_clientAddress].endDate = _newEndTime;
    }

    /// @dev Remove a client from the whitelist
    /// @param _clientAddress The address of the client to remove
    function removeClientFromWhitelist(address _clientAddress)
    external
    onlyOwner
    checkClientWhitelisted(_clientAddress)
    {
        uint256 _amount = checkClientFund(_clientAddress);
        (bool sent, bytes memory data) = payable(_clientAddress).call{
                value: _amount
            }("");
        require(sent, "Cannot transfer client funds");
        bool result = whitelist.remove(_clientAddress);
        require(result, "Client not whitelisted or already removed");
        emit ClientRemovedFromWhitelist(_clientAddress, block.timestamp);
    }

    /// @dev Remove all contracts associated with a client
    /// @param _clientAddress The address of the client
    function removeAllContractOfClient(address _clientAddress)
    external
    onlyOwner
    {
        uint256 _totalCount = contractWhitelisted[_clientAddress].length();
        require(
            _totalCount != 0,
            "Contracts not whitelisted or already removed"
        );
        contractWhitelisted[_clientAddress].clear();
    }

    /// @dev Allows the owner to claim free node expenses.
    /// Only the owner can do this.
    /// @param _amount The amount to be claimed to coldwallet.
    function claimFreeNodeExpenses(uint256 _amount) external onlyOwner {
        require(
            coldWallet != address(0),
            "Invalid Address: Address cannot be a zero address"
        );
        require(
            _amount <= supraFund,
            "Insufficient Funds: Claiming free node expenses"
        );
        supraFund -= _amount;
        (bool sent, bytes memory data) = payable(coldWallet).call{
                value: _amount
            }("");
        require(sent, "Cannot claim free node expenses");
    }

    /// @dev Execute a refund from the supra fund to a client
    /// @param _fundReceiver The address of the client receiving the refund
    /// @param _amount The amount to be refunded
    /// - require The client is whitelisted
    /// - require The refund amount is less than or equal to the supra fund
    function executeRefund(address _fundReceiver, uint256 _amount)
    external
    onlyOwner
    checkClientWhitelisted(_fundReceiver)
    {
        require(_amount <= supraFund, "Insufficient funds: executing refund");
        supraFund -= _amount;
        clientFund[_fundReceiver] = clientFund[_fundReceiver] + _amount;
        emit SupraRefunded(_fundReceiver, _amount);
    }

    /// @dev Updates the address of the developer.
    /// Only the owner is authorized to perform this action.
    /// @param _newDeveloper The address of the new developer to be set.
    function updateDeveloper(address _newDeveloper) external onlyOwner {
        require(
            _newDeveloper != address(0),
            "Developer address cannot be a zero address"
        );
        developer = _newDeveloper;
    }

    /// @dev Sets the minimum balance limit for SupraAdmin.
    /// @param _limit The new minimum balance limit for SupraAdmin.
    function updateMinBalanceSupra(uint256 _limit) external onlyOwner {
        minBalanceLimitSupra = _limit;
    }

    /// @dev Deposits ETH into the SupraFund contract.
    function depositSupraFund() external payable onlyOwner {
        supraFund += msg.value;
    }

    /// @dev Pauses withdrawals for the contract.
    /// Only the owner is authorized to perform this action.
    /// Emits a {Paused} event.
    function pauseWithdrawal() external onlyOwner {
        _pause();
    }

    /// @dev Resumes withdrawals for the contract.
    /// Only the owner is authorized to perform this action.
    /// Emits an {Unpaused} event.
    function unpauseWithdrawal() external onlyOwner {
        _unpause();
    }

    /// @dev Sets the generator contract address.
    /// Only the owner can do this.
    /// @param _newGenerator The new generator contract address.
    /// @param _newRouter The new router contract address
    function updateGeneratorRouter(address _newGenerator, address _newRouter)
    external
    onlyOwner
    {
        require(
            isContract(_newGenerator) && isContract(_newRouter),
            "Address cannot be a wallet address"
        );
        require(
            _newGenerator != address(0) && _newRouter != address(0),
            "Contract address cannot be a zero address"
        );
        generator = _newGenerator;
        router = _newRouter;
    }

    /**
        #######################################################################################
            :::::::::::::::::::::: WHITELISTED CLIENT OPERATIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Allows a client to add a contract to their whitelist.
    /// @param _contractAddress The address of the contract being added.
    function addContractToWhitelist(address _contractAddress)
    external
    checkClientWhitelisted(msg.sender)
    {
        require(isContract(_contractAddress), "Address cannot be EOA");
        bool result = contractWhitelisted[msg.sender].add(_contractAddress);
        require(result, "Contract is Already whitelisted");
        emit ContractWhitelisted(msg.sender, _contractAddress, block.timestamp);
    }

    /// @dev Removes a contract from a client's whitelist.
    /// Only the client who added the contract can remove it.
    /// @param _contractAddress The address of the contract to remove.
    function removeContractFromWhitelist(address _contractAddress) external {
        bool result = contractWhitelisted[msg.sender].remove(_contractAddress);
        require(result, "Contract is not whitelisted or already removed");
        emit ContractsDeletedFromWhitelist(_contractAddress, block.timestamp);
    }

    /// @dev Allows a client to deposit funds into their account.
    function depositFundClient()
    external
    payable
    checkClientWhitelisted(msg.sender)
    {
        clientFund[msg.sender] = clientFund[msg.sender] + msg.value;
        emit ClientDeposited(msg.sender, msg.value);
    }

    ///  @dev Sets the minimum balance limit for the calling client.
    ///  @param _limit The new minimum balance limit for the calling client.
    function setMinBalanceClient(uint256 _limit)
    external
    checkClientWhitelisted(msg.sender)
    {
        require(
            _limit >= minBalanceLimitSupra,
            "Cannot set a lower limit than the limit set by the deployer"
        );
        minBalanceLimitClient[msg.sender] = _limit;
        emit MinBalanceClientSet(msg.sender, _limit);
    }

    /// @dev Allows a client to withdraw their funds.
    /// @param _amount The amount to be withdrawn.
    /// Emits a ClientWithdrawal event.
    function withdrawFundClient(uint256 _amount) external whenNotPaused {
        require(
            _amount <= checkClientFund(msg.sender),
            "Insufficient Funds: Fund withdrawing by user"
        );
        clientFund[msg.sender] = checkClientFund(msg.sender) - _amount;
        (bool sent, bytes memory data) = payable(msg.sender).call{
                value: _amount
            }("");
        require(sent, "Cannot withdraw client funds");
        emit ClientWithdrwal(msg.sender, _amount);
    }

    /**
        #######################################################################################
            ::::::::::::::::::::::::: GENERATOR RELATED FUNCTIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Allows the generator contract to collect funds from a client's balance.
    /// @param _clientAddress The address of the client whose funds are being collected.
    /// @param _amount The amount of funds to be collected.
    function collectFund(address _clientAddress, uint256 _amount)
    external
    override
    {
        require(
            msg.sender == generator,
            "Caller is not the owner: Only Generator can collect funds"
        );
        require(
            _amount <= checkClientFund(_clientAddress),
            "Insufficient Funds: Collecting funds by generator"
        );
        clientFund[_clientAddress] = clientFund[_clientAddress] - _amount;
        supraFund = supraFund + _amount;
        emit SupraCollected(_clientAddress, _amount);
    }

    /// @dev Returns the fund balance of the specified client address.
    /// Only authorized callers, including the whitelisted client, developer, owner, and generator, can perform this action.
    /// @param _clientAddress The address of the client whose fund balance is to be checked.
    /// @return The fund balance of the specified client address.
    function checkClientFund(address _clientAddress)
    public
    view
    override
    checkClientWhitelisted(_clientAddress)
    returns (uint256)
    {
        address s = msg.sender;
        require(
            s == _clientAddress ||
            s == developer ||
            s == owner() ||
            s == generator ||
            s == router,
            "Unauthorized Access: Address not allowed to check funds"
        );
        return clientFund[_clientAddress];
    }

    /**
        #######################################################################################
            :::::::::::::::::::::: ROUTER RELATED FUNCTIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Returns a boolean indicating whether the given client and contract addresses are eligible for interaction.
    /// @param _clientAddress The address of the client.
    /// @param _contractAddress The address of the contract.
    /// @return A boolean indicating whether the given client and contract addresses are eligible for interaction.
    function isContractEligible(
        address _clientAddress,
        address _contractAddress
    )
    public
    view
    override
    checkClientWhitelisted(_clientAddress)
    returns (bool)
    {
        return (isContractWhitelisted(_clientAddress, _contractAddress));
    }

    /// @dev Checks whether the minimum balance for a given client address has been reached.
    /// @param _clientAddress The client address to check.
    /// @return A boolean indicating whether the minimum balance for the given client address has been reached.
    function isMinimumBalanceReached(address _clientAddress)
    public
    view
    override
    checkClientWhitelisted(_clientAddress)
    returns (bool)
    {
        address s = msg.sender;
        require(
            s == _clientAddress ||
            s == developer ||
            s == owner() ||
            s == generator ||
            s == router,
            "Unauthorized Access: Address cannot check minimum balance"
        );
        return (checkClientFund(_clientAddress) <=
        checkMinBalance(_clientAddress));
    }

    /**
        #######################################################################################
            :::::::::::::::::::::: ADMIN + APPROVER OPERATIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Propose a new cold wallet address
    /// @param _newColdWallet The address of the new cold wallet
    function proposeColdWallet(address _newColdWallet) external onlyOwner {
        _tempWallet = _newColdWallet;
        adminFeelsOK = true;
    }

    /// @dev Confirm a proposed cold wallet address
    /// @notice This function can only be executed by the approver
    /// @notice The proposal must be confirmed by the owner before the cold wallet can be updated
    /// - require The proposal is ready to be confirmed
    function confirmColdWallet() external {
        require(
            msg.sender == approver,
            "Unauthorized Access: Only Approver can confirm"
        );
        require(adminFeelsOK, "Cold wallet propose not ready");
        coldWallet = _tempWallet;
        adminFeelsOK = false;
        emit ColdWalletConfirmed(coldWallet);
    }

    /**
        #######################################################################################
            :::::::::::::::::::::: CRON AND SCRIPT RELATED FUNCTIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Returns an array of whitelisted client addresses along with their respective fund balances and minimum balance requirements.
    /// @return A tuple of three arrays: (1) an array of whitelisted client addresses, (2) an array of their fund balances, and (3) an array of their minimum balance requirements.
    function checkBalanceAllWhitelisted()
    external
    view
    returns (
        address[] memory,
        uint256[] memory,
        uint256[] memory
    )
    {
        address s = msg.sender;
        require(
            s == developer || s == owner(),
            "Unauthorized Access: Only developer or deployer can check the balance"
        );
        uint256 count = countTotalWhitelistedClient();
        address[] memory _clients = listAllWhitelistedClient();

        uint256[] memory _funds = new uint256[](count);
        uint256[] memory _minBalance = new uint256[](count);

        for (uint256 loop = 0; loop < count; loop++) {
            address client = _clients[loop];
            _funds[loop] = clientFund[client];
            _minBalance[loop] = checkMinBalance(client);
        }

        return (_clients, _funds, _minBalance);
    }

    /// @dev Returns the minimum balance limit for the SupraAdmin.
    /// @return The minimum balance limit for the SupraAdmin.
    function checkMinBalanceSupra() public view returns (uint256) {
        return minBalanceLimitSupra;
    }

    /// @dev Returns the total number of whitelisted clients.
    /// @return The total number of whitelisted clients.
    function countTotalWhitelistedClient() public view returns (uint256) {
        return whitelist.length();
    }

    /**
        #######################################################################################
            ::::::::::::::::::::::::: RESTRICTED VIEW FUNCTIONS ::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Returns the effective balance for a given client address.
    /// @param _clientAddress The client address to check.
    /// @return The effective balance for the given client address.
    function checkEffectiveBalance(address _clientAddress)
    public
    view
    checkClientWhitelisted(_clientAddress)
    returns (uint256)
    {
        address s = msg.sender;
        require(
            s == _clientAddress || s == developer || s == owner(),
            "Unauthorized Access: Cannot check effective balance"
        );
        uint256 balance;
        if (checkClientFund(_clientAddress) > checkMinBalance(_clientAddress)) {
            balance =
            checkClientFund(_clientAddress) -
            checkMinBalance(_clientAddress);
        }
        return balance;
    }

    /// @dev Returns the number of contracts whitelisted by a client.
    /// @param _clientAddress The client address to check.
    /// @return The number of contracts whitelisted by the client.
    function countTotalWhitelistedContractByClient(address _clientAddress)
    public
    view
    checkClientWhitelisted(_clientAddress)
    returns (uint256)
    {
        address s = msg.sender;
        require(
            s == _clientAddress ||
            s == developer ||
            s == owner() ||
            s == generator,
            "Unauthorized Access: Cannot check the total count"
        );
        return contractWhitelisted[_clientAddress].length();
    }

    /// @dev Get subscription information for a client.
    /// @param _clientAddress The client's address.
    /// @return A tuple containing the start timestamp and the end timestamp of the subscription period.
    function getSubscriptionInfoByClient(address _clientAddress)
    external
    view
    checkClientWhitelisted(_clientAddress)
    returns (
        uint256,
        uint256,
        bool
    )
    {
        address s = msg.sender;
        require(
            s == _clientAddress ||
            s == developer ||
            s == owner() ||
            s == generator,
            "Unauthorized Access: Cannot check the subscription info"
        );
        Subscription memory subscription = subscriptionPeriod[_clientAddress];
        return (
        subscription.startDate,
        subscription.endDate,
        subscription.isSnap
        );
    }

    /// @dev Check if a contract is whitelisted for a client
    /// @param _clientAddress The address of the client
    /// @param _contractAddress The address of the contract to check
    /// @return A boolean indicating whether the contract is whitelisted
    function isContractWhitelisted(
        address _clientAddress,
        address _contractAddress
    ) public view checkClientWhitelisted(_clientAddress) returns (bool) {
        address s = msg.sender;
        require(
            s == _clientAddress ||
            s == developer ||
            s == owner() ||
            s == generator ||
            s == router,
            "Unauthorized Access: Cannot check for the whitelisted contract"
        );
        return contractWhitelisted[_clientAddress].contains(_contractAddress);
    }

    /// @dev Returns an array of all whitelisted contracts for a specified client address.
    /// Only authorized callers, including the whitelisted client, developer, and owner, can perform this action.
    /// @param _clientAddress The address of the client whose whitelisted contracts are to be listed.
    /// @return An array of all whitelisted contracts for the specified client address.
    function listAllWhitelistedContractByClient(address _clientAddress)
    external
    view
    checkClientWhitelisted(_clientAddress)
    returns (address[] memory)
    {
        address s = msg.sender;
        require(
            s == _clientAddress || s == developer || s == owner(),
            "Unauthorized Access: Cannot check the list of whitelisted contracts"
        );
        require(_clientAddress != address(0), "User address cannot be zero");
        uint256 totalContracts = contractWhitelisted[_clientAddress].length();

        address[] memory contracts = new address[](totalContracts);
        uint256 count = 0;
        for (count; count < totalContracts; count++) {
            address contractAddress = contractWhitelisted[_clientAddress].at(
                count
            );
            contracts[count] = contractAddress;
        }
        if (count == 0) {
            return new address[](0);
        }
        return contracts;
    }

    /// @dev Returns an array of all whitelisted clients.
    /// @return An array of all whitelisted client addresses.
    function listAllWhitelistedClient() public view returns (address[] memory) {
        address s = msg.sender;
        require(
            s == developer || s == owner(),
            "Unauthorized Access: Cannot list whitelisted clients"
        );
        address[] memory clients = new address[](whitelist.length());
        for (uint256 i = 0; i < whitelist.length(); i++) {
            address value = whitelist.at(i);
            clients[i] = value;
        }
        return clients;
    }

    /// @dev Returns the current balance of the SupraFund contract.
    /// @return The current balance of the SupraFund contract.
    function checkSupraFund() external view returns (uint256) {
        require(
            msg.sender == owner() || msg.sender == developer,
            "Unauthorized Access: Cannot check supra funds"
        );
        return supraFund;
    }

    /**
        #######################################################################################
               :::::::::::::::::::::::::: PUBLIC FUNCTIONS :::::::::::::::::::::::::
        #######################################################################################
    */

    /// @dev Checks if a client is whitelisted.
    /// @param _clientAddress The client address to check.
    /// @return True if the client is whitelisted, false otherwise.
    function isClientWhitelisted(address _clientAddress)
    public
    view
    returns (bool)
    {
        return whitelist.contains(_clientAddress);
    }

    /// @dev Returns the minimum balance limit for a given client address.
    /// @param _clientAddress The client address to check.
    /// @return The minimum balance limit for the given client address.
    function checkMinBalance(address _clientAddress)
    public
    view
    override
    returns (uint256)
    {
        uint256 limit;
        if (checkMinBalanceClient(_clientAddress) > checkMinBalanceSupra()) {
            limit = checkMinBalanceClient(_clientAddress);
        } else {
            limit = checkMinBalanceSupra();
        }
        return limit;
    }

    /**
        #######################################################################################
               :::::::::::::::::::::::: INETRNAL FUNCTIONS ::::::::::::::::::::::::
        #######################################################################################
    */
    /// @dev Returns the minimum balance limit for a given client address.
    /// @param _clientAddress The client address to check.
    /// @return The minimum balance limit for the given client address.
    function checkMinBalanceClient(address _clientAddress)
    internal
    view
    returns (uint256)
    {
        return minBalanceLimitClient[_clientAddress];
    }

    /// @dev Add subscription information for a client
    /// @param _clientAddress The address of the client
    /// @param _start The start timestamp of the subscription
    /// @param _end The end timestamp of the subscription
    /// @param _isSnap A flag indicating whether the subscription is a snapshot subscription
    function addSubscriptionInfoByClient(
        address _clientAddress,
        uint256 _start,
        uint256 _end,
        bool _isSnap
    ) internal {
        subscriptionPeriod[_clientAddress] = Subscription(
            _start,
            _end,
            _isSnap
        );
    }
}

// File: src/EnumerableSet.sol



/**
 * ###############################################################
 *         this is not exact replica of OpenZepplin implementation
 *     ###############################################################
 */

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity 0.8.19;

library EnumerableSet {
    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * ###################################################################################
     *         :::: this is the new method added on top of openzepplin implementation ::::
     *     ###################################################################################
     */
    function _clear(Set storage set) private returns (bool) {
        for (uint256 i = 0; i < set._values.length; i++) {
            delete set._indexes[set._values[i]];
        }
        delete set._values;
        return true;
    }

    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    struct Bytes32Set {
        Set _inner;
    }

    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    struct AddressSet {
        Set _inner;
    }

    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    function clear(AddressSet storage set) internal returns (bool) {
        return _clear(set._inner);
    }

    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

// File: src/Ownable2Step.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)

pragma solidity 0.8.19;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}

// File: src/Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity 0.8.19;

import "./Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

// File: src/IDepositContract.sol



// INSTRUCTIONS : Contains methods that will be used by the ROUTER and GENERATOR contracts.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IDepositContract {
    function isContractEligible(address _clientAddress, address _contractAddress) external view returns (bool);

    function isMinimumBalanceReached(address _clientAddress) external view returns (bool);

    function checkMinBalance(address _clientAddress) external view returns (uint256);

    function checkClientFund(address _clientAddress) external view returns (uint256);

    function collectFund(address _clientAddress, uint256 _amount) external;
}

// File: src/CheckContractAddress.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract CheckContractAddress {
    /// @dev Returns a boolean indicating whether the given address is a contract or not.
    /// @param _addr The address to be checked.
    /// @return A boolean indicating whether the given address is a contract or not.
    function isContract(address _addr) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(_addr)
        }
        return size > 0;
    }
}

// File: src/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity 0.8.19;

import "./Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: src/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity 0.8.19;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}