// File: Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
	function _msgSender() internal view virtual returns (address) {
		return msg.sender;
	}

	function _msgData() internal view virtual returns (bytes calldata) {
		return msg.data;
	}
}

// File: ITraits.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.0;

interface ITraits {
	function tokenURI(uint256 tokenId) external view returns (string memory);
}

// File: IWoolf.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.0;

interface IWoolf {
	// struct to store each token's traits
	struct ApeWolf {
		bool isApe;
		uint8 skin;
		uint8 eyes;
		uint8 mouth;
		uint8 clothing;
		uint8 headwear;
		uint8 alphaIndex;
	}

	function getPaidTokens() external view returns (uint256);

	function getTokenTraits(uint256 tokenId) external view returns (ApeWolf memory);

	function ownerOf(uint256 tokenId) external view returns (address owner);

	function transferFrom(
		address from,
		address to,
		uint256 tokenId
	) external;

	function safeTransferFrom(
		address from,
		address to,
		uint256 tokenId,
		bytes calldata data
	) external;
}

// File: Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
	address private _owner;

	event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

	/**
	 * @dev Initializes the contract setting the deployer as the initial owner.
	 */
	constructor() {
		_setOwner(_msgSender());
	}

	/**
	 * @dev Returns the address of the current owner.
	 */
	function owner() public view virtual returns (address) {
		return _owner;
	}

	/**
	 * @dev Throws if called by any account other than the owner.
	 */
	modifier onlyOwner() {
		require(owner() == _msgSender(), "Ownable: caller is not the owner");
		_;
	}

	/**
	 * @dev Leaves the contract without owner. It will not be possible to call
	 * `onlyOwner` functions anymore. Can only be called by the current owner.
	 *
	 * NOTE: Renouncing ownership will leave the contract without an owner,
	 * thereby removing any functionality that is only available to the owner.
	 */
	function renounceOwnership() public virtual onlyOwner {
		_setOwner(address(0));
	}

	/**
	 * @dev Transfers ownership of the contract to a new account (`newOwner`).
	 * Can only be called by the current owner.
	 */
	function transferOwnership(address newOwner) public virtual onlyOwner {
		require(newOwner != address(0), "Ownable: new owner is the zero address");
		_setOwner(newOwner);
	}

	function _setOwner(address newOwner) private {
		address oldOwner = _owner;
		_owner = newOwner;
		emit OwnershipTransferred(oldOwner, newOwner);
	}
}

// File: Strings.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @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);
	}

	/**
	 * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
	 */
	function toHexString(uint256 value) internal pure returns (string memory) {
		if (value == 0) {
			return "0x00";
		}
		uint256 temp = value;
		uint256 length = 0;
		while (temp != 0) {
			length++;
			temp >>= 8;
		}
		return toHexString(value, length);
	}

	/**
	 * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
	 */
	function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
		bytes memory buffer = new bytes(2 * length + 2);
		buffer[0] = "0";
		buffer[1] = "x";
		for (uint256 i = 2 * length + 1; i > 1; --i) {
			buffer[i] = _HEX_SYMBOLS[value & 0xf];
			value >>= 4;
		}
		require(value == 0, "Strings: hex length insufficient");
		return string(buffer);
	}
}

// File: Traits.sol



// SPDX-License-Identifier: MIT LICENSE

pragma solidity ^0.8.0;
import "./Ownable.sol";
import "./Strings.sol";
import "./ITraits.sol";
import "./IWoolf.sol";

contract Traits is Ownable, ITraits {
	using Strings for uint256;

	// struct to store each trait's data for metadata and rendering
	struct Trait {
		string name;
		string png;
	}
	string private baseURI;
	uint256 public number;

	// mapping from trait type (index) to its name
	string[6] _traitTypes = ["Skin", "Eyes", "Mouth", "clothing", "Headwear", "Alpha"];
	// storage of each traits name and base64 PNG data
	mapping(uint8 => mapping(uint8 => Trait)) public traitData;
	// mapping from alphaIndex to its score
	string[4] _alphas = ["8", "7", "6", "5"];

	IWoolf public woolf;

	constructor() {}

	/** ADMIN */

	function setWoolf(address _woolf) external onlyOwner {
		woolf = IWoolf(_woolf);
	}

	/**
	 * administrative to upload the names and images associated with each trait
	 * @param traitType the trait type to upload the traits for (see traitTypes for a mapping)
	 * @param traits the names and base64 encoded PNGs for each trait
	 */
	function uploadTraits(
		uint8 traitType,
		uint8[] calldata traitIds,
		Trait[] calldata traits
	) external onlyOwner {
		require(traitIds.length == traits.length, "Mismatched inputs");
		for (uint256 i = 0; i < traits.length; i++) {
			traitData[traitType][traitIds[i]] = Trait(traits[i].name, traits[i].png);
		}
	}

	/** RENDER */

	/**
	 * generates an attribute for the attributes array in the ERC721 metadata standard
	 * @param traitType the trait type to reference as the metadata key
	 * @param value the token's trait associated with the key
	 * @return a JSON dictionary for the single attribute
	 */
	function attributeForTypeAndValue(string memory traitType, string memory value) internal pure returns (string memory) {
		return string(abi.encodePacked('{"trait_type":"', traitType, '","value":"', value, '"}'));
	}

	/**
	 * generates an array composed of all the individual traits and values
	 * @param tokenId the ID of the token to compose the metadata for
	 * @return a JSON array of all of the attributes for given token ID
	 */
	function compileAttributes(uint256 tokenId) public view returns (string memory) {
		IWoolf.ApeWolf memory s = woolf.getTokenTraits(tokenId);
		string memory traits;
		if (s.isApe) {
			traits = string(
				abi.encodePacked(
					attributeForTypeAndValue(_traitTypes[0], traitData[0][s.skin].name),
					",",
					attributeForTypeAndValue(_traitTypes[1], traitData[1][s.eyes].name),
					",",
					attributeForTypeAndValue(_traitTypes[2], traitData[2][s.mouth].name),
					",",
					attributeForTypeAndValue(_traitTypes[4], traitData[4][s.headwear].name),
					","
				)
			);
		} else {
			traits = string(
				abi.encodePacked(
					attributeForTypeAndValue(_traitTypes[0], traitData[6][s.alphaIndex].name),
					",",
					attributeForTypeAndValue(_traitTypes[1], traitData[7][s.eyes].name),
					",",
					attributeForTypeAndValue(_traitTypes[2], traitData[8][s.mouth].name),
					",",
					attributeForTypeAndValue(_traitTypes[3], traitData[9][s.clothing].name),
					",",
					attributeForTypeAndValue(_traitTypes[4], traitData[10][s.headwear].name),
					",",
					attributeForTypeAndValue("Alpha Score", _alphas[s.alphaIndex]),
					","
				)
			);
		}
		return
			string(
				abi.encodePacked(
					"[",
					traits,
					'{"trait_type":"Generation","value":',
					tokenId <= woolf.getPaidTokens() ? '"Gen 0"' : '"Gen 1"',
					'},{"trait_type":"Type","value":',
					s.isApe ? '"Ape"' : '"Wolf"',
					"}]"
				)
			);
	}

	function setBaseURI(string calldata _baseURI) external onlyOwner {
		baseURI = _baseURI;
	}

	function getBaseURI() public view returns (string memory) {
		return baseURI;
	}

	function setNumber(uint256 _number) external onlyOwner {
		number = _number;
	}

	/**
	 * generates a base64 encoded metadata response without referencing off-chain content
	 * @param tokenId the ID of the token to generate the metadata for
	 * @return a base64 encoded JSON dictionary of the token's metadata and SVG
	 */
	function tokenURI(uint256 tokenId) public view override returns (string memory) {
		IWoolf.ApeWolf memory s = woolf.getTokenTraits(tokenId);

		string memory metadata = string(
			abi.encodePacked(
				'{"name": "',
				s.isApe ? "Ape #" : "Wolf #",
				tokenId.toString(),
				'", "description": "Thousands of Ape and Wolves compete on a farm in the metaverse. A tempting prize of $MPeach awaits, with deadly high stakes. All the metadata and images are generated and stored 100% on-chain. No IPFS. NO API. Just the Ethereum blockchain.", "image": "',
				getBaseURI(),
				number >= tokenId ? tokenId.toString() : "0",
				'.png", "attributes":',
				compileAttributes(tokenId),
				"}"
			)
		);

		return string(abi.encodePacked("data:application/json;base64,", base64(bytes(metadata))));
	}

	/** BASE 64 - Written by Brech Devos */

	string internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

	function base64(bytes memory data) internal pure returns (string memory) {
		if (data.length == 0) return "";

		// load the table into memory
		string memory table = TABLE;

		// multiply by 4/3 rounded up
		uint256 encodedLen = 4 * ((data.length + 2) / 3);

		// add some extra buffer at the end required for the writing
		string memory result = new string(encodedLen + 32);

		assembly {
			// set the actual output length
			mstore(result, encodedLen)

			// prepare the lookup table
			let tablePtr := add(table, 1)

			// input ptr
			let dataPtr := data
			let endPtr := add(dataPtr, mload(data))

			// result ptr, jump over length
			let resultPtr := add(result, 32)

			// run over the input, 3 bytes at a time
			for {

			} lt(dataPtr, endPtr) {

			} {
				dataPtr := add(dataPtr, 3)

				// read 3 bytes
				let input := mload(dataPtr)

				// write 4 characters
				mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F)))))
				resultPtr := add(resultPtr, 1)
				mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F)))))
				resultPtr := add(resultPtr, 1)
				mstore(resultPtr, shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F)))))
				resultPtr := add(resultPtr, 1)
				mstore(resultPtr, shl(248, mload(add(tablePtr, and(input, 0x3F)))))
				resultPtr := add(resultPtr, 1)
			}

			// padding with '='
			switch mod(mload(data), 3)
			case 1 {
				mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
			}
			case 2 {
				mstore(sub(resultPtr, 1), shl(248, 0x3d))
			}
		}

		return result;
	}
}