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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./interfaces/IERC6672.sol";

contract ERC6672 is ERC721, IERC6672 {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    bytes4 public constant IERC6672_ID = type(IERC6672).interfaceId;

    mapping(address => mapping(uint256 => mapping(bytes32 => bool))) redemptionStatus;
    mapping(address => mapping(uint256 => mapping(bytes32 => string))) public memos;
    mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) redemptions;

    constructor() ERC721("Multiple RedeemableNFT", "mrNFT") {}

    function isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) external view returns (bool) {
        return _isRedeemed(_operator, _redemptionId, _tokenId);
    }

    function getRedemptionIds(address _operator, uint256 _tokenId) external view returns (bytes32[] memory) {
        require(redemptions[_operator][_tokenId].length() > 0, "ERC6672: token doesn't have any redemptions.");
        return redemptions[_operator][_tokenId].values();
    }
    
    function redeem(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external {
        address _operator = msg.sender;
        require(!_isRedeemed(_operator, _redemptionId, _tokenId), "ERC6672: token already redeemed.");
        _update(_operator, _redemptionId, _tokenId, _memo, true);
        redemptions[_operator][_tokenId].add(_redemptionId);
    }

    function cancel(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external {
        address _operator = msg.sender;
        require(_isRedeemed(_operator, _redemptionId, _tokenId), "ERC6672: token doesn't redeemed.");
        _update(_operator, _redemptionId, _tokenId, _memo, false);
        _removeRedemption(_operator, _redemptionId, _tokenId);
    }

    function _isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) internal view returns (bool) {
        require(_exists(_tokenId), "ERC6672: token doesn't exists.");
        return redemptionStatus[_operator][_tokenId][_redemptionId];
    }

    function _update(address _operator, bytes32 _redemptionId, uint256 _tokenId, string memory _memo, bool isRedeemed_) internal {
        redemptionStatus[_operator][_tokenId][_redemptionId] = isRedeemed_;
        memos[_operator][_tokenId][_redemptionId] = _memo;
    }

    function _removeRedemption(address _operator, bytes32 _redemptionId, uint256 _tokenId) internal {
        redemptions[_operator][_tokenId].remove(_redemptionId);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC6672).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}