contract JugglingNFT is ERC721, Ownable {
    
    // nameとsymbolの設定。ERC721参照
    constructor() ERC721("JugglingNFT", "JNFT") {}
  uint256 public nextTokenId = 0;
  uint256 public donatedAmount = 0.001 ether;
  
//   alreadyDonated[tokenId] == true はtransferFromの要件である。
   mapping (uint256 => bool) private alreadyDonated;

//   https://www.huobi.co.jp/ja-jp/donation/　huobi japanが行っている首里城復旧募金のアドレス。
  address charityAddress = 0x9C388AFbc760E19CfA9190F60E0e641b7D053e4a;

    // Optional mapping for token URIs
    mapping(uint256 => string) private _tokenURIs;

// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721URIStorage.sol参照。
    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

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

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        return super.tokenURI(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), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }

// ownerがstringを入力すれば、それをtokenURIとするNFTがmintされる。
// SafeMathを使うのがなんか嫌だったからrequire(nextTokenId + 1 > nexTokenId);を使った。本質的にはsafeMathと変わらない。
// safeMathはopenzeppelinのopenzeppelin-contracts/contracts/utils/math/SafeMath.sol参照。
  function mint(string memory _tokenURI) external onlyOwner {
    uint256 tokenId = nextTokenId;
    super._mint(msg.sender, tokenId);
    _setTokenURI(tokenId, _tokenURI);
    require(nextTokenId + 1 > nextTokenId);
    nextTokenId++;
  }
    
//   ownerはtransactionに必要な寄付額を自由に決めることができる。ただし0.001ether以下である。
  function changeDonatedAmount(uint256 _newDonatedAmount) public onlyOwner {
      require(0.001 ether >= _newDonatedAmount);
      donatedAmount = _newDonatedAmount;
  }

//   transactionに必要な寄付額を確認するための関数。
  function DonatedAmount() public view returns(uint256) {
      return donatedAmount;
  }

// NFTの所有者は、fromに送り主アドレスを、toに送り先アドレスを、tokenIdに当該NFTのtokenIDを入力すれば、NFTを移転させることができる。
// ただし、transactionに必要な寄付額を同時にコントラクトアドレスに送る必要がある。
  function donateAndTransfer(address from, address to, uint256 tokenId) external payable {
      require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
      require(msg.value >= donatedAmount);
      _transfer(from, to, tokenId);
  }
  
// コントラクトアドレスに寄付されたETHを、募金先アドレスにのみ送ることができる関数。誰でも実行できる。
// なお、この関数以外にコントラクトアドレスからETHを引き出す手段はない。
  function withdrawForCharity() external {
      payable(charityAddress).transfer(address(this).balance);
  }

// tokenIdを入力した上でこの関数を使ってETHをコントラクトアドレスに送った場合、当該tokenIdの保有者は一度だけ募金なしでtransferFromによりNFTを移転させることができる。
// つまりこの関数を実行すれば、metamaskを使ってNFTを移転させることができる。
// 誰でも実行可能。
  function donateBeforeTransfer(uint256 tokenId) external payable {
      require(msg.value >= donatedAmount);
      alreadyDonated[tokenId] = true;
  }
  
// donateBeforeTransferを使わずにmetamaskでtransferFromを実行すると、デフォのガスリミット21000ではそもそもintrinsic gas too lowのエラーによってトランザクションの送信すらできない。
// また、ガスリミットを53000にすれば、トランザクションの送信自体を行うことができるが、donateBeforeTransferを実行していないと、トランザクションが失敗する。
// transaction毎に募金させるため、transferFromを実行した後は alreadyDonated[tokenId] がfalseになるようにしている。
  function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        require(alreadyDonated[tokenId]);
        alreadyDonated[tokenId] = false;
        _transfer(from, to, tokenId);
    }
 
// transferFromに対して行ったoverrideとほぼ同じ内容にしている。
    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");
        require(alreadyDonated[tokenId]);
        alreadyDonated[tokenId] = false;
        _safeTransfer(from, to, tokenId, _data);
    }
    
}
function donateBeforeTransfer(uint256 tokenId) external payable {
    require(msg.value >= donatedAmount);
    alreadyDonated[tokenId] = true;
}

// tokenIdを入力した上でこの関数を使ってETHをコントラクトアドレスに送った場合、当該tokenIdの保有者は一度だけ募金なしでtransferFromによりNFTを移転させることができる。
// つまりこの関数を実行すれば、metamaskを使ってNFTを移転させることができる。
// 誰でも実行可能。