library SVG {

	using Strings for uint256;

	/// Returns a named element based on the supplied attributes and contents
	/// @dev attributes and contents is usually generated from abi.encodePacked, attributes is expecting a leading space
	/// @param name The name of the element
	/// @param attributes The attributes of the element, as bytes, with a leading space
	/// @param contents The contents of the element, as bytes
	/// @return a bytes collection representing the whole element
	function createElement(string memory name, bytes memory attributes, bytes memory contents) internal pure returns (bytes memory) {
		return abi.encodePacked(
			"<", attributes.length == 0 ? bytes(name) : abi.encodePacked(name, attributes),
			contents.length == 0 ? bytes("/>") : abi.encodePacked(">", contents, "</", name, ">")
		);
	}

	/// Returns the root SVG attributes based on the supplied width and height
	/// @dev includes necessary leading space for createElement's `attributes` parameter
	/// @param width The width of the SVG view box
	/// @param height The height of the SVG view box
	/// @return a bytes collection representing the root SVG attributes, including a leading space
	function svgAttributes(uint256 width, uint256 height) internal pure returns (bytes memory) {
		return abi.encodePacked(" viewBox='0 0 ", width.toString(), " ", height.toString(), "' xmlns='http://www.w3.org/2000/svg'");
	}

	/// Returns an RGB bytes collection suitable as an attribute for SVG elements based on the supplied Color and ColorType
	/// @dev includes necessary leading space for all types _except_ None
	/// @param attribute The `ISVGTypes.ColorAttribute` of the desired attribute
	/// @param value The converted color value as bytes
	/// @return a bytes collection representing a color attribute in an SVG element
	function colorAttribute(ISVGTypes.ColorAttribute attribute, bytes memory value) internal pure returns (bytes memory) {
		if (attribute == ISVGTypes.ColorAttribute.Fill) return _attribute("fill", value);
		if (attribute == ISVGTypes.ColorAttribute.Stop) return _attribute("stop-color", value);
		return  _attribute("stroke", value); // Fallback to Stroke
	}

	/// Returns an RGB color attribute value
	/// @param color The `ISVGTypes.Color` of the color
	/// @return a bytes collection representing the url attribute value
	function colorAttributeRGBValue(ISVGTypes.Color memory color) internal pure returns (bytes memory) {
		return _colorValue(ISVGTypes.ColorAttributeKind.RGB, OnChain.commaSeparated(
			bytes(uint256(color.red).toString()),
			bytes(uint256(color.green).toString()),
			bytes(uint256(color.blue).toString())
		));
	}

	/// Returns a URL color attribute value
	/// @param url The url to the color
	/// @return a bytes collection representing the url attribute value
	function colorAttributeURLValue(bytes memory url) internal pure returns (bytes memory) {
		return _colorValue(ISVGTypes.ColorAttributeKind.URL, url);
	}

	/// Returns an `ISVGTypes.Color` that is brightened by the provided percentage
	/// @param source The `ISVGTypes.Color` to brighten
	/// @param percentage The percentage of brightness to apply
	/// @param minimumBump A minimum increase for each channel to ensure dark Colors also brighten
	/// @return color the brightened `ISVGTypes.Color`
	function brightenColor(ISVGTypes.Color memory source, uint32 percentage, uint8 minimumBump) internal pure returns (ISVGTypes.Color memory color) {
		color.red = _brightenComponent(source.red, percentage, minimumBump);
		color.green = _brightenComponent(source.green, percentage, minimumBump);
		color.blue = _brightenComponent(source.blue, percentage, minimumBump);
		color.alpha = source.alpha;
	}

	/// Returns an `ISVGTypes.Color` based on a packed representation of r, g, and b
	/// @notice Useful for code where you want to utilize rgb hex values provided by a designer (e.g. #835525)
	/// @dev Alpha will be hard-coded to 100% opacity
	/// @param packedColor The `ISVGTypes.Color` to convert, e.g. 0x835525
	/// @return color representing the packed input
	function fromPackedColor(uint24 packedColor) internal pure returns (ISVGTypes.Color memory color) {
		color.red = uint8(packedColor >> 16);
		color.green = uint8(packedColor >> 8);
		color.blue = uint8(packedColor);
		color.alpha = 0xFF;
	}

	/// Returns a mixed Color by balancing the ratio of `color1` over `color2`, with a total percentage (for overmixing and undermixing outside the source bounds)
	/// @dev Reverts with `RatioInvalid()` if `ratioPercentage` is > 100
	/// @param color1 The first `ISVGTypes.Color` to mix
	/// @param color2 The second `ISVGTypes.Color` to mix
	/// @param ratioPercentage The percentage ratio of `color1` over `color2` (e.g. 60 = 60% first, 40% second)
	/// @param totalPercentage The total percentage after mixing (for overmixing and undermixing outside the input colors)
	/// @return color representing the result of the mixture
	function mixColors(ISVGTypes.Color memory color1, ISVGTypes.Color memory color2, uint32 ratioPercentage, uint32 totalPercentage) internal pure returns (ISVGTypes.Color memory color) {
		if (ratioPercentage > 100) revert RatioInvalid();
		color.red = _mixComponents(color1.red, color2.red, ratioPercentage, totalPercentage);
		color.green = _mixComponents(color1.green, color2.green, ratioPercentage, totalPercentage);
		color.blue = _mixComponents(color1.blue, color2.blue, ratioPercentage, totalPercentage);
		color.alpha = _mixComponents(color1.alpha, color2.alpha, ratioPercentage, totalPercentage);
	}

	/// Returns a proportionally-randomized Color between the start and stop colors using a random Color seed
	/// @dev Each component (r,g,b) will move proportionally together in the direction from start to stop
	/// @param start The starting bound of the `ISVGTypes.Color` to randomize
	/// @param stop The stopping bound of the `ISVGTypes.Color` to randomize
	/// @param random An `ISVGTypes.Color` to use as a seed for randomization
	/// @return color representing the result of the randomization
	function randomizeColors(ISVGTypes.Color memory start, ISVGTypes.Color memory stop, ISVGTypes.Color memory random) internal pure returns (ISVGTypes.Color memory color) {
		uint16 percent = uint16((1320 * (uint(random.red) + uint(random.green) + uint(random.blue)) / 10000) % 101); // Range is from 0-100
		color.red = _randomizeComponent(start.red, stop.red, random.red, percent);
		color.green = _randomizeComponent(start.green, stop.green, random.green, percent);
		color.blue = _randomizeComponent(start.blue, stop.blue, random.blue, percent);
		color.alpha = 0xFF;
	}

	function _attribute(bytes memory name, bytes memory contents) private pure returns (bytes memory) {
		return abi.encodePacked(" ", name, "='", contents, "'");
	}

	function _brightenComponent(uint8 component, uint32 percentage, uint8 minimumBump) private pure returns (uint8 result) {
		uint32 wideComponent = uint32(component);
		uint32 brightenedComponent = wideComponent * (percentage + 100) / 100;
		uint32 wideMinimumBump = uint32(minimumBump);
		if (brightenedComponent - wideComponent < wideMinimumBump) {
			brightenedComponent = wideComponent + wideMinimumBump;
		}
		if (brightenedComponent > 0xFF) {
			result = 0xFF; // Clamp to 8 bits
		} else {
			result = uint8(brightenedComponent);
		}
	}

	function _colorValue(ISVGTypes.ColorAttributeKind attributeKind, bytes memory contents) private pure returns (bytes memory) {
		return abi.encodePacked(attributeKind == ISVGTypes.ColorAttributeKind.RGB ? "rgb(" : "url(#", contents, ")");
	}

	function _mixComponents(uint8 component1, uint8 component2, uint32 ratioPercentage, uint32 totalPercentage) private pure returns (uint8 component) {
		uint32 mixedComponent = (uint32(component1) * ratioPercentage + uint32(component2) * (100 - ratioPercentage)) * totalPercentage / 10000;
		if (mixedComponent > 0xFF) {
			component = 0xFF; // Clamp to 8 bits
		} else {
			component = uint8(mixedComponent);
		}
	}

	function _randomizeComponent(uint8 start, uint8 stop, uint8 random, uint16 percent) private pure returns (uint8 component) {
		if (start == stop) {
			component = start;
		} else { // This is the standard case
			(uint8 floor, uint8 ceiling) = start < stop ? (start, stop) : (stop, start);
			component = floor + uint8(uint16(ceiling - (random & 0x01) - floor) * percent / uint16(100));
		}
	}
}
/**
 * @dev These methods are best suited towards view/pure only function calls (ALL the way through the call stack).
 * Do not waste gas using these methods in functions that also update state, unless your need requires it.
 */
function brightenColor(ISVGTypes.Color memory source, uint32 percentage, uint8 minimumBump) internal pure returns (ISVGTypes.Color memory color) {
	color.red = _brightenComponent(source.red, percentage, minimumBump);
	color.green = _brightenComponent(source.green, percentage, minimumBump);
	color.blue = _brightenComponent(source.blue, percentage, minimumBump);
	color.alpha = source.alpha;
}

/// Returns an `ISVGTypes.Color` that is brightened by the provided percentage
/// @param source The `ISVGTypes.Color` to brighten
/// @param percentage The percentage of brightness to apply
/// @param minimumBump A minimum increase for each channel to ensure dark Colors also brighten
/// @return color the brightened `ISVGTypes.Color`