// Contract: usernamenft
// Target function: function getBackgroundColor(uint _token_id, uint _nonce) internal view returns(bytes6)
(
		address target,
		bytes memory data,
		string memory errorMessage
	) internal returns (bytes memory) {
		require(isContract(target), "Address: delegate call to non-contract");

		(bool success, bytes memory returndata) = target.delegatecall(data);
		return _verifyCallResult(success, returndata, errorMessage);
	}

	function _verifyCallResult(
		bool success,
		bytes memory returndata,
		string memory errorMessage
	) private pure returns (bytes memory) {
		if (success) {
			return returndata;
		} else {
			// Look for revert reason and bubble it up if present
			if (returndata.length > 0) {
				// The easiest way to bubble the revert reason is using memory via assembly

				assembly {
					let returndata_size := mload(returndata)
					revert(add(32, returndata), returndata_size)
				}
			} else {
				revert(errorMessage);
			}
		}
	}
}





/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <brecht@loopring.org>
library Base64 {
	bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

	/// @notice Encodes some bytes to the base64 representation
	function encode(bytes memory data) internal pure returns (string memory) {
		uint256 len = data.length;
		if (len == 0) return "";

		// multiply by 4/3 rounded up
		uint256 encodedLen = 4 * ((len + 2) / 3);

		// Add some extra buffer at the end
		bytes memory result = new bytes(encodedLen + 32);

		bytes memory table = TABLE;

		assembly {
			let tablePtr := add(table, 1)
			let resultPtr := add(result, 32)

			for {
				let i := 0
			} lt(i, len) {

			} {
				i := add(i, 3)
				let input := and(mload(add(data, i)), 0xffffff)

				let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
				out := shl(8, out)
				out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
				out := shl(8, out)
				out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
				out := shl(8, out)
				out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
				out := shl(224, out)

				mstore(resultPtr, out)

				resultPtr := add(resultPtr, 4)
			}

			switch mod(len, 3)
			case 1 {
				mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
			}
			case 2 {
				mstore(sub(resultPtr, 1), shl(248, 0x3d))
			}

			mstore(result, encodedLen)
		}

		return string(result);
	}
}





/**
 * @dev String operations.
 */
library Strings {
	bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

	/**
	 * @dev Converts a `uint256` to its ASCII `string` decimal representation.
	 */
	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);
	}
}





/**
 * username-nft Contract
 */
contract usernamenft is ERC721, ReentrancyGuard, Ownable
{
	struct Username
	{
		string username;
		bytes6 bg_color;
		bytes6 text_color;
		string comment;
		bool exists;
	}

	mapping(uint => Username) private usernamesMapping;
	mapping(string => bool) private usernameExistsMapping;

	uint private end_date;
	uint public max_usernames_per_transaction = 10;
	uint public username_price = 100000000000000000;
	uint public total_usernames = 0;
	uint public max_username_supply = 8888;
	bool public max_username_supply_overriden = false;


	/**
	 * @dev Set up the contract and randomly set the cut off time for season one.
	 */
	constructor() ERC721("username-nft", "NAME") Ownable()
	{
		end_date = getRandomEndDate();
	}


	/**
	 * @dev Allow withdrawal from the main wallet.
	 */
	function withdraw() public onlyOwner
	{
		uint balance = address(this).balance;
		payable(msg.sender).transfer(balance);
	}


	/**
	 * @dev Public payable method to process a single username.
	 *
	 * @param _username string memory
	 */
	function mintUsername(string memory _username) public payable nonReentrant
	{
		require(validateContract());
		require(msg.value >= username_price, "Not enough ether.");
		require(validateUsername(_username));

		mintUsernameToken(_username);
	}


	/**
	 * @dev Public payable method to process an array usernames.
	 *
	 * @param _usernames string[] memory
	 */
	function mintUsernameMulti(string[] memory _usernames) public payable nonReentrant
	{
		require(validateContract());

		uint num_usernames = _usernames.length;

		require(msg.value >= (username_price * num_usernames), "Not enough ether.");
		require((num_usernames > 0) && (num_usernames <= max_usernames_per_transaction), "Too many usernames.");

		// Loop through each username to make sure each of them are valid.
		for (uint i=0; i<num_usernames; i++)
		{
			require(validateUsername(_usernames[i]));
		}

		// All good to go - now mint all the usernames.
		for (uint i=0; i<num_usernames; i++)
		{
			mintUsernameToken(_usernames[i]);
		}
	}


	/**
	 * @dev Check the general contract requirements for minting
	 *
	 * @return bool
	 */
	function validateContract() internal returns(bool)
	{
		// If we've hit the end date and the max supply hasn't been cut down yet, reduce it.
		if ((block.timestamp > end_date) && (!max_username_supply_overriden))
		{
			uint new_max_username_supply = total_usernames + 50;
			if (new_max_username_supply < max_username_supply) max_username_supply = new_max_username_supply;
			max_username_supply_overriden = true;
		}

		require((total_usernames < max_username_supply), "Season one has ended.");

		return true;
	}


	/**
	 * @dev Check the username to make sure it's valid.
	 *
	 * @param _username string memory
	 * @return bool
	 */
	function validateUsername(string memory _username) internal view returns(bool)
	{
		require(usernameExistsMapping[_username] != true, "username exists.");
		require(onlyValidCharacters(_username), "Lowercase letters and numbers only.");
		require(((bytes(_username).length >= 2) && (bytes(_username).length <= 10)), "Must be between 2 and 10 characters.");

		return true;
	}


	/**
	 * @dev Create a new token with the given username and store the .
	 *
	 * @param _username string memory
	 */
	function mintUsernameToken(string memory _username) internal
	{
		uint new_token_id = getTokenIdfromString(_username);

		uint nonce = 0;
		bytes6 bg_color = getBackgroundColor(new_token_id, nonce++);
		bytes6 text_color = getTextColor(new_token_id, nonce++);
		string memory comment = getComment(new_token_id, nonce++);

		if (total_usernames == 0) comment = "first.";

		_safeMint(_msgSender(), new_token_id);

		usernamesMapping[new_token_id] = Username(_username, bg_color, text_color, comment, true);
		usernameExistsMapping[_username] = true;
		total_usernames++;
	}


	/**
	 * @dev Output the svg in the json array for the lols.
	 *
	 * @param _token_id uint256
	 * @return string memory
	 */
	function tokenURI(uint256 _token_id) public view override returns(string memory)
	{
		require(usernamesMapping[_token_id].exists == true, "Invalid ID.");

		Username memory username = usernamesMapping[_token_id];

		string memory svg = buildSVGFromToken(username);

		string memory json = string(
			abi.encodePacked(
				'{"name": "', username.username, '",',
				'"image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '",',
				'"attributes": [{"trait_type": "background color", "value": "', username.bg_color, '"},{"trait_type": "text color", "value": "', username.text_color, '"},{"trait_type": "special message", "value": "', username.comment, '"}],',
				'"background_color": "', username.bg_color, '"}'
			)
		);
		string memory encoded_json = string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));

		return encoded_json;
	}


	/**
	 * @dev Build the SVG using the data from the username token.
	 *
	 * @param username username memory
	 * @return string memory
	 */
	function buildSVGFromToken(Username memory username) internal pure returns(string memory)
	{
		string memory glitch = "";
		bytes memory filter = "";
		bytes6 text_colour = username.text_color;

		if (keccak256(abi.encodePacked(username.text_color)) == keccak256(abi.encodePacked("glitch")))
		{
			glitch = string(abi.encodePacked(
				"<defs><filter id='filter'><feFlood flood-color='#",
				username.bg_color,
				"' result='#",
				username.bg_color,
				"'/><feFlood flood-color='red' result='flood1'/><feFlood flood-color='limegreen' result='flood2'/><feOffset in='SourceGraphic' dx='2' dy='0' result='off1a'/><feOffset in='SourceGraphic' dx='1' dy='0' result='off1b'/><feOffset in='SourceGraphic' dx='-1' dy='0' result='off2a'/><feOffset in='SourceGraphic' dx='-1' dy='0' result='off2b'/>",
				"<feComposite in='flood1' in2='off1a' operator='in' result='comp1'/><feComposite in='flood2' in2='off2a' operator='in' result='comp2'/>",
				"<feMerge x='0' width='100%' result='merge1'><feMergeNode in='black'/><feMergeNode in='comp1'/><feMergeNode in='off1b'/><animate attributeName='y' id='y' dur='8s' values='254px;254px;180px;255px;180px;152px;152px;200px;190px;255px;255px;170px;210px;190px;254px;190px;220px;160px;180px;254px;252px' keyTimes='0;0.362;0.368;0.421;0.440;0.477;0.518;0.564;0.593;0.613;0.644;0.693;0.721;0.736;0.772;0.818;0.844;0.894;0.925;0.939;1' repeatCount='indefinite'/><animate attributeName='height' id='h' dur='8s' values='10px;0px;10px;30px;50px;0px;10px;0px;0px;0px;10px;50px;40px;0px;0px;0px;40px;30px;10px;0px;50px' kyTimes='0;0.362;0.368;0.421;0.440;0.477;0.518;0.564;0.593;0.613;0.644;0.693;0.721;0.736;0.772;0.818;0.844;0.894;0.925;0.939;1' repeatCount='indefinite'/></feMerge>",
				"<feMerge><feMergeNode in='SourceGraphic'/><feMergeNode in='merge1'/></feMerge></filter></defs>"
			));

			filter = "filter:url(#filter);";
			text_colour = "000000";
		}

		string memory svg = string(abi.encodePacked(
			"<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' preserveAspectRatio='xMinYMin meet' viewBox='0 0 420 420'><style type='text/css'><![CDATA[ @supports(-webkit-text-size-adjust:none)and(not(-ms-accelerator:true))and(not(-moz-appearance:none)){text{filter:none !important;}} ]]></style><rect width='100%' height='100%' fill='#",
			username.bg_color,
			"'/>",
			glitch,
			"<g><text x='50%' y='50%' style='",
			filter,
			'font-size:45pt;font-weight:800;font-family:"Trebuchet MS",sans-serif;letter-spacing:1pt;fill:#',
			text_colour,
			";color:#",
			text_colour,
			";' dominant-baseline='middle' text-anchor='middle' fill='#",
			text_colour,
			"'>",
			username.username,
			".</text></g></svg>"
		));

		return svg;
	}


	/**
	 * @dev Make sure the string only has lowercase alphanumeric characters in it.
	 *
	 * @param _str string memory
	 * @return bool
	 */
	function onlyValidCharacters(string memory _str) internal pure returns(bool)
	{
		bytes memory string_bytes = bytes(_str);
		uint bytes_len = string_bytes.length;

		for (uint i=0; i<bytes_len; i++)
		{
			if (!(
				(string_bytes[i] >= 0x61 && string_bytes[i] <= 0x7A) // lowercase
				|| (string_bytes[i] >= 0x30 && string_bytes[i] <= 0x39) // number
			))
			{
				return false;
			}
		}

		return true;
	}


	/**
	 * @dev Convert the characters from the string into a unique uint.
	 *
	 * @param _str memory string
	 * @return uint
	 */
	function getTokenIdfromString(string memory _str) internal pure returns(uint)
	{
		bytes memory str_bytes = bytes(_str);
		uint integer = 0;
		uint bytes_len = str_bytes.length;

		for (uint i=0; i<bytes_len; i++)
		{
			integer = (integer * 100) + (uint8(str_bytes[i]) - 47);
		}

		return integer;
	}


	/**
	 * @dev Public method to allow users to look up their username before attempting to mint.
	 *
	 * @param _username string memory
	 * @return string memory
	 */
	function isUsernameAvailable(string memory _username) public view returns(string memory)
	{
		require(validateUsername(_username));
		return string(abi.encodePacked("'", _username, "' is still available."));
	}


	/**
	 * @dev Get a random number based on the seed and modulus.
	 *
	 * @param _seed uint
	 * @param _nonce uint
	 * @param _mod uint
	 * @return uint
	 */
	function getRandomNumber(uint _seed, uint _nonce, uint _mod) internal view returns(uint)
	{
		return uint256(keccak256(abi.encodePacked(block.timestamp, Strings.toString(_seed), _nonce))) % _mod;
	}


	/**
	 * @dev Get a random number between 30 and 90 to represent the cut-off date for season one.
	 *
	 * @return uint
	 */
	function getRandomEndDate() internal view returns(uint)
	{
		uint rand = uint256(keccak256(abi.encodePacked(block.timestamp))) % 100;

		if (rand < 30) rand = (30 + rand);
		else if (rand > 90) rand = (90 - (rand - 90));

		return (block.timestamp + (rand * 86400));
	}