// Contract: PET721
// Target function: function fetchUserInventory(address wallet) public view returns (string memory)
ely transfers `tokenId` token from `from` to `to`.
       - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
      * - `tokenId` token must exist and be owned by `from`.
      * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
      * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
      *
      * Emits a {Transfer} event.
      */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }
    
    /**
     * @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.
     * - `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 virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to 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 virtual {
        require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _holderTokens[from].remove(tokenId);
        _holderTokens[to].add(tokenId);

        _tokenOwners.set(tokenId, to);

        emit Transfer(from, to, tokenId);
    }

    /**
     * @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`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view returns (bool) {
        return _tokenOwners.contains(tokenId);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @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, bytes memory _data) internal virtual {
        _mint(to, tokenId);
        require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }
    
    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     * - 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`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { }
    
    /**
     * @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()) {
            return true;
        }
        bytes memory returndata = to.functionCall(abi.encodeWithSelector(
            IERC721Receiver(to).onERC721Received.selector,
            _msgSender(),
            from,
            tokenId,
            _data
        ), "ERC721: transfer to non ERC721Receiver implementer");
        bytes4 retval = abi.decode(returndata, (bytes4));
        return (retval == IERC721Receiver.onERC721Received.selector);
    }
    
    /**
     * @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) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _holderTokens[to].add(tokenId);

        _tokenOwners.set(tokenId, to);

        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
     * Requirements:
     * - `tokenId` must exist.
     */
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }
    
    /**
     * @dev Mint NFTs. Only the owner can call it.
     */
    function mintTokenByETH( uint8 _mrId, uint256 _count) public payable{    
        
        require(_count >0);
        // require(_count <= 10, "exceeded the mintable count of purchases in once.");
        require((mrCount[_mrId] + _count) <= mrInfos[_mrId].maxCount, "exceeded the maximum mintable count of purchases.");
        require(msg.value == mrInfos[_mrId].mrPrice * _count, "exceeded the balance.");
        require(mrInfos[_mrId].currency == 1);
        uint256 i;
        uint256 newId;
        for(i = 0; i < _count; i++)
        {
        
            newId = _tokenIds.current();
            _tokenIds.increment();
            mrIds[newId] = _mrId;
            
            mrCount[_mrId] = mrCount[_mrId].add(1);
            
            _mint(msg.sender, newId);
            
            _setTokenURI(newId, mrInfos[_mrId].mrMetaDataURI);
            
            ownedNftPrice[newId] = mrInfos[_mrId].mrPrice;
            
            ownedNftState[newId] = 0;
        }
    }

     function mintTokenByOnlyByAdmin(address _to, uint8 _mrId, uint256 _count) public onlyOwner{    
        
        require(_count >0);
        // require(_count <= 10, "exceeded the mintable count of purchases in once.");
        require((mrCount[_mrId] + _count) <= mrInfos[_mrId].maxCount, "exceeded the maximum mintable count of purchases.");
        require(mrInfos[_mrId].currency == 1);
        uint256 i;
        uint256 newId;
        for(i = 0; i < _count; i++)
        {
        
            newId = _tokenIds.current();
            _tokenIds.increment();
            mrIds[newId] = _mrId;
            
            mrCount[_mrId] = mrCount[_mrId].add(1);
            
            _mint(_to, newId);
            
            _setTokenURI(newId, mrInfos[_mrId].mrMetaDataURI);
            
            ownedNftPrice[newId] = mrInfos[_mrId].mrPrice;
            
            ownedNftState[newId] = 0;
        }
    }
    
    // buy item from MarketPlace
    function buyItemFromMarketPlace(uint256 tokenId) public payable{
        require(ownedNftState[tokenId] == 1);
        uint256 price = getUserMrPrice(tokenId);
        require(price > 0);
        require(msg.value >= price);
        
        address ownerofToken = ownerOf(tokenId);
        
        uint256 fee = msg.value * 3 / 1000;
        IERC721(address(this)).transferFrom(ownerofToken, msg.sender, tokenId);
        payable(ownerofToken).transfer(msg.value - fee);
        ownedNftState[tokenId] = 0;
    }
    
    /**
     * @dev create new NFT MR item. Only the owner can call it.
     */
    function mintMrId(string memory _mrName, uint256 _mrRate, uint256 _mrPrice, 
        string memory _mrMetaDataURI, uint256 _maxCount) public onlyOwner returns (uint8){
            
        require(_mrIdKindCount.current() < 256, "impossible to create a new kind of nft item any more.");
        uint8 newMrId = uint8(_mrIdKindCount.current());
        _mrIdKindCount.increment();
        
        MRinfo memory mrInfo;

        mrInfo = MRinfo(newMrId, _mrName, _mrRate, _mrPrice, _mrMetaDataURI, _maxCount, 1);
        mrInfos[newMrId] = mrInfo;
        mrInfoIdList.push(mrInfos[newMrId].mrName);
        return newMrId;
    }


    function getMrInfoIdList(uint256 index) public view returns (string memory) {
        return mrInfoIdList[index];
    }
    /**
     * @dev Get mrId for a specific tokenId.
     */
    function getMrId(uint256 _tokenId) public view returns (uint8) {
        require(_exists(_tokenId), "nonexistent token");
        return mrIds[_tokenId];
    }

    /**
     * @dev Get the associated mrName for a specific mrId.
     */
    function getMrName(uint8 _mrId) public view returns (string memory){
        return mrInfos[_mrId].mrName;
    }
    
    /**
     * @dev Set a unique name for each mrId. It is supposed to be called once.
     */
    function setMrName(uint8 mrId_, string memory name_) public onlyOwner {
        mrInfos[mrId_].mrName = name_;
    }
    
     /**
     * @dev Get the associated rate for a specific mrId.
     */
    function getMrRate(uint8 _mrId) public view returns (uint256){
        return mrInfos[_mrId].mrRate;
    }
    
    /**
     * @dev Set a rate for each mrId. It is supposed to be called once.
     */
    function setMrRate(uint8 mrId_, uint256 _rate) public onlyOwner {
        mrInfos[mrId_].mrRate = _rate;
    }
    
    /**
     * @dev Get the associated price for a specific mrId.
     */
    function getMrPrice(uint8 _mrId) public view returns (uint256){
        return mrInfos[_mrId].mrPrice;
    }
    
    /**
     * @dev Set a price for each mrId. It is supposed to be called once.
     */
    function setMrPrice(uint8 mrId_, uint256 price_) public onlyOwner {
        mrInfos[mrId_].mrPrice = price_;
    }

    /**
     * @dev Get the associated mrName for a unique tokenId.
     */
    function getMrNameOfTokenId(uint256 _tokenId) public view returns (string memory) {
        require(_exists(_tokenId), "nonexistent token");
        return mrInfos[mrIds[_tokenId]].mrName;
    }
    
    function getMrItemKindCount() public view returns (uint256){
        return _mrIdKindCount.current();
    }
    
    /**
     * @dev Get a MetaDataURI for each mrId. It is supposed to be called once.
     */
    function getMrMetaDataURI(uint8 mrId_) public view returns (string memory) {
        string memory MetaData =mrInfos[mrId_].mrMetaDataURI;
        return MetaData;
    }
    
    /**
     * @dev Set a MetaDataURI for each mrId. It is supposed to be called once.
     */
    function setMrMetaDataURI(uint8 mrId_, string memory _MetaData) public onlyOwner {
        mrInfos[mrId_].mrMetaDataURI = _MetaData;
    }
    
    /**
     * @dev Get a minted token count for each mrId. It is supposed to be called once.
     */
    function getMrMintedTokenCount(uint8 mrId_) public view returns (uint256) {
        return mrCount[mrId_];
    }
    
    /**
     * @dev Get a initial mintable max token count for each mrId. It is supposed to be called once.
     */
    function getMrMaxTokenCount(uint8 mrId_) public view returns (uint256) {
        return mrInfos[mrId_].maxCount;
    }
    
    /**
     * @dev Set a maxCount for each mrId. It is supposed to be called once.
     */
    function setMrMaxCount(uint8 mrId_, uint256 _maxCount) public onlyOwner {
        mrInfos[mrId_].maxCount = _maxCount;
    }
    
    /**
     * @dev Get a maxCount for each mrId. It is supposed to be called once.
     */
    function getMrMaxCount(uint8 mrId_) public view returns (uint256) {
        return mrInfos[mrId_].maxCount;
    }
    
    /**
     * @dev Set a maxCount for each mrId. It is supposed to be called once. 1:BNB, 2:LPLT
     */
    function setMrCurrencyKind(uint8 mrId_, uint8 _currency) public onlyOwner {
        _currency = _currency % 3;
        if (_currency == 0)
            _currency = 1;
        mrInfos[mrId_].currency = _currency;
    }
    
    /**
     * @dev Get a maxCount for each mrId. It is supposed to be called once.
     */
    function getMrCurrencyKind(uint8 mrId_) public view returns (uint8) {
        return mrInfos[mrId_].currency;
    }
    
    /**
     * @dev Get a price for _tokenId. It is supposed to be called once.
     */
    function getUserMrPrice(uint256 _tokenId) public view returns (uint256) {
        require(_exists(_tokenId), "nonexistent token");
        return ownedNftPrice[_tokenId];
    }
    
    /**
     * @dev Set a price for _tokenId. It is supposed to be called once.
     */
    function setUserMrPrice(uint256 _tokenId, uint256 _amount) public  {
        require(msg.sender == ownerOf(_tokenId));
        require(_exists(_tokenId), "nonexistent token");
        ownedNftPrice[_tokenId] = _amount;
    }
    
    /**
     * @dev Get a salable state for _tokenId. It is supposed to be called once. true: salable, false: not salable
     */
    function getUserNftSellState(uint256 _tokenId) public view returns (uint8){
        require(_exists(_tokenId), "nonexistent token");
        return ownedNftState[_tokenId];
    }
    
    /**
     * @dev Set a salable state for _tokenId. It is supposed to be called once. true: salable, false: not salable
     */
    function setUserNftSellState(uint256 _tokenId, uint256 _price, uint8 state) public {
        require(_exists(_tokenId), "nonexistent token");
        require(msg.sender == ownerOf(_tokenId));
        
        state = state % 2;
        setUserMrPrice(_tokenId, _price);
        
        if (state == 1)
        {
            approve(address(this), _tokenId);
        }
        else
        {
            approve(address(0), _tokenId);
        }
        
        ownedNftState[_tokenId] = state;
    }