contract ERC721P is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable {
    using Address for address;
    using Strings for uint256;

    uint16 private currentIndex;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to ownership details
    mapping(uint256 => address) private _ownerships;

    // Mapping owner address to tokens
    mapping(address => uint16[]) private _addressTokens;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    constructor(
        string memory name_,
        string memory symbol_
    ) {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index) public view override returns (uint256) {
        require(index > 0 && index <= totalSupply(), "ERC721P: global index out of bounds");
        return index;
    }

    /**
     * @dev not part of standard IERC721Enumerable
     * This function returns all the tokens of a certain owner as an array
     * and is very cheap to use on-chain
     */
    
    function tokensOfOwner(address owner)
    public
    view
    returns (uint16[] memory)
    {
        return _addressTokens[owner];
    }
    

    /**
     * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index)
    public
    view
    override
    returns (uint256)
    {
        require(index < balanceOf(owner), "ERC721P: owner index out of bounds");
        return _addressTokens[owner][index];
    }

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

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view override returns (uint256) {
        require(owner != address(0), "ERC721P: balance query for the zero address");
        return _addressTokens[owner].length;
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view override returns (address) {
        address owner = _ownerships[tokenId];
        require(owner != address(0), "ERC721P: owner query for nonexistent token");

        return _ownerships[tokenId];
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
    {
        require(
            ownerOf(tokenId) != address(0),
            "ERC721Metadata: URI query for nonexistent token"
        );

        string memory baseURI = _baseURI();
        return
        bytes(baseURI).length > 0
        ? string(abi.encodePacked(baseURI, tokenId.toString()))
        : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overriden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`)
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _ownerships[tokenId] != address(0);
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public override {
        address owner = _ownerships[tokenId];
        require(to != owner, "ERC721P: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721P: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId, owner);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view override returns (address) {
        require(_exists(tokenId), "ERC721P: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public override {
        require(operator != _msgSender(), "ERC721P: approve to caller");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator)
    public
    view
    virtual
    override
    returns (bool)
    {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public override {
        _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 {
        _transfer(from, to, tokenId);
        require(
            _checkOnERC721Received(from, to, tokenId, _data),
            "ERC721P: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     * 
     * Doesn't check if the target can receive ERC721's.
     * Requirements:
     *
     * - `to` cannot be the zero address
     *
     * Emits a {Transfer} event.
     */
    function _mint(
        address to,
        uint256 quantity
    ) internal {
        require(to != address(0), "ERC721P: mint to the zero address");

        _beforeTokenTransfers(address(0), to, currentIndex + 1, quantity);

        for (uint256 i = 0; i < quantity; i++) {
            currentIndex++;
            _addressTokens[to].push(currentIndex);
            _ownerships[currentIndex] = to;
            emit Transfer(address(0), to, currentIndex);
        }

        _afterTokenTransfers(address(0), to, currentIndex, quantity);
    }

    function _safeMint(address to, uint256 quantity) internal {
        _safeMint(to, quantity, "");
    }

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(
        address to,
        uint256 quantity,
        bytes memory _data
    ) internal {
        require(to != address(0), "ERC721P: mint to the zero address");

        _beforeTokenTransfers(address(0), to, currentIndex + 1, quantity);

        for (uint256 i = 0; i < quantity; i++) {
            currentIndex++;
            _addressTokens[to].push(currentIndex);
            _ownerships[currentIndex] = to;
            emit Transfer(address(0), to, currentIndex);
            require(
                _checkOnERC721Received(address(0), to, currentIndex, _data),
                "ERC721P: transfer to non ERC721Receiver implementer"
            );
        }

        _afterTokenTransfers(address(0), to, currentIndex, quantity);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * 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
    ) private {
        address prevOwner = ownerOf(tokenId);

        require(
            prevOwner == from,
            "ERC721P: transfer from incorrect owner"
        );

        bool isApprovedOrOwner = (_msgSender() == prevOwner ||
            getApproved(tokenId) == _msgSender() ||
            isApprovedForAll(prevOwner, _msgSender()));

        require(
            isApprovedOrOwner,
            "ERC721P: transfer caller is not owner nor approved"
        );

        require(to != address(0), "ERC721P: transfer to the zero address");

        _beforeTokenTransfers(from, to, tokenId, 1);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId, prevOwner);

        // Deletes the tokenId from the current owner
        _findAndDelete(from, uint16(tokenId)); 
        // Adds the tokenId to the new owner
        _addressTokens[to].push(uint16(tokenId));
        _ownerships[tokenId] = to;

        emit Transfer(from, to, tokenId);
        _afterTokenTransfers(from, to, tokenId, 1);
    }

    /**
     * @dev Deletes `tokenId` from the `owner` tokens array.
     *
     * Doesn't preserve order of tokens.
     */
    function _findAndDelete(address owner, uint16 tokenId) internal {
        for (uint i=0; i < _addressTokens[owner].length - 1; i++){
            if (_addressTokens[owner][i] == tokenId){
                _addressTokens[owner][i] = _addressTokens[owner][_addressTokens[owner].length - 1];
                _addressTokens[owner].pop();
                return;
            }
        }
        _addressTokens[owner].pop();
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits a {Approval} event.
     */
    function _approve(
        address to,
        uint256 tokenId,
        address owner
    ) private {
        _tokenApprovals[tokenId] = to;
        emit Approval(owner, to, tokenId);
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param _data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try
            IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data)
            returns (bytes4 retval) {
                return retval == IERC721Receiver(to).onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721P: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting.
     *
     * startTokenId - the first token id to be transferred
     * quantity - the amount to be transferred
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     */
    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}

    /**
     * @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes
     * minting.
     *
     * startTokenId - the first token id to be transferred
     * quantity - the amount to be transferred
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero.
     * - `from` and `to` are never both zero.
     */
    function _afterTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}
}
/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata and Enumerable extension. Built to optimize for lower gas during batch mints.
 *
 * The contract is fully ERC621 Enumerable compatible, including cheap on-chain enumeration.
 * The enumeration is optimized by using arrays of uint16 for efficient packing.
 * As limitation, the maximum NFT supply can be max(uint16) tokens.
 *
 * Assumes serials are sequentially minted starting at 1 (e.g. 1, 2, 3..).
 *
 * Does not support burning tokens to address(0).
 */
function safeTransferFrom(
    address from,
    address to,
    uint256 tokenId,
    bytes memory _data
) public override {
    _transfer(from, to, tokenId);
    require(
        _checkOnERC721Received(from, to, tokenId, _data),
        "ERC721P: transfer to non ERC721Receiver implementer"
    );
}

/**
 * @dev See {IERC721-safeTransferFrom}.
 */