// Contract: PiSpace
// Target function: function generateSVG(uint _pie, string memory _ingredients, uint _num_circles, string[] memory _colours) internal view returns (bytes memory)
OrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
		_safeTransfer(from, to, tokenId, _data);
	}
	function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual {
		_transfer(from, to, tokenId);
		require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
	}
	function _exists(uint256 tokenId) internal view virtual returns (bool) {
		return _owners[tokenId] != address(0);
	}
	function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
		require(_exists(tokenId), "ERC721: operator query for nonexistent token");
		address owner = ERC721.ownerOf(tokenId);
		return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
	}
	function _safeMint(address to, uint256 tokenId) internal virtual {
		_safeMint(to, tokenId, "");
	}
	function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual {
		_mint(to, tokenId);
		require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
	}
	function _mint(address to, uint256 tokenId) internal virtual {
		require(to != address(0), "ERC721: mint to the zero address");
		require(!_exists(tokenId), "ERC721: token already minted");
		_beforeTokenTransfer(address(0), to, tokenId);
		_balances[to] += 1;
		_owners[tokenId] = to;
		emit Transfer(address(0), to, tokenId);
	}
	function _burn(uint256 tokenId) internal virtual {
		address owner = ERC721.ownerOf(tokenId);
		_beforeTokenTransfer(owner, address(0), tokenId);
		_approve(address(0), tokenId);
		_balances[owner] -= 1;
		delete _owners[tokenId];
		emit Transfer(owner, address(0), tokenId);
	}
	function _transfer(address from, address to, uint256 tokenId) internal virtual {
		require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
		require(to != address(0), "ERC721: transfer to the zero address");
		_beforeTokenTransfer(from, to, tokenId);
		_approve(address(0), tokenId);
		_balances[from] -= 1;
		_balances[to] += 1;
		_owners[tokenId] = to;
		emit Transfer(from, to, tokenId);
	}
	function _approve(address to, uint256 tokenId) internal virtual {
		_tokenApprovals[tokenId] = to;
		emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
	}
	function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool)
	{
		if (to.isContract()) {
			try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
				return retval == IERC721Receiver(to).onERC721Received.selector;
			} catch (bytes memory reason) {
				if (reason.length == 0) {
					revert("ERC721: transfer to non ERC721Receiver implementer");
				} else {
					// solhint-disable-next-line no-inline-assembly
					assembly {
						revert(add(32, reason), mload(reason))
					}
				}
			}
		} else {
			return true;
		}
	}
	function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { }
}

abstract contract Ownable is Context {
	address private _owner;
	event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
	constructor () {
		address msgSender = _msgSender();
		_owner = msgSender;
		emit OwnershipTransferred(address(0), msgSender);
	}
	function owner() public view virtual returns (address) {
		return _owner;
	}
	modifier onlyOwner() {
		require(owner() == _msgSender(), "Ownable: caller is not the owner");
		_;
	}
	function renounceOwnership() public virtual onlyOwner {
		emit OwnershipTransferred(_owner, address(0));
		_owner = address(0);
	}
	function transferOwnership(address newOwner) public virtual onlyOwner {
		require(newOwner != address(0), "Ownable: new owner is the zero address");
		emit OwnershipTransferred(_owner, newOwner);
		_owner = newOwner;
	}
}

abstract contract ReentrancyGuard {
	uint256 private constant _NOT_ENTERED = 1;
	uint256 private constant _ENTERED = 2;
	uint256 private _status;
	constructor() {
		_status = _NOT_ENTERED;
	}
	modifier nonReentrant() {
		require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
		_status = _ENTERED;
		_;
		_status = _NOT_ENTERED;
	}
}

library Strings {
	bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
	function toString(uint256 value) internal pure returns (string memory) {
		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);
	}
	function toAsciiString(address x) internal pure returns (string memory) {
		bytes memory s = new bytes(40);
		for (uint i = 0; i < 20; i++) {
			bytes1 b = bytes1(uint8(uint(uint160(x)) / (2**(8*(19 - i)))));
			bytes1 hi = bytes1(uint8(b) / 16);
			bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
			s[2*i] = char(hi);
			s[2*i+1] = char(lo);
		}
		return string(s);
	}
	function char(bytes1 b) internal pure returns (bytes1 c) {
		if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
		else return bytes1(uint8(b) + 0x57);
	}
}

library Base64 {
	bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	function encode(bytes memory data) internal pure returns (string memory) {
		uint256 len = data.length;
		if (len == 0) return "";
		uint256 encodedLen = 4 * ((len + 2) / 3);
		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);
	}
}

library DynamicBuffer {
	function allocate(uint256 capacity) internal pure returns (bytes memory buffer) {
		assembly {
			let container := mload(0x40)
			{
				let size := add(capacity, 0x60)
				let newNextFree := add(container, size)
				mstore(0x40, newNextFree)
			}
			{
				let length := add(capacity, 0x40)
				mstore(container, length)
			}
			buffer := add(container, 0x20)
			mstore(buffer, 0)
		}
		return buffer;
	}
	function appendUnchecked(bytes memory buffer, bytes memory data) internal pure {
		assembly {
			let length := mload(data)
			for {
				data := add(data, 0x20)
				let dataEnd := add(data, length)
				let copyTo := add(buffer, add(mload(buffer), 0x20))
			} lt(data, dataEnd) {
				data := add(data, 0x20)
				copyTo := add(copyTo, 0x20)
			} {
				mstore(copyTo, mload(data))
			}
			mstore(buffer, add(mload(buffer), length))
		}
	}
	function appendSafe(bytes memory buffer, bytes memory data) internal pure {
		uint256 capacity;
		uint256 length;
		assembly {
			capacity := sub(mload(sub(buffer, 0x20)), 0x40)
			length := mload(buffer)
		}
		require(length + data.length <= capacity, "DynamicBuffer: Appending out of bounds.");
		appendUnchecked(buffer, data);
	}
}

library Address {
	function isContract(address account) internal view returns (bool) {
		uint256 size;
		// solhint-disable-next-line no-inline-assembly
		assembly { size := extcodesize(account) }
		return size > 0;
	}
	function sendValue(address payable recipient, uint256 amount) internal {
		require(address(this).balance >= amount, "Address: insufficient balance");
		// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
		(bool success, ) = recipient.call{ value: amount }("");
		require(success, "Address: unable to send value, recipient may have reverted");
	}
	function functionCall(address target, bytes memory data) internal returns (bytes memory) {
	  return functionCall(target, data, "Address: low-level call failed");
	}
	function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
		return functionCallWithValue(target, data, 0, errorMessage);
	}
	function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
		return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
	}
	function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
		require(address(this).balance >= value, "Address: insufficient balance for call");
		require(isContract(target), "Address: call to non-contract");
		// solhint-disable-next-line avoid-low-level-calls
		(bool success, bytes memory returndata) = target.call{ value: value }(data);
		return _verifyCallResult(success, returndata, errorMessage);
	}
	function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
		return functionStaticCall(target, data, "Address: low-level static call failed");
	}
	function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
		require(isContract(target), "Address: static call to non-contract");
		// solhint-disable-next-line avoid-low-level-calls
		(bool success, bytes memory returndata) = target.staticcall(data);
		return _verifyCallResult(success, returndata, errorMessage);
	}
	function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
		return functionDelegateCall(target, data, "Address: low-level delegate call failed");
	}
	function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
		require(isContract(target), "Address: delegate call to non-contract");
		// solhint-disable-next-line avoid-low-level-calls
		(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 {
			if (returndata.length > 0) {
				// solhint-disable-next-line no-inline-assembly
				assembly {
					let returndata_size := mload(returndata)
					revert(add(32, returndata), returndata_size)
				}
			} else {
				revert(errorMessage);
			}
		}
	}
}

contract PiSpace is ERC721, ReentrancyGuard, Ownable
{
	using Strings for int;
	using Strings for uint;
	using Strings for bytes32;
	using DynamicBuffer for bytes;

	bool public sale_active = false;
	uint public sale_price = 0.0314 ether;
	uint public max_supply = 314;
	uint public pies_baked = 0;

	string[] blend_modes = ["overlay", "lighten", "multiply"];

	mapping(uint => string) public pie_ingredients;


	/**
	 * @dev Setup all the variables for the Traits and the Collection.
	 */
	constructor() ERC721("Pi-Space", "PIES") {}


	/**
	* @dev Calculate the total amount minted so far.
	*/
	function totalSupply() public view returns (uint)
	{
		return pies_baked;
	}


	/**
	 * @dev Bake a Pi-Space and add the ingredients to the mapping.
	 */
	function bake() public payable nonReentrant
	{
		require(totalSupply() < max_supply, "All the Pies have already been baked.");
		require(sale_active, "The Pies are not on sale at the moment.");
		require(msg.value >= sale_price, "Insufficient funds to bake a Pie.");

		uint pie = totalSupply() + 1;

		string memory ingredients = prepareIngredients(msg.sender);

		_safeMint(msg.sender, pie);

		pie_ingredients[pie] = ingredients;
		pies_baked++;
	}


	/**
	 * @dev Generate the random seed for the `_pie`.
	 */
	function prepareIngredients(address _sender) internal view returns (string memory)
	{
		uint balance = this.balanceOf(_sender);
		address address_string;

		if (balance > 0)
		{
			address_string = address(uint160(uint(keccak256(abi.encodePacked(_sender, balance.toString())))));
		}
		else
		{
			address_string = _sender;
		}

		return Strings.toAsciiString(address_string);
	}


	/**
	 * @dev Get the Ingredients for the `_pie`.
	 */
	function getIngredients(uint _pie) public view returns (string memory)
	{
		return pie_ingredients[_pie];
	}


	/**
	 * @dev Output the svg in the json array for the lols.
	 */
	function tokenURI(uint _pie) public view override returns (string memory)
	{
		require(_exists(_pie), "This Pie doesn't exist.");

		string memory ingredients = getIngredients(_pie);
		string[] memory colours = new string[](6);

		uint num_circles = rand(ingredients, 0, 13) + 10;

		colours[0] = "ffffff";
		colours[1] = getSliceFromBytesString(ingredients, 0, 6);
		colours[2] = getSliceFromBytesString(ingredients, 2, 6);
		colours[3] = getSliceFromBytesString(ingredients, 20, 6);
		colours[4] = getSliceFromBytesString(ingredients, 22, 6);
		colours[5] = getSliceFromBytesString(ingredients, 32, 6);

		bytes memory svg = generateSVG(_pie, ingredients, num_circles, colours);

		string memory json = string(abi.encodePacked(
			'data:application/json,{"name":"Pi-Space #',
			bytes(_pie.toString()),
			'","description":"Pi-Space is the second blockchain generative art project from Rant&Code. Each piece is generated using unique data from the wallet used to mint it.\\n\\n[Website](https://rantandcode.co.uk/pi-space) | [Twitter](https://twitter.com/rantandcode_)","image":"data:image/svg+xml;base64,',
			bytes(Base64.encode(svg)),
			'","attributes":[{"trait_type": "Circles", "value": "',
			bytes(num_circles.toString()),
			'"},{"trait_type": "Colour 1", "value": "#',
			colours[1],
			'"},{"trait_type": "Colour 2", "value": "#',
			colours[2],
			'"},{"trait_type": "Colour 3", "value": "#',
			colours[3],
			'"},{"trait_type": "Colour 4", "value": "#',
			colours[4],
			'"},{"trait_type": "Background", "value": "#',
			colours[5],
			'"}], "background_color": "#',
			colours[5],
			'"}'
		));

		return json;
	}