contract Lootbox is ERC721Enumerable, ReentrancyGuard, Ownable {

    struct Box {
        address addr;
        uint amount;
    }

    uint public nextTokenId;
    mapping (uint => Box) public boxes;

    event BoxCreated(uint tokenId, address erc20addr, uint amount);
    event BoxOpened(uint tokenId, address erc20addr, uint amount);

    function getName(uint256 tokenId) public view returns (string memory) {
        return ERC20(address(boxes[tokenId].addr)).name();
    }

    function getSymbol(uint256 tokenId) public view returns (string memory) {
        return ERC20(address(boxes[tokenId].addr)).symbol();
    }

    function getAddress(uint256 tokenId) public view returns (string memory) {
        return addr2str(boxes[tokenId].addr);
    }

    function getAmount(uint256 tokenId) public view returns (string memory) {
        return toString(boxes[tokenId].amount);
    }

    function getWholeTokens(uint256 tokenId) public view returns (string memory) {
        return whole2str(boxes[tokenId].amount, ERC20(address(boxes[tokenId].addr)).decimals());
    }

    function getFractionTokens(uint256 tokenId) public view returns (string memory) {
        return decimals2str(boxes[tokenId].amount, ERC20(address(boxes[tokenId].addr)).decimals());
    }

    // specify the erc20 token address, and the amount,
    // and create a Lootbox NFT.
    // as a prerequisite: you must first approve the amount
    // you wish to put in the Lootbox on the target erc20 token contract.
    function create(address token, uint amount) public nonReentrant {
        ERC20 erc20 = ERC20(token);
        require(erc20.allowance(_msgSender(), address(this)) >= amount, "First, you must call approve for the amount on the erc20 token!");

        // lock the tokens into this Bundle
        erc20.transferFrom(_msgSender(), address(this), amount);
        boxes[nextTokenId] = Box(token, amount);

        // mint nft to msg.sender
        _safeMint(_msgSender(), nextTokenId);

        // emit event for tracking
        emit BoxCreated(nextTokenId, token, amount);

        // increment tokenId for next creator
        nextTokenId++;
    }

    // transfer the the erc20 tokens inside the Lootbox to yourself.
    // this action burns the Lootbox.
    function redeem(uint tokenId) public nonReentrant {
        // require(bundles[tokenId].addr != 0x0, "invalid tokenId");
        require(_msgSender() == ownerOf(tokenId), "you dont own this tokenId");

        Box memory b = boxes[tokenId];
        ERC20 erc20 = ERC20(b.addr);

        // tansfer the tokens out of the bundle, to the redeemer (ie: msg.sender)
        erc20.transfer(_msgSender(), b.amount);

        // burn the tokenId forever
        _burn(tokenId);

        // emit event for tracking
        emit BoxOpened(tokenId, b.addr, b.amount);
    }

    function tokenURI(uint256 tokenId) override public view returns (string memory) {
        string[13] memory parts;

        string memory tokenAddr = getAddress(tokenId);

        uint spot;
        parts[spot++] = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.h1{text-align: left;text-decoration: underline;text-decoration-thickness: 2px;font: normal normal 800 30px/39px EB Garamond;letter-spacing: 0px;color: #F3F3F3;fill: #F3F3F3;}.txt {text-align: left;font: normal normal 800 18px/39px EB Garamond;letter-spacing: 0px;text-decoration-thickness: 2px;fill: grey;}.a {display: block;margin-bottom: 12px;text-align: left;font: italic normal normal 12px EB Garamond;letter-spacing: 0px;color: #F3F3F3;text-decoration: none;fill: blue;}.txt2 {text-align: left;font: normal normal normal 15ps/25px EB Garamond;letter-spacing: 0px;text-decoration-thickness: 2px;color: #F3F3F3;fill: #F3F3F3;}</style><rect width="100%" height="100%" fill="black"/><text x="10" y="40" class="h1">';
        parts[spot++] = getSymbol(tokenId);
        parts[spot++] = '</text><text x="10" y="75" class="txt">';
        parts[spot++] = getName(tokenId);
        parts[spot++] = '</text><text x="10" y="100" ><a class="a" href="https://etherscan.io/token/';
        parts[spot++] = tokenAddr;
        parts[spot++] = '">';
        parts[spot++] = tokenAddr;
        parts[spot++] = '</a></text><text x="10" y="130" class="txt2">Qty: ';
        parts[spot++] = getWholeTokens(tokenId);
        parts[spot++] = '.';
        parts[spot++] = getFractionTokens(tokenId);
        parts[spot++] = '</text></svg>';

        string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7], parts[8]));
        output = string(abi.encodePacked(output, parts[9], parts[10], parts[11], parts[12]));

        // instructions on how to redeem() the tokens in here!
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Lootbox #', toString(tokenId), ' ', getSymbol(tokenId), '", "description": "Lootbox is a way to wrap ERC20 tokens into an NFT and show the contents easily with an svg. (Inspired by Loot!). To transfer the tokens to yourself, you must own the NFT and call redeem(tokenId) on the Lootbox contract.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));

        return output;
    }

    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT license
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    // helper inside addr2str
    // if necessary, converts utf8 numeric to alpha
    function check_utf_uint8(uint8 _c) internal view returns (uint8 _ret){
        _ret = _c;
        if(_ret > 0x39){
            // its the a to f
            _ret += 7;
        }
    }

    // helper for addr2str
    // split a bytes1 into its two 4 bit utf8 char ints
    function byte_2_utf_ints(bytes1 _b) internal view returns (uint8 _x, uint8 _y){
        _x = check_utf_uint8(0x30 + ((uint8(_b) >> 4) & 0x0f));
        _y = check_utf_uint8(0x30 + ((uint8(_b)) & 0x0f));
    }

    // converts an address into a string prefixed with 0x
    function addr2str(address _addr) public view returns (string memory){
        bytes20 byts = bytes20(_addr);
        bytes memory b = new bytes(42); // the size is the answer to life, the universe, and everything

        // put first bytes
        uint j;
        b[j++] = bytes1(0x30); // "0"
        b[j++] = bytes1(0x78); // "x"

        uint i;
        uint8 x;
        uint8 y;
        for(; i < 20; ++i){
            (x, y) = byte_2_utf_ints(bytes1(byts[i]));
            b[j++] = bytes1(x);
            b[j++] = bytes1(y);
        }

        return string(b);
    }

    // return the string representing the whole portion of the tokens (everything to left of decimal pt)
    function whole2str(uint qty, uint decimals) public view returns (string memory){
        return toString(qty / (10 ** decimals));
    }

    // return a string representing the decimal portion of the tokens (left padded with 0s)
    function decimals2str(uint qty, uint decimals) public view returns (string memory){
        //uint decimals = decimals();
        bytes memory buffer = new bytes(decimals);
        uint i;
        for( ; i < decimals; ++i){
            buffer[i] = bytes1(0x30);
        }
        //return string(buffer);
        string memory vStr = toString(qty % 10**decimals);

        // traverse in reverse
        uint j = bytes(vStr).length;
        i = decimals;
        for(; j > 0; --j){
            buffer[i-1] = bytes(vStr)[j-1];
            --i;
        }
        //buffer[17] = bytes(vStr)[3]; // works actually with 1234
        return string(buffer);
    }

    constructor() ERC721("Lootbox", "BOX") Ownable() {}
}
function addr2str(address _addr) public view returns (string memory){
    bytes20 byts = bytes20(_addr);
    bytes memory b = new bytes(42); // the size is the answer to life, the universe, and everything

    // put first bytes
    uint j;
    b[j++] = bytes1(0x30); // "0"
    b[j++] = bytes1(0x78); // "x"

    uint i;
    uint8 x;
    uint8 y;
    for(; i < 20; ++i){
        (x, y) = byte_2_utf_ints(bytes1(byts[i]));
        b[j++] = bytes1(x);
        b[j++] = bytes1(y);
    }

    return string(b);
}

// converts an address into a string prefixed with 0x