// Contract: FriendlyFractals
// Target function: function compact(uint256 seed, uint256 curtype, uint256 subiteration, int256 x, int256 y, int256 dx, int256 dy) internal pure returns (uint256)
(tokenId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _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.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

interface IERC721Enumerable is IERC721 {
    function totalSupply() external view returns (uint256);
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
    function tokenByIndex(uint256 index) external view returns (uint256);
}

abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
    mapping(uint256 => uint256) private _ownedTokensIndex;
    uint256[] private _allTokens;
    mapping(uint256 => uint256) private _allTokensIndex;

    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

    function totalSupply() public view virtual override returns (uint256) {
        return _allTokens.length;
    }

    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }
        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}

abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor() {
        _setOwner(_msgSender());
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

contract FriendlyFractals is Ownable, ERC721("Friendly Fractals", "FRFRAC"), ERC721Enumerable, ReentrancyGuard {
	uint256 public curId = 0;
	uint256 public mintPrice = 0;
	mapping(uint256 => uint256) public tokenSeed;

	bytes private uriHead1 = "data:application/json;charset=utf-8,%7B%22name%22%3A%20%22Friendly%20Fractals%22%2C%22description%22%3A%20%22Fully%20on-chain%20generative%20fractal%20patterns%20based%20on%20the%20dragon%20curve.%22%2C%22image%22%3A%20%22data%3Aimage%2Fsvg%2Bxml%3Bcharset%3Dutf-8%2C%3Csvg%20width%3D'100%25'%20height%3D'100%25'%20viewBox%3D'0%200%20100000%20100000'%20style%3D'stroke-width%3A400%3B%20";
	bytes private uriHead2 = "%20background-color%3Argb(50%2C50%2C50)'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E";
	bytes private uriTail = "%3C%2Fsvg%3E%22%7D";

    constructor() {
        mint(); // tokenId 0 for deployer
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721Enumerable)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function mint() payable public nonReentrant {
    	require(curId < 300, "FriendlyFractals: Max 300 tokens");
    	require(msg.value >= mintPrice, "FriendlyFractals: Insufficient value to mint");

    	_safeMint(_msgSender(), curId);
    	// generate random seed
    	tokenSeed[curId] = uint256(keccak256(abi.encodePacked(curId, block.timestamp, blockhash(block.number - 1))));
    	curId += 1;

    	// refund value above mint price
    	if (msg.value > mintPrice) {
	    	address payable refundee = payable(_msgSender());
	    	refundee.transfer(msg.value - mintPrice);
    	}

    	mintPrice += 1e16;
    }

    function claim() public onlyOwner {
    	address payable sendee = payable(owner());
    	sendee.transfer(address(this).balance);
    }

    function contractURI() public view returns (string memory) {
        return tokenURI(0);
    }

    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        return tokenSVGFromSeed(tokenSeed[tokenId]);
    }

    function tokenSVGFromSeed(uint256 seed) internal view returns (string memory) {
        uint256 numIterations = 8;

        // space to store the string format of x1, y1, x2, y2
        string[4] memory vars = ["1000000", "1000000", "1000000", "1000000"];

        // constants which affect the final outcome	
        seed = uint256(keccak256(abi.encodePacked(seed)));
        uint256 min_subiteration = 1 + seed % 2;

        // space for all the data to compute the fractal
        uint256[] memory data = new uint256[](2**(numIterations+1)-1);

        data[0]= compact(seed, 0, 0, 35000, 35000, 30000, 30000);
        for (uint256 i=0; i<2**numIterations-1; i++) {
            //generate both children
            data[2*i+1] = getChildData(data[i], true, min_subiteration);
            data[2*i+2] = getChildData(data[i], false, min_subiteration);
        }

        // generate result string
        // each line takes up to 84 characters.
        bytes memory result = new bytes(84*(2**numIterations)+550);
        bytes memory cur;
        assembly {
        	cur := add(result, 32) //offset length header
        } 
        uint256 totallen = 0;
    	uint256 linelen;


        // add head
    	(linelen, cur) = copyString(cur, uriHead1);
    	totallen += linelen;

        (linelen, cur) = copyString(cur, getColorString(seed));
    	totallen += linelen;

    	(linelen, cur) = copyString(cur, uriHead2);
    	totallen += linelen;

    	// add body
        for (uint256 i=2**numIterations-1; i<data.length; i++) {
        	bytes memory curLine = getCurLine(data, i, vars);
        	(linelen, cur) = copyString(cur, curLine);
        	totallen += linelen;
        }

        // add tail
    	(linelen, cur) = copyString(cur, uriTail);
    	totallen += linelen;

        // reassign total length
        assembly {
        	mstore(result, totallen)
        }

        return string(result);
    }

    function getChildData(uint256 data, bool leftChild, uint256 min_subiteration) internal view returns (uint256) {
    	uint256 seed;
    	uint256 subiteration;
    	uint256 curtype;
    	int256 x;
        int256 y;
        int256 dx;
        int256 dy;

        (seed, curtype, subiteration, x, y, dx, dy) = expand(data);
        // if subiteration 0, update type and subiteration
        if (subiteration == 0) {
        	seed = uint256(keccak256(abi.encodePacked(seed)));
        	subiteration = min_subiteration + seed % 4;

			seed = uint256(keccak256(abi.encodePacked(seed)));
        	curtype = seed % 9;
        }

		int256 sqrt2a = 14142;
		int256 sqrt2b = 10000;

		// rotate 45 degrees and scale by 1/sqrt(2). dx1,dy1 is for one direction of rotation, dx2,dy2 for the other
        // int256 dx1 = (dx * sqrt2b / sqrt2a - dy * sqrt2b / sqrt2a ) * sqrt2b / sqrt2a;
        // int256 dy1 = (dx * sqrt2b / sqrt2a + dy * sqrt2b / sqrt2a ) * sqrt2b / sqrt2a;
        // int256 dx2 = (dx * sqrt2b / sqrt2a + dy * sqrt2b / sqrt2a ) * sqrt2b / sqrt2a;
        // int256 dy2 =  (-dx * sqrt2b / sqrt2a + dy * sqrt2b / sqrt2a) * sqrt2b / sqrt2a;
        int256 dx1;
        int256 dy1;
        int256 dx2;
        int256 dy2;

        assembly {
        	dx1 := sdiv(mul(sub(sdiv(mul(dx, sqrt2b), sqrt2a),sdiv(mul(dy, sqrt2b),sqrt2a)),sqrt2b),sqrt2a)
        	dy1 := sdiv(mul(add(sdiv(mul(dx, sqrt2b), sqrt2a),sdiv(mul(dy, sqrt2b),sqrt2a)),sqrt2b),sqrt2a)
        	dx2 := sdiv(mul(add(sdiv(mul(dx, sqrt2b), sqrt2a),sdiv(mul(dy, sqrt2b),sqrt2a)),sqrt2b),sqrt2a)
        	dy2 := sdiv(mul(add(sdiv(mul(add(1,not(dx)), sqrt2b), sqrt2a),sdiv(mul(dy, sqrt2b),sqrt2a)),sqrt2b),sqrt2a)
        }


        // reuse x,y,dx,dy for return. Not enough local variables
        if (leftChild && (curtype == 3) || 
            !leftChild && (curtype == 0 || curtype == 4 || curtype == 8)) {
        	assembly {
        		x := add(x, dx)
        		y := add(y, dy)
        	}
        } else if (curtype == 6 || 
        		  !leftChild && (curtype == 1 || curtype == 3)) {
        	assembly {
        		x := add(x, dx1)
        		y := add(y, dy1)
        	}
        } else if (!leftChild && (curtype == 2 || curtype == 5 || curtype == 7)) {
        	assembly {
        		x := add(x, dx2)
        		y := add(y, dy2)
        	}
        }

        if (leftChild && (curtype == 0 || curtype == 1 || curtype == 4 || curtype == 5 || curtype == 7) ||
        	!leftChild && (curtype == 2 || curtype == 5)) {
        	dx = dx1;
        	dy = dy1;

        }  else if (leftChild && (curtype == 6) ||
        			!leftChild && (curtype == 3 || curtype == 4)) {
        	assembly {
        		dx := add(1, not(dx1))
        		dy := add(1, not(dy1))
        	}

        } else if (leftChild && (curtype == 2 || curtype == 8) || 
        		  !leftChild && (curtype == 1 || curtype == 6)) {
    		dx = dx2;
    		dy = dy2;

        } else if (leftChild && (curtype == 3) || 
        			!leftChild && (curtype == 0 || curtype == 7 || curtype == 8)) {
        	assembly {
        		dx := add(1, not(dx2))
        		dy := add(1, not(dy2))
        	}
        } 

    	return compact(seed, curtype, subiteration-1, x, y, dx, dy);
    }