contract NFTToken is ERC721Tradable, RoyaltiesV2Impl {
    
    // Constants
    bytes4 private constant _INTERFACE_ID_EIP2981 = 0x2a55205a;
    
    // Events
    event PermanentURI(string _value, uint256 indexed _id); // OpenSea Freezing Metadata

    string      _tokenURI = "";
    string      _contractURI = "";
    
    constructor(address _proxyRegistryAddress)
        ERC721Tradable("RelatioNFT", "RelatioNFT", _proxyRegistryAddress)
    {}

    function baseTokenURI() override public view returns (string memory) {
        return _tokenURI;
    }
    
    function transferFrom(address from, address to, uint256 tokenId) override public whenNotPaused {
        super.transferFrom(from, to, tokenId);
    }

    function burn(uint256 tokenId) public whenNotPaused {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId));
        _burn(tokenId);
    }
    
    // Updates
    // OpenSea Freezing Metadata
    function freezeMetadataForToken(uint256 _tokenId) public ownerOrOperator {
        require(_exists(_tokenId));
        
        string memory metadataUri = tokenURI(_tokenId);
        
        emit PermanentURI(metadataUri, _tokenId);
    }
    
    function freezingMetadataForTokens(uint256 _collectionId, uint256 _startTokenId, uint256 _endTokenId) public ownerOrOperator {
        require(_collectionId < collections.length); // Wrong collection
        
        for (uint i = _startTokenId; i < _endTokenId + 1; i++) {
            freezeMetadataForToken(i);
        }
    }
    
    function setTokenURI(string memory _uri) external onlyOwner {
        _tokenURI = _uri;
    }

    function setContractURI(string memory _uri) external onlyOwner {
        _contractURI = _uri;
    }
    
    function unpause() external onlyOwner {
        _unpause();
    }
    
    function pause() external onlyOwner {
        _pause();
    }
    
    function setProxyRegistryAddress(address _proxyRegistryAddress) external onlyOwner {
        proxyRegistryAddress = _proxyRegistryAddress;
    }
    
    
    // Secondary Marketplace Functions
    // OpenSea
    function contractURI() public view returns (string memory) {
        return _contractURI;
    }
    
    /// @notice Helper allowing OpenSea gas-less trading by verifying who's operator for owner
    /// @dev Allows to check if `operator` is owner's OpenSea proxy on eth mainnet / rinkeby
    ///      or to check if operator is OpenSea's proxy contract on Polygon and Mumbai
    /// @param owner the owner we check for
    /// @param operator the operator (proxy) we check for
    function isOwnersOpenSeaProxy(address owner, address operator) public view returns (bool)
    {
        address _proxyRegistry = proxyRegistryAddress;

        if (_proxyRegistry != address(0)) {
            if (block.chainid == 1 || block.chainid == 4) {
                
                return address(ProxyRegistry(_proxyRegistry).proxies(owner)) == operator;
                
            } else if (block.chainid == 137 || block.chainid == 80001) {

                return _proxyRegistry == operator;
            }
        }

        return false;
    }
    
    // Rarible Royalties V2
    function setRoyalties(uint _tokenId, address payable _royaltiesReceipientAddress, uint96 _percentageBasisPoints) public ownerOrOperator {
        LibPart.Part[] memory _royalties = new LibPart.Part[](1);
        _royalties[0].value = _percentageBasisPoints;
        _royalties[0].account = _royaltiesReceipientAddress;
        
        _saveRoyalties(_tokenId, _royalties);
    }
    
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) {
        if (interfaceId == LibRoyaltiesV2._INTERFACE_ID_ROYALTIES || interfaceId == _INTERFACE_ID_EIP2981) {
            return true;
        }
        
        return super.supportsInterface(interfaceId);
    }
    
    // EIP-2981 - https://eips.ethereum.org/EIPS/eip-2981
    function royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (address receiver, uint256 amount) {
        require(_exists(tokenId));
        
        LibPart.Part[] memory royalInfo = royalties[0];
        uint256 royaltyValue = royalInfo[0].value;
        address royaltyAccount = royalInfo[0].account;
        uint256 royaltyAmount = SafeMath.div(SafeMath.mul(salePrice, royaltyValue), 10000);

        return (royaltyAccount, royaltyAmount);
    }
}
function setRoyalties(uint _tokenId, address payable _royaltiesReceipientAddress, uint96 _percentageBasisPoints) public ownerOrOperator {
    LibPart.Part[] memory _royalties = new LibPart.Part[](1);
    _royalties[0].value = _percentageBasisPoints;
    _royalties[0].account = _royaltiesReceipientAddress;
        
    _saveRoyalties(_tokenId, _royalties);
}

// Rarible Royalties V2