contract ERC721PremintBasic is ERC721Premint, IERC721Enumerable, Ownable {
    using Strings for uint256;

    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
    mapping(uint256 => uint256) private _ownedTokensIndex;
    string private _baseUri;

    // Storage: tokenURIs
    mapping(uint256 => string) private _tokenURIs;
    

    constructor(string memory name_, string memory symbol_, address premintOwner_) 
                ERC721Premint(name_, symbol_, premintOwner_) {}

    /**
     * @dev See {ERC721Premint}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721Enumerable).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    function canMint(address to, uint256 count) public view virtual returns (bool) {
        require(to != address(0), "Recipient is required");  
        require(count > 0, "Mint: Count must be 1 or more");
        return true;
    }

    function canPremint(uint256 count) public view virtual returns (bool) {
        require(count > 0, "Premint: Count must be 1 or more");
        return true;
    }

    function mintTo(address to, uint256 count) public onlyOwner {  
        require(canMint(to, count));
        
        for(uint256 i = 0; i < count; i++){
            _safeMint(to, totalSupply() + 1);
        }
    }

    function premint(uint256 count) public virtual onlyOwner {
        require(canPremint(count));
        _premint(count);
    }

    function burn(uint256 tokenId) public virtual {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
        _burn(tokenId);
    }

    function _burn(uint256 tokenId) internal virtual override {
        super._burn(tokenId);

        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }
    }

    /**
     * @dev See {IERC721Enumerable}.
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply();
    }

    /**
     * @dev See {IERC721Enumerable}.
     */
    function tokenOfOwnerByIndex(address owner_, uint256 index) external view override returns (uint256 tokenId) {
        require(index < balanceOf(owner_), "ERC721Enumerable: owner index out of bounds");
        if (_isPremintOwner(owner_)){
            //
            // IMPORTANT !!! Calling with index of preminted tokens throws an error!
            //
            require(index < originBalanceOf(owner_), "Premint: Premint Owner index out of bounds");
        } 
        return _ownedTokens[owner_][index];
    }

    /**
     * @dev See {IERC721Enumerable}.
     * 
     * Important!!! Without throw error when token by index is burned!
     */
    function tokenByIndex(uint256 index) external view override returns (uint256) {
        require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
        return index + 1;
    }

    /**
     * @dev {Storage}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

        string memory base = _baseURI();
        string memory _tokenURI = _tokenURIs[tokenId];

        if (bytes(_tokenURI).length > 0) {
            return _tokenURI;
        }

        return bytes(base).length > 0 ? string(abi.encodePacked(base, tokenId.toString())) : ""; 
    }

    /**
     * @dev {Storage}.
     */
    function baseURI() public view virtual returns (string memory) {
        return _baseUri;
    }
    function setBaseURI(string memory uri) public virtual onlyOwner {
        _baseUri = uri;
    }
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseUri;
    }

    /**
     * @dev {Storage}.
     */
    function setTokenURI(uint256 tokenId, string memory _tokenURI) public virtual onlyOwner {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }


    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            // do nothing
        } else if (from != to && !isPremintedToken(tokenId)) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            // do nothing
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721Premint.originBalanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }   

    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721Premint.originBalanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
}
/**
 *  ERC721Premint with sequential token indexes and others features.
 */
function setTokenURI(uint256 tokenId, string memory _tokenURI) public virtual onlyOwner {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }
/**
 * @dev {Storage}.
 */