contract EthereumMailService0x1 is ERC721URIStorage, Ownable {
    using Strings for uint256;

    uint256 id = 1;

    uint256 ownerId = 1;

    mapping(uint256 => uint256) public levels;

    mapping(address => bool) public claimed;

    ProxyRegistry proxyRegistry;

    mapping(address => bool) whiteList;

    function getImage(uint256 tokenId) internal view returns (string memory) {
        string[5] memory parts;
        parts[
            0
        ] = unicode'<svg version="1.1" viewBox="0.0 0.0 699.0288713910761 700.005249343832" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><style>.base { fill: white; font-family: san-serif; font-size: 50px;  animation: animate ';
        parts[1] = getShake(tokenId);
        parts[
            2
        ] = unicode's linear infinite;} @keyframes animate {	0%, 100% {	text-shadow: -2px -2px 0 #0ff, 2px 2px 0 #f00;	}	25% {	text-shadow: 2px 2px 0 #0ff, -2px -2px 0 #f00;	}	50% {	text-shadow: 2px -2px 0 #0ff, 2px -2px 0 #f00;	}	75% {	text-shadow: -2px 2px 0 #0ff, -2px 2px 0 #f00;	}}</style><clipPath id="p.0"><path d="m0 0l699.0289 0l0 700.00525l-699.0289 0l0 -700.00525z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l699.0289 0l0 700.00525l-699.0289 0z" fill-rule="evenodd"/><path fill="#000000" d="m0.96587926 1.9422572l699.0236 0l0 700.0l-699.0236 0z" fill-rule="evenodd"/><path stroke="#000000" stroke-width="1.0" stroke-linejoin="round" stroke-linecap="butt" d="m0.96587926 1.9422572l699.0236 0l0 700.0l-699.0236 0z" fill-rule="evenodd"/><path fill="#000000" d="m138.13255 351.94226l84.93805 -191.35434l254.81416 0l84.93805 191.35434l-84.93805 191.35431l-254.81416 0z" fill-rule="evenodd"/><path stroke="#ffffff" stroke-width="4.0" stroke-linejoin="round" stroke-linecap="butt" d="m138.13255 351.94226l84.93805 -191.35434l254.81416 0l84.93805 191.35434l-84.93805 191.35431l-254.81416 0z" fill-rule="evenodd"/><text x="260" y="360" class="base">level ';
        parts[3] = levels[tokenId].toString();
        parts[4] = "</text></g></svg>";
        return
            string(
                abi.encodePacked(
                    parts[0],
                    parts[1],
                    parts[2],
                    parts[3],
                    parts[4]
                )
            );
    }

    function getShake(uint256 tokenId) internal view returns (string memory) {
        if (levels[tokenId] == 0) return "0";
        if (levels[tokenId] >= 100) return "1";
        return (10 - levels[tokenId] / 10).toString();
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        string memory base64Json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "EMS 0x1 #',
                        tokenId.toString(),
                        '", "description": "EMS 0x1 is a kind of NFT that evolves with secondary sales. Other data are omitted for explanation by others.Thanks to everyone who helped ems", "image": "data:image/svg+xml;base64,',
                        Base64.encode(bytes(getImage(tokenId))),
                        '","attributes": [{"trait_type": "Level", "value": "',
                        levels[tokenId].toString(),
                        '"}]}'
                    )
                )
            )
        );

        return
            string(
                abi.encodePacked("data:application/json;base64,", base64Json)
            );
    }

    function compound(uint256[] memory tokenIds) public {
        require(
            tokenIds.length > 1,
            "Ethereum Mail Service 0x1:tokenIds length must gt 1"
        );
        uint256 sumLevel = 0;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                ownerOf(tokenIds[i]) == msg.sender,
                string(
                    abi.encodePacked(
                        "Ethereum Mail Service 0x1: must ownerOf tokenId ",
                        tokenIds[i].toString()
                    )
                )
            );
            sumLevel += levels[tokenIds[i]];
            _burn(tokenIds[i]);
        }
        levels[id] = sumLevel;
        _safeMint(_msgSender(), id);
        id++;
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    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"
        );
        levelUp(from, tokenId);

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    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"
        );
        levelUp(from, tokenId);
        _safeTransfer(from, to, tokenId, _data);
    }

    function levelUp(address from, uint256 tokenId) private {
        address proxy = proxyRegistry.proxies(from);
        if (proxy == msg.sender && msg.sender != address(0)) {
            levels[tokenId] += 1;
            return;
        }
    }

    function claim() public {
        require(
            id < 3188 || whiteList[msg.sender],
            "Ethereum Mail Service 0x: no stock"
        );
        require(
            !claimed[msg.sender],
            "Ethereum Mail Service 0x: already claimed"
        );
        _safeMint(_msgSender(), id);
        id++;
        claimed[msg.sender] = true;
    }

    function claimOwner() public onlyOwner{
        require(
            ownerId < 88 ,
            "Ethereum Mail Service 0x: no stock"
        ); 
        _safeMint(_msgSender(), id);               
        id++;
        ownerId++;
    }

    function addWhiteList(address[] memory _addrs) public onlyOwner {
        for (uint256 i = 0; i < _addrs.length; i++) {
            whiteList[_addrs[i]]=true;
        }
    }

    constructor(ProxyRegistry _proxyRegistry)
        public
        ERC721("Ethereum Mail Service 0x1", "EMS 0x1")
    {
        proxyRegistry = _proxyRegistry;
    }
}
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"
    );
    levelUp(from, tokenId);
    _safeTransfer(from, to, tokenId, _data);
}

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