contract PunkStats is ERC721Enumerable, ReentrancyGuard, Ownable {

    ERC721 gear = ERC721(0xFf796cbbe32B2150A4585a3791CADb213D0F35A3);
    
    // mirror a dice roll
    function random(string memory input) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input))) % 6) + 1;
    }

    function getReflexes(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Reflexes", false);
    }
    
    function getPower(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Power", false);
    }
    
    function getIntuition(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Intuition", false);
    }

    function getGearhead(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Gearhead", false);
    }

    function getCharm(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Charm", false);
    }

    function getRecovery(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Recovery", false);
    }
    
    function getExternalMemory(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "External Memory", true);
    }

    function getLuck(uint256 tokenId) public pure returns (string memory) {
        return pluck(tokenId, "Luck", false);
    }
    
    function pluck(uint256 tokenId, string memory keyPrefix, bool tera) internal pure returns (string memory) {
        uint256 roll1 = random(string(abi.encodePacked(keyPrefix, toString(tokenId), "1")));
        uint256 min = roll1;
        uint256 roll2 = random(string(abi.encodePacked(keyPrefix, toString(tokenId), "2")));
        min = min > roll2 ? roll2 : min;
        uint256 roll3 = random(string(abi.encodePacked(keyPrefix, toString(tokenId), "3")));
        min = min > roll3 ? roll3 : min;
        uint256 roll4 = random(string(abi.encodePacked(keyPrefix, toString(tokenId), "4")));
        min = min > roll4 ? roll4 : min;
        
        // get 3 highest dice rolls
        uint256 stat = roll1 + roll2 + roll3 + roll4 - min;

        string memory suffix = "";
        if (tera == true) {
            suffix = " TB";
        }
        
        string memory output = string(abi.encodePacked(keyPrefix, "_ ", toString(stat), suffix));

        return output;
    }

    function tokenURI(uint256 tokenId) override public pure returns (string memory) {
        string[17] memory parts;
        parts[0] = '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 420 420" style="enable-background:new 0 0 420 420;" xml:space="preserve"><style type="text/css">.st0{fill:#222222;}.st1{fill:#FFFFFF;}.st2{font family:serif;}.st3{font-size:14px;}.st4{fill:none;stroke:#15E51F;stroke-width:0.5;stroke-miterlimit:10;}</style><rect class="st0" width="420" height="420"/><text transform="matrix(1 0 0 1 10 20)" class="st1 st2 st3">';

        parts[1] = getReflexes(tokenId);

        parts[2] = '</text><text transform="matrix(1 0 0 1 10 40)" class="st1 st2 st3">';

        parts[3] = getPower(tokenId);

        parts[4] = '</text><text transform="matrix(1 0 0 1 10 60)" class="st1 st2 st3">';

        parts[5] = getIntuition(tokenId);

        parts[6] = '</text><text transform="matrix(1 0 0 1 10 80)" class="st1 st2 st3">';

        parts[7] = getGearhead(tokenId);

        parts[8] = '</text><text transform="matrix(1 0 0 1 10 100)" class="st1 st2 st3">';

        parts[9] = getCharm(tokenId);

        parts[10] = '</text><text transform="matrix(1 0 0 1 10 120)" class="st1 st2 st3">';

        parts[11] = getRecovery(tokenId);

        parts[12] = '</text><text transform="matrix(1 0 0 1 10 140)" class="st1 st2 st3">';

        parts[13] = getExternalMemory(tokenId);

        parts[14] = '</text><text transform="matrix(1 0 0 1 10 160)" class="st1 st2 st3">';

        parts[15] = getLuck(tokenId);

        parts[16] = '</text><g><g><path class="st4" d="M387.6,384.4h-0.7v10.8c0,0.6,0.5,1.2,1.2,1.2h14c0.6,0,1.2-0.5,1.2-1.2v-9.4h-0.9c-0.3,0-0.6-0.3-0.6-0.6 v-3.6c0-0.3,0.3-0.6,0.6-0.6h0.9v-4.7c0-0.6-0.5-1.2-1.2-1.2h-9.5c-0.5,0-0.9,0.2-1.2,0.5l-4,4c-0.3,0.3-0.5,0.8-0.5,1.2v1.6h0.7 c0.3,0,0.6,0.3,0.6,0.6v0.8C388.2,384.1,387.9,384.4,387.6,384.4z M401.9,379.1h-1.7v-2.5h1.7V379.1z M399.2,379.1h-1.6v-2.5h1.6 L399.2,379.1L399.2,379.1z M396.5,379.1h-1.7v-2.5h1.7V379.1z M392.1,376.6h1.6v2.5h-1.6V376.6z M389.5,378.9h1.5v2.5h-1.5V378.9 L389.5,378.9z"/></g></g></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], parts[13], parts[14], parts[15], parts[16]));
        
        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Punk Stats #', toString(tokenId), '", "description": "Punk Stats are randomized table top RPG style stats generated and stored on chain. It is the first Gear derivative designed by the community. Feel free to use Punk Stats in any way you want.", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
        output = string(abi.encodePacked('data:application/json;base64,', json));

        return output;
    }

    function claim(uint256 tokenId) public nonReentrant {
        require(tokenId > 7777 && tokenId < 9576, "Token ID invalid");
        _safeMint(_msgSender(), tokenId);
    }
    
    function claimForGear(uint256 tokenId) public nonReentrant {
        require(tokenId > 0 && tokenId < 8001, "Token ID invalid");
        require(gear.ownerOf(tokenId) == msg.sender, "Not Gear owner");
        _safeMint(_msgSender(), tokenId);
    }
    
    function ownerClaim(uint256 tokenId) public nonReentrant onlyOwner {
        require(tokenId > 9575 && tokenId < 10001, "Token ID invalid");
        _safeMint(owner(), tokenId);
    }
    
    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);
    }
    
    constructor() ERC721("Punk Stats", "STATS") Ownable() {}
}
function random(string memory input) internal pure returns (uint256) {
    return (uint256(keccak256(abi.encodePacked(input))) % 6) + 1;
}

// mirror a dice roll