// Contract: FAMCO
// Target function: function tokenURI(uint256 tokenId) public view override returns (string memory)
8 multiplier;
    }

    // cost for minting NFT
    mapping ( uint8 => TokenRarity ) public tokenRarity;

    // Enable Trading
    bool public tradingEnabled = false;

    // Toggle URI based on rarity vs tokenID
    bool public UriByRarity = true;

    // Mint Token
    address public mintToken = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address public rewardToken = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address public mintRecipient;

    ////////////////////////////////////////////////
    ///////////     OWNER FUNCTIONS      ///////////
    ////////////////////////////////////////////////

    function __init__(address mintRecipient_) external onlyOwner {
        require(
            mintRecipient_ != address(0),
            'Zero Addr'
        );
        tokenRarity[0].cost = 500 * 10**6;
        tokenRarity[1].cost = 1000 * 10**6;
        tokenRarity[2].cost = 2000 * 10**6;
        tokenRarity[3].cost = 5000 * 10**6;
        tokenRarity[0].multiplier = 1;
        tokenRarity[1].multiplier = 2;
        tokenRarity[2].multiplier = 4;
        tokenRarity[3].multiplier = 10;
        mintRecipient = mintRecipient_;
        canGiveRewards[mintRecipient_] = true;
    }

    function resumeTrading() external onlyOwner {
        tradingEnabled = true;
    }

    function pauseTrading() external onlyOwner {
        tradingEnabled = false;
    }

    function withdraw() external onlyOwner {
        (bool s,) = payable(msg.sender).call{value: address(this).balance}("");
        require(s);
    }

    function withdrawToken(address token_) external onlyOwner {
        require(token_ != address(0), 'Zero Address');
        IERC20(token_).transfer(msg.sender, IERC20(token_).balanceOf(address(this)));
    }

    function setBaseURI(string calldata newURI) external onlyOwner {
        baseURI = newURI;
    }

    function setURIExtention(string calldata newExtention) external onlyOwner {
        ending = newExtention;
    }

    function setMintToken(address newMintToken) external onlyOwner {
        mintToken = newMintToken;
    }

    function setMintRecipient(address mintRecipient_) external onlyOwner {
        mintRecipient = mintRecipient_;
    }

    function setRewardToken(address rewardToken_) external onlyOwner {
        rewardToken = rewardToken_;
    }

    function setRarityCost(uint8 rarity, uint256 cost) external onlyOwner {
        require(tokenRarity[rarity].multiplier > 0, 'Multiplier Not Set');
        tokenRarity[rarity].cost = cost;
    }

    function setRarityMultiplier(uint8 rarity, uint8 multiplier) external onlyOwner {
        require(tokenRarity[rarity].multiplier == 0, 'Multiplier Already Set');
        tokenRarity[rarity].multiplier = multiplier;
    }

    function setCanGiveRewards(address account, bool canGive) external onlyOwner {
        canGiveRewards[account] = canGive;
    }

    function setURIByRarity(bool isUriBasedOnRarity) external onlyOwner {
        UriByRarity = isUriBasedOnRarity;
    }

    /**
        Example:
        [ 0x123...abc, 0x456...def ]
        [ 5, 10 ]
        [ 3, 0 ]
        
        Gives the result:
        0x123...abc gets 5 Platinum NFTs
        0x456...def gets 10 Bronze NFTs

     */
    function ownerMint(address[] calldata tos, uint256[] calldata nums, uint8[] calldata raritys) external onlyOwner {
        
        uint addrLen = tos.length;
        require(addrLen == nums.length && nums.length == raritys.length, 'Invalid Lengths');
        
        for (uint i = 0; i < addrLen;) {
            for (uint j = 0; j < nums[i];) {
                _safeMint(tos[i], currentSupplyIndex, raritys[i]);
                unchecked { ++j; }
            }
            unchecked { ++i; }
        }
    }


    ////////////////////////////////////////////////
    ///////////     PUBLIC FUNCTIONS     ///////////
    ////////////////////////////////////////////////


    /** 
     * Mints `numberOfMints` NFTs To Caller
     */
    function mint(address to, uint256 num, uint8 rarity) external {
        require(
            tradingEnabled,
            'Trading Not Enabled'
        );
        require(
            num > 0, 
            'Invalid Number'
        );
        require(
            tokenRarity[rarity].cost > 0 && tokenRarity[rarity].multiplier > 0,
            'Invalid Rarity'
        );
        
        // determine cost in USDC
        uint256 cost = tokenRarity[rarity].cost * num;

        // ensure user has balance and approval of this amount
        require(
            IERC20(mintToken).balanceOf(msg.sender) >= cost,
            'Insufficient USDC Balance'
        );
        require(
            IERC20(mintToken).allowance(msg.sender, address(this)) >= cost,
            'Insufficient Allowance'
        );
        require(
            IERC20(mintToken).transferFrom(msg.sender, mintRecipient, cost),
            'Error Transfer From'
        );

        for (uint i = 0; i < num;) {
            _safeMint(to, currentSupplyIndex, rarity);
            unchecked { ++i; }
        }
    }

    function claimRewards() external nonReentrant {
        require(
            _balances[msg.sender] > 0,
            'Zero Balance'
        );
        _claimRewards(msg.sender);
    }


    function burn(uint256 tokenID) external nonReentrant {
        require(_isApprovedOrOwner(_msgSender(), tokenID), "caller not owner nor approved");
        _claimRewards(_owners[tokenID]);
        _burn(tokenID);
    }

    function giveRewards(uint256 amount) external nonReentrant {
        require(
            IERC20(rewardToken).balanceOf(msg.sender) >= amount,
            'Insufficient Balance'
        );
        require(
            IERC20(rewardToken).allowance(msg.sender, address(this)) >= amount,
            'Insufficient BTC Allowance'
        );
        require(
            IERC20(rewardToken).transferFrom(msg.sender, address(this), amount),
            'Failure Transfer From'
        );

        // give rewards
        unchecked {
            dividendsPerShare += ( amount * PRECISION ) / totalShares;
            totalRewards += amount;
        }        
    }

    receive() external payable {}

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public override {
        address wpowner = ownerOf(tokenId);
        require(to != wpowner, "ERC721: approval to current owner");

        require(
            _msgSender() == wpowner || isApprovedForAll(wpowner, _msgSender()),
            "ERC721: not approved or owner"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address _operator, bool approved) public override {
        _setApprovalForAll(_msgSender(), _operator, approved);
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "caller not owner nor approved");
        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "caller not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }



    ////////////////////////////////////////////////
    ///////////    INTERNAL FUNCTIONS    ///////////
    ////////////////////////////////////////////////


    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(
        address to,
        uint256 tokenId,
        uint8 rarity
    ) internal {
        _mint(to, tokenId, rarity);
        require(
            _checkOnERC721Received(address(0), to, tokenId, ""),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId, uint8 rarity) internal {
        require(!_exists(tokenId), "ERC721: token already minted");

        _owners[tokenId] = to;
        tokenIDToRarity[tokenId] = rarity;
        unchecked {
            currentSupplyIndex++;
            _totalSupply++;
            _balances[to]++;
            totalShares += tokenRarity[rarity].multiplier;
            ownerRewards[to].share += tokenRarity[rarity].multiplier;
        }

        ownerRewards[to].totalExcluded = getCumulativeDividends(ownerRewards[to].share);

        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `_data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: non ERC721Receiver implementer");
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal nonReentrant {
        require(ownerOf(tokenId) == from, "Incorrect owner");
        require(to != address(0), "zero address");
        require(balanceOf(from) > 0, 'Zero Balance');

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        // claim rewards for from
        _claimRewards(from);

        // if to has balance claim rewards for to
        if (_balances[to] > 0) {
            _claimRewards(to);
        }

        // Allocate balances
        _balances[from] = _balances[from].sub(1);
        _balances[to] += 1;
        _owners[tokenId] = to;

        // fetch multiplier based on rarity
        uint8 multiplier = tokenRarity[tokenIDToRarity[tokenId]].multiplier;

        // Allocate reward shares based on rarity
        ownerRewards[from].share -= multiplier;
        ownerRewards[to].share += multiplier;

        // reset reward allocations
        ownerRewards[from].totalExcluded = getCumulativeDividends(ownerRewards[from].share);
        ownerRewards[to].totalExcluded = getCumulativeDividends(ownerRewards[to].share);

        // emit transfer
        emit Transfer(from, to, tokenId);
    }

    /**
        Claims BNB Reward For User
     */
    function _claimRewards(address user) internal {

        // return if zero balance
        if (_balances[user] == 0) {
            return;
        }

        // fetch pending rewards
        uint pending = pendingRewards(user);
        uint rewardBalance = IERC20(rewardToken).balanceOf(address(this));
        if (pending > rewardBalance) {
            pending = rewardBalance;
        }

        // return if no rewards
        if (pending == 0) {
            return;
        }
        
        // reset total rewards
        ownerRewards[user].totalExcluded = getCumulativeDividends(ownerRewards[user].share);

        // transfer reward to user
        IERC20(rewardToken).transfer(user, pending);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        require(_exists(tokenId), 'Token Does Not Exist');

        // owner of token
        address owner_ = ownerOf(tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        // decrement balance
        _balances[owner_] -= 1;
        delete _owners[tokenId];

        // decrement total supply
        _totalSupply -= 1;

        // decrement multiplier from total shares
        uint256 multiplier = tokenRarity[tokenIDToRarity[tokenId]].multiplier;
        totalShares -= multiplier;
        ownerRewards[owner_].share -= multiplier;
        ownerRewards[owner_].totalExcluded = getCumulativeDividends(ownerRewards[owner_].share);

        // emit transfer
        emit Transfer(owner_, address(0), tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address wpowner,
        address _operator,
        bool approved
    ) internal {
        require(wpowner != _operator, "ERC721: approve to caller");
        _operatorApprovals[wpowner][_operator] = approved;
        emit ApprovalForAll(wpowner, _operator, approved);
    }



    ////////////////////////////////////////////////
    ///////////      READ FUNCTIONS      ///////////
    ////////////////////////////////////////////////

    function checkClaimEligibility(address _to, uint256 _quantity) external view returns (string memory) {
        if (tradingEnabled && _to != address(0) && _quantity > 0) {
            return "";
        }
        return "Sale is not live";
    }

    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }

    function getIDsByOwner(address owner_) external view returns (uint256[] memory) {
        uint256[] memory ids = new uint256[](balanceOf(owner_));
        if (balanceOf(owner_) == 0) return ids;
        uint256 count = 0;
        for (uint i = 0; i < currentSupplyIndex;) {
            if (_owners[i] == owner_) {
                ids[count] = i;
                unchecked { count++; }
            }
            unchecked { ++i; }
        }
        return ids;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address wpowner) public view override returns (uint256) {
        require(wpowner != address(0), "query for the zero address");
        return _balances[wpowner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view override returns (address) {
        address wpowner = _owners[tokenId];
        require(wpowner != address(0), "query for nonexistent token");
        return wpowner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public pure override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public pure override returns (string memory) {
        return _symbol;
    }