contract MEMELtdPoolV2MintFee is MemeTokenWrapper, Ownable, Pausable {
	using SafeMath for uint256;
	ERC1155Tradable public memeLtd;

	struct Card {
		uint256 points;
		uint256 releaseTime;
		uint256 mintFee;
	}

	uint256 public periodStart;
	uint256 public maxStake;
	uint256 public rewardRate = 11574074074000; // 1 point per day per staked MEME
	uint256 public totalFeesCollected;
	uint256 public spentPineapples;
	uint256 public controllerShare;
	address public rescuer;
	address public artist;
	address public controller;

	mapping(address => uint256) public pendingWithdrawals;
	mapping(address => uint256) public lastUpdateTime;
	mapping(address => uint256) public points;
	mapping(uint256 => Card) public cards;

	event CardAdded(uint256 card, uint256 points, uint256 mintFee, uint256 releaseTime);
	event Staked(address indexed user, uint256 amount);
	event Withdrawn(address indexed user, uint256 amount);
	event Redeemed(address indexed user, uint256 amount);

	modifier updateReward(address account) {
		if (account != address(0)) {
			points[account] = earned(account);
			lastUpdateTime[account] = block.timestamp;
		}
		_;
	}

	constructor(
		uint256 _periodStart,
		uint256 _maxStake,
		uint256 _controllerShare,
		address _artist,
		address _controller,
		ERC1155Tradable _memeLtdAddress,
		IERC20 _memeAddress
	) public MemeTokenWrapper(_memeAddress) {
		periodStart = _periodStart;
		maxStake = _maxStake;
		controllerShare = _controllerShare;
		artist = _artist;
		controller = _controller;
		memeLtd = _memeLtdAddress;
	}

	function cardMintFee(uint256 id) public view returns (uint256) {
		return cards[id].mintFee;
	}

	function cardReleaseTime(uint256 id) public view returns (uint256) {
		return cards[id].releaseTime;
	}

	function cardPoints(uint256 id) public view returns (uint256) {
		return cards[id].points;
	}

	function earned(address account) public view returns (uint256) {
		uint256 blockTime = block.timestamp;
		return
			balanceOf(account).mul(blockTime.sub(lastUpdateTime[account]).mul(rewardRate)).div(1e8).add(
				points[account]
			);
	}

	// stake visibility is public as overriding MemeTokenWrapper's stake() function
	function stake(uint256 amount) public updateReward(msg.sender) whenNotPaused() {
		require(block.timestamp >= periodStart, "Pool not open");
		require(amount.add(balanceOf(msg.sender)) <= maxStake, "Deposit 5 MEME or less, no whales");

		super.stake(amount);
		emit Staked(msg.sender, amount);
	}

	// override MemeTokenWrapper's withdraw() function
	function withdraw(uint256 amount) public updateReward(msg.sender) {
		require(amount > 0, "Cannot withdraw 0");

		super.withdraw(amount);
		emit Withdrawn(msg.sender, amount);
	}

	function exit() external {
		withdraw(balanceOf(msg.sender));
	}

	function redeem(uint256 id) public payable updateReward(msg.sender) {
		require(cards[id].points != 0, "Card not found");
		require(block.timestamp >= cards[id].releaseTime, "Card not released");
		require(points[msg.sender] >= cards[id].points, "Redemption exceeds point balance");
		require(msg.value == cards[id].mintFee, "Support our artists. Send the proper ETH");

		if (cards[id].mintFee > 0) {
			uint256 _controllerShare = msg.value.mul(controllerShare).div(1000);
			uint256 _artistRoyalty = msg.value.sub(_controllerShare);
			require(_artistRoyalty.add(_controllerShare) == msg.value, "problem with fee");

			totalFeesCollected = totalFeesCollected.add(cards[id].mintFee);
			pendingWithdrawals[controller] = pendingWithdrawals[controller].add(_controllerShare);
			pendingWithdrawals[artist] = pendingWithdrawals[artist].add(_artistRoyalty);
		}

		points[msg.sender] = points[msg.sender].sub(cards[id].points);
		spentPineapples = spentPineapples.add(cards[id].points);
		memeLtd.mint(msg.sender, id, 1, "");
		emit Redeemed(msg.sender, cards[id].points);
	}

	function rescuePineapples(address account) public updateReward(account) returns (uint256) {
		require(msg.sender == rescuer, "!rescuer");
		uint256 earnedPoints = points[account];
		spentPineapples = spentPineapples.add(earnedPoints);
		points[account] = 0;

		// transfer remaining MEME to the account
		if (balanceOf(account) > 0) {
			_rescuePineapples(account);
		}

		emit Redeemed(account, earnedPoints);
		return earnedPoints;
	}

	function setArtist(address _artist) public onlyOwner {
		uint256 amount = pendingWithdrawals[artist];
		pendingWithdrawals[artist] = 0;
		pendingWithdrawals[_artist] = pendingWithdrawals[_artist].add(amount);
		artist = _artist;
	}

	function setController(address _controller) public onlyOwner {
		uint256 amount = pendingWithdrawals[controller];
		pendingWithdrawals[controller] = 0;
		pendingWithdrawals[_controller] = pendingWithdrawals[_controller].add(amount);
		controller = _controller;
	}

	function setRescuer(address _rescuer) public onlyOwner {
		rescuer = _rescuer;
	}

	function setControllerShare(uint256 _controllerShare) public onlyOwner {
		controllerShare = _controllerShare;
	}

	function createCard(
		uint256 _supply,
		uint256 _points,
		uint256 _mintFee,
		uint256 _releaseTime
	) public onlyOwner returns (uint256) {
		uint256 tokenId = memeLtd.create(_supply, 0, "", "");
		require(tokenId > 0, "ERC1155 create did not succeed");

		Card storage c = cards[tokenId];
		c.points = _points;
		c.releaseTime = _releaseTime;
		c.mintFee = _mintFee;
		emit CardAdded(tokenId, _points, _mintFee, _releaseTime);
		return tokenId;
	}

	function withdrawFee() public {
		require(msg.sender == artist || msg.sender == controller, "!artist or !controller");
		uint256 amount = pendingWithdrawals[msg.sender];
		require(amount > 0, "nothing to withdraw");
		pendingWithdrawals[msg.sender] = 0;
		msg.sender.transfer(amount);
	}
}
function stake(uint256 amount) public updateReward(msg.sender) whenNotPaused() {
	require(block.timestamp >= periodStart, "Pool not open");
	require(amount.add(balanceOf(msg.sender)) <= maxStake, "Deposit 5 MEME or less, no whales");

	super.stake(amount);
	emit Staked(msg.sender, amount);
}

// stake visibility is public as overriding MemeTokenWrapper's stake() function