//License-Identifier: MIT
pragma solidity ^0.8.9;




contract GB is ERC721Enumerable, ERC721URIStorage, Ownable, AccessControl {

    modifier onlyTokenOwner(uint tid) {
        require(ownerOf(tid) == msg.sender, "GreedyBoys: only token owner can call this");
        _;
    }

    modifier tokensAreInit(uint tid1, uint tid2) {
        require(isInit[tid1] && isInit[tid2], "GreedyBoys: both tokens must be init");
        _;
    }

    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant FRONT_ROLE = keccak256("FRONT_ROLE");

	string public constant BASE_URL = "https://arweave.net/";
	uint public constant AR_HASH_SIZE = 43;

	// Controller
	address public controller;									        // contract controller

    // Fees
    address public collector;                                           // fee collector
    uint public feeBps;                                                 // fee percent

	// Tokenomics
	uint public maxTokens;										        // max token count, set at construction;

	// Metadata
	mapping (uint => bool) public isInit;						        // token id => is initialized
	mapping (uint => string) private tokenURIs;					        // token id => token URI

	// Minters
	mapping (uint => address) public minters;					        // token id => address

	// GB params
	mapping (string => mapping (uint => uint)) private params;	        // index name => token id => index value

	event Minted(uint tokenId, address to);
	event Init(uint tokenId, address by, string metadata, uint flair, uint rarity, uint bgdnoff);
	event Corrected(uint tokenId, string oldMetadata, string newMetadata);

	constructor(uint _maxTokens, address ownerAddr)
	ERC721("GreedyBoys", "GB")
	{
		maxTokens = _maxTokens;
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(ADMIN_ROLE, msg.sender);
        transferOwnership(ownerAddr);
	}

	// public

	function tokenURI(uint tokenId)
	public view override(ERC721URIStorage, ERC721)
	returns (string memory)
	{
		require(isInit[tokenId], "GreedyBoys: token URI is not initialised yet");
		return string(abi.encodePacked(BASE_URL, tokenURIs[tokenId]));
	}

	function supportsInterface(bytes4 interfaceId)
	public view override(ERC721Enumerable, ERC721, AccessControl)
	returns (bool)
	{
		return ERC721Enumerable.supportsInterface(interfaceId);
	}

	function getParam(uint tokenId, string calldata paramName)
	public view
	returns (uint paramValue)
	{
		paramValue = params[paramName][tokenId];
	}

	// external only admin

	function correct(uint tokenId, string calldata _metadata, uint flair, uint rarity, uint bgdnoff)
	external
	onlyRole(ADMIN_ROLE)
	{
		require(_exists(tokenId) && isInit[tokenId], "GreedyBoys: cannot correct non-existing or non-init token");
		string memory oldMetadata = tokenURI(tokenId);
		_setTokenURI(tokenId, _metadata);
		params['rarity'][tokenId] = rarity;
		params['flair'][tokenId] = flair;
		params['bogdanoff_index'][tokenId] = bgdnoff;
		emit Corrected(tokenId, oldMetadata, _metadata);
	}

    function setCollectionFeeBps(uint bps)
    external
    onlyRole(ADMIN_ROLE)
    {
        require(bps >= 0 && bps <= 10000, "GreedyBoys: fee percent must be in [0, 100]");
        feeBps = bps;
    }

    function setCollector(address collectorAddr)
    external
    onlyRole(ADMIN_ROLE)
    {
        collector = collectorAddr;
    }

	function setController(address controllerAddr)
	external
    onlyRole(ADMIN_ROLE)
	{
		controller = controllerAddr;
        grantRole(FRONT_ROLE, controllerAddr);
	}

	function setParam(uint tokenId, string calldata paramName, uint paramValue)
	external
	onlyRole(ADMIN_ROLE)
	{
		require(_exists(tokenId) && isInit[tokenId]);
		require(params[paramName][tokenId] == 0);
		params[paramName][tokenId] = paramValue;
	}

	// external only controller

	function mint(address to, uint tokenId)
	external
	onlyRole(FRONT_ROLE)
	{
		require(totalSupply() <= maxTokens, "GreedyBoys: max supply reached");
		require(tokenId <= maxTokens, "GreedyBoys: tokenId must be <= maxTokens");
		minters[tokenId] = to;
		_safeMint(to, tokenId);
		emit Minted(tokenId, to);
	}

	function init(uint tokenId, string calldata _metadata, uint flair, uint rarity, uint bgdnoff)
	external
	onlyRole(FRONT_ROLE)
	{
		require(_exists(tokenId) && !isInit[tokenId], "GreedyBoys: cannot init non-existing or already init token");
		isInit[tokenId] = true;
		_setTokenURI(tokenId, _metadata);
		params['rarity'][tokenId] = rarity;
		params['flair'][tokenId] = flair;
		params['bogdanoff_index'][tokenId] = bgdnoff;
		emit Init(tokenId, msg.sender, _metadata, flair, rarity, bgdnoff);
	}

	//
	// internal
	//

	function _baseURI()
	internal override pure
	returns (string memory)
	{
		return BASE_URL;
	}

	function _setTokenURI(uint256 tokenId, string memory _tokenURI)
	internal override
	{
		require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
		tokenURIs[tokenId] = _tokenURI;
	}

	function _burn(uint tokenId)
	internal override(ERC721URIStorage, ERC721)
	{
		isInit[tokenId] = false;
		tokenURIs[tokenId] = "";
		ERC721URIStorage._burn(tokenId);
	}

	function _beforeTokenTransfer(address from, address to, uint tokenId)
	internal override(ERC721Enumerable, ERC721)
	{
		ERC721Enumerable._beforeTokenTransfer(from, to, tokenId);
	}

}
[{"inputs":[{"internalType":"uint256","name":"_maxTokens","type":"uint256"},{"internalType":"address","name":"ownerAddr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"string","name":"oldMetadata","type":"string"},{"indexed":false,"internalType":"string","name":"newMetadata","type":"string"}],"name":"Corrected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"string","name":"metadata","type":"string"},{"indexed":false,"internalType":"uint256","name":"flair","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rarity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bgdnoff","type":"uint256"}],"name":"Init","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"Minted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"AR_HASH_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BASE_URL","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FRONT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collector","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"_metadata","type":"string"},{"internalType":"uint256","name":"flair","type":"uint256"},{"internalType":"uint256","name":"rarity","type":"uint256"},{"internalType":"uint256","name":"bgdnoff","type":"uint256"}],"name":"correct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeBps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"paramName","type":"string"}],"name":"getParam","outputs":[{"internalType":"uint256","name":"paramValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"_metadata","type":"string"},{"internalType":"uint256","name":"flair","type":"uint256"},{"internalType":"uint256","name":"rarity","type":"uint256"},{"internalType":"uint256","name":"bgdnoff","type":"uint256"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"isInit","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"minters","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bps","type":"uint256"}],"name":"setCollectionFeeBps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collectorAddr","type":"address"}],"name":"setCollector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controllerAddr","type":"address"}],"name":"setController","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"paramName","type":"string"},{"internalType":"uint256","name":"paramValue","type":"uint256"}],"name":"setParam","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]