contract SellYourSoul is ERC721Enumerable, Ownable  {
    //using Trigonometry for uint16;
    string private metadataURI = ""; 
    address public _owned;
    //address public owner = 0xaF69610ea9ddc95883f97a6a3171d52165b69B03; // for OpenSea storefront integration. Doesn't do anything in-contract.
    address proxyRegistryAddress;
    uint256 private _currentTokenId = 0;
    uint public constant TOKEN_LIMIT = 1000*1024; // 100*1024 for prod
    uint public constant ARTIST_PRINTS = 32; // 32 for testing, 32 for prod
    uint public constant PRICE = 30 * 1000000000000000; // 'finney':      200 * '1000000000000000',
    // The beneficiary is Transporter Control
    address public constant BENEFICIARY = 0x340281d051B6F0BBE305a352967419d05D4dFfa6;
    mapping(uint256=>string) tokenName; 


    string[] private relation = [
        "acceptance",     // 0
        "acquiescence",   // 1
        "bend",           // 2
        "cave",           // 3
        "condescend",     // 4
        "hold",           // 5
        "peace",          // 6
        "resignation",    // 7
        "settle",         // 8
        "tolerate",       // 9
        "yield to sth"    // 10
    ];
    string[] private characteristic = [
        "capitulation",   // 0
        "compromise",     // 1
        "defer",          // 2
        "hold your nose", // 3
        "philosophical",  // 4
        "resort",         // 5
        "surrender",      // 6
        "trade",          // 7
        "bend",           // 8
        "cave",           // 9
        "relent",         // 10
        "reconcile",      // 11
        "submit",         // 12
        "trade",          // 13
        "yield"           // 14
    ];
    string[] private feature = [
        "resign",         // 0
        "accumulation",   // 1
        "production",     // 2
        "exchange",       // 3
        "market",         // 4
        "maximize",       // 5
        "value",          // 6
        "ownership",      // 7
        "labor",          // 8
        "investment",     // 9
        "money",          // 10
        "profit",         // 11
        "competition",    // 12
        "efficient",      // 13
        "factors",        // 14
        "production",     // 15
        "maximization",   // 16
        "process"         // 17
    ];
    string[] private presence = [
        "sellers",        // 0
        "buyers",         // 1
        "hodlers",        // 2
        "speculators",    // 3
        "scammers",       // 4
        "developers",     // 5
        "marketers",      // 6
        "leaders"         // 7
    ];
    string[] private direction = [
        "bulls",        // 0
        "bears"         // 1
    ];
    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
     // 0xa5409ec958C83C3f309868babACA7c86DCB077c1 <- registerProxyAddress on mainnet 
     // 0xa5409ec958C83C3f309868babACA7c86DCB077c1 <- rinkeby 
     // 0x58807baD0B376efc12F5AD86aAc70E78ed67deaE <- polygon 
    constructor (string memory name_, string memory symbol_, address _proxyRegistryAddress, string memory uri) ERC721(name_, symbol_) {
        proxyRegistryAddress = _proxyRegistryAddress;
        _owned = msg.sender;
        //super._mint(msg.sender, totalSupply());
        metadataURI = uri;

        for(uint i=0;i<=ARTIST_PRINTS;i++)
           _mintTo(msg.sender); 
    }
    
    function _mintTo(address newOwner) internal  
    {
        super._mint(newOwner, _getNextTokenId());
    }
    function mint(address toAddress) payable public {
        require( totalSupply() < TOKEN_LIMIT,"token limit"); 
        
        uint amount = 0;
        if (totalSupply() >= ARTIST_PRINTS) {
            amount = PRICE;
            require(msg.value >= amount, "!value 0.03 eth");
        }
        
        _mintTo(toAddress); 
        
        if (msg.value > amount) {
            payable(msg.sender).transfer(msg.value - amount); // send back whats more then list price 
        }
        if (amount > 0) {
            payable(BENEFICIARY).transfer(amount); // send rest to BENEFICIARY
        }
    }
    function mintAndSign(string memory signature) payable public {
        require( totalSupply() < TOKEN_LIMIT,"token limit"); 
        
        uint amount = 0;
        if (totalSupply() >= ARTIST_PRINTS) {
            amount = PRICE;
            require(msg.value >= amount, "!value 100 finney");
        }
        
        _mintTo(msg.sender); 
        
        if (msg.value > amount) {
            payable(msg.sender).transfer(msg.value - amount); // send back whats more then list price 
        }
        if (amount > 0) {
            payable(BENEFICIARY).transfer(amount); // send rest to BENEFICIARY
        }
        
        tokenName[totalSupply()] = signature; 
    }
    function mintWithSignature(address toAddress, string memory signature) payable public {
        require( totalSupply() < TOKEN_LIMIT,"token limit"); 
        
        uint amount = 0;
        if (totalSupply() >= ARTIST_PRINTS) {
            amount = PRICE;
            require(msg.value >= amount, "!value 100 finney");
        }
        
        _mintTo(toAddress); 
        
        if (msg.value > amount) {
            payable(msg.sender).transfer(msg.value - amount); // send back whats more then list price 
        }
        if (amount > 0) {
            payable(BENEFICIARY).transfer(amount); // send rest to BENEFICIARY
        }
        
        tokenName[totalSupply()] = signature; 
    }
    
    /**
     * @dev calculates the next token ID based on totalSupply
     * @return uint256 for the next token ID
    */
    function _getNextTokenId() private view returns (uint256) {
        return totalSupply() + 1;
    }
    /*
    function supportsFactoryInterface() override public pure returns (bool) {
        return true;
    }*/
    function setMetadata(string memory uri) public
    {
        require(msg.sender==_owned,"!owned"); 
        metadataURI = uri;
    }
    function signDeed(uint256 tokenId, string memory newName) public
    {
        require(_exists(tokenId),"!exists");
        require(msg.sender==ownerOf(tokenId),"!owner");
        tokenName[tokenId] = newName;
    }
    function contractURI() public view returns (string memory) {
        return metadataURI; 
    }
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // 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);
    }
    // from: https://ethereum.stackexchange.com/questions/31457/substring-in-solidity/31470
    function substring(string memory str, uint startIndex, uint endIndex) internal pure returns (string memory) {
        bytes memory strBytes = bytes(str);
        bytes memory result = new bytes(endIndex-startIndex);
        for(uint i = startIndex; i < endIndex; i++) {
            result[i-startIndex] = strBytes[i];
        }
        return string(result);
    }
    function toByte(uint8 _uint8) internal pure returns (bytes1) {
        if(_uint8 < 10) {
            return bytes1(_uint8 + 48);
        } else {
            return bytes1(_uint8 + 87);
        }
    }
    function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
        uint8 i = 0;
        bytes memory bytesArray = new bytes(64);
        for (i = 0; i < bytesArray.length; i++) {
    
            uint8 _f = uint8(_bytes32[i/2] & 0x0f);
            uint8 _l = uint8(_bytes32[i/2] >> 4);
    
            bytesArray[i] = toByte(_f);
            i = i + 1;
            bytesArray[i] = toByte(_l);
        }
        return string(bytesArray);
    }
    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory name = string(abi.encodePacked('Sell Your Soul #', toString(tokenId), ' '));
        string memory description = "With Signature You Comply To Sell Your Soul"; 
        string memory image = Base64.encode(bytes(generateImage(tokenId))); //generateBase64Image(tokenId);
        string memory attributes = string(abi.encodePacked('","attributes":[{"trait_type":"Feature", "value": "',
                                                              getFeature(tokenId),
                                                              '"},{"trait_type":"Characteristic", "value": "',
                                                              getCharacteristic(tokenId),
                                                              '"},{"trait_type":"Direction", "value": "',
                                                              getDirection(tokenId),
                                                              '"},{"trait_type":"Type", "value": "',
                                                              getRelation(tokenId),
                                                              '"},{"trait_type":"Tribe", "value": "'
                                                              ,getPresence(tokenId),'"}]}'));
        return string(
            abi.encodePacked(
                'data:application/json;base64,',
                Base64.encode(
                    bytes(
                        abi.encodePacked(
                            '{"name":"', name, tokenName[tokenId], 
                            '", "description":"', description,
                            '","image_data":"data:image/svg+xml;base64,',
                            image,
                            '","image":"data:image/svg+xml;base64,', 
                            image,
                            attributes
                        )
                    )
                )
            )
        );
    }
    
    function getTokenSignature(uint256 tokenId) public view returns (string memory) {
        return tokenName[tokenId];
    }
    function getRelation(uint256 tokenId) public view returns (string memory) {
        return relation[tokenId%10];
    }
    function getFeature(uint256 tokenId) public view returns (string memory) {
        return feature[tokenId%18];
    }
    function getCharacteristic(uint256 tokenId) public view returns (string memory) {
        return characteristic[tokenId%15];
    }
    function getPresence(uint256 tokenId) public view returns (string memory) {
        return presence[tokenId%8];
    }
    function getDirection(uint256 tokenId) public view returns (string memory) {
        return direction[tokenId%2];
    }
    function generateBase64Image(uint256 tokenId) public view returns (string memory) {
        return Base64.encode(bytes(generateImage(tokenId)));
    }
    function generateImage(uint256 tokenId) public view returns (string memory) {
        //uint    morph = uint(bytes32(keccak256(abi.encodePacked(block.timestamp))));  // we are changing with time
        string memory back = bytes32ToString(keccak256(abi.encodePacked(tokenId, tokenName[tokenId])));
    
        return string(
            abi.encodePacked(
                '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><defs><style type="text/css">@import url("https://fonts.googleapis.com/css?family=Indie+Flower|New+Rocker");</style></defs>',
                '<style>.bl {mix-blend-mode:color-dodge;stroke-dasharray:6;stroke:#ffffff05;stroke-width:200;}.main {fill:#aaaaaa;direction:ltr;font-size:55px;font-family:New Rocker}.undr {fill:#999999;direction:ltr;font-size:15px;font-family:New Rocker}.old  {fill:#999999;mix-blend-mode:overlay;direction:ltr;font-size:55px;font-family:New Rocker} .sig  {fill:white;direction:ltr;font-size:25px;font-family:Indie Flower}</style>',
                '<rect width="100%" height="100%" fill="black"/>',
                '<text x="50%" y="40%" dominant-baseline="middle" text-anchor="middle" class="main">Sell your soul</text>',
                '<text x="20" y="210" dominant-baseline="middle" text-anchor="left" class="undr">Sign here: ...................................................................</text>',
                '<text x="10%" y="17%" dominant-baseline="middle" text-anchor="left" class="old" transform="scale(0.19 3)">', back, '</text>',
                '<text x="90" y="205" dominant-baseline="middle" text-anchor="right" class="sig" textLength="246" lengthAdjust="spacingAndGlyphs">',tokenName[tokenId],'</text>'
                '</svg>'
            )
        );
    }
    
    
    function isApprovedForAll(address owner, address operator) override public view returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }
}
/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
function substring(string memory str, uint startIndex, uint endIndex) internal pure returns (string memory) {
    bytes memory strBytes = bytes(str);
    bytes memory result = new bytes(endIndex-startIndex);
    for(uint i = startIndex; i < endIndex; i++) {
        result[i-startIndex] = strBytes[i];
    }
    return string(result);
}

// from: https://ethereum.stackexchange.com/questions/31457/substring-in-solidity/31470