contract GlitchClocks is ERC721, ERC721Burnable, ERC721Enumerable, Ownable {
	using Strings for uint256;

	string private _baseTokenURI;
	string private _baseTokenExtension = ".json";

	uint256 public constant NFT_COST = 0.025 ether;
	uint256 public constant MAX_SUPPLY = 10080;
	uint256 private constant MAX_MINT_PER_CALL = 25;

	bool public isSaleActive = false;
	bool public isDestroyClockActive = false;
	bool public isReflectionsActive = false;
	string public provenanceHash = "";

	mapping(uint256 => uint256) private _availableDistributions;
	mapping(address => uint256) private _burnCounts;

	event EthDeposited(uint256 amount);
	event EthClaimed(address to, uint256 amount);
	event ClockDestroyed(uint256 tokenId);

	constructor(string memory name, string memory symbol, string memory initBaseURI) ERC721(name, symbol) {
		setBaseURI(initBaseURI);
	}

	modifier saleActive {
		require(isSaleActive, "Sale has not started");
		_;
	}

	modifier destroyClockActive {
		require(isReflectionsActive, "Destroy Clock is not active");
		_;
	}

	modifier reflectionsActive {
		require(isReflectionsActive, "Reflection is not active");
		_;
	}

	function getProvenanceHash() public view returns (string memory) {
		return provenanceHash;
	}

	function setProvenanceHash(string memory newProvenanceHash) public onlyOwner {
		provenanceHash = newProvenanceHash;
	}

	function mintClocks(uint256 numNFTs) public payable saleActive {
		require(MAX_SUPPLY > totalSupply(), "Sale has ended");
		require(
			numNFTs > 0 && numNFTs <= MAX_MINT_PER_CALL,
			"Exceeds MAX_MINT_PER_CALL"
		);
		require(
			MAX_SUPPLY >= totalSupply() + numNFTs,
			"Exceeds MAX_SUPPLY"
		);
		require(msg.value >= NFT_COST * numNFTs, "Incorrect Ether value");

		for (uint256 i = 0; i < numNFTs; i++) {
			uint256 mintIndex = totalSupply() + 1;
			_safeMint(msg.sender, mintIndex);
		}
	}

	// Used for Giveaways
	function reserveMint(uint256 reservedAmount) public onlyOwner {
		uint beginSupply = totalSupply();
		for (uint256 i = 1; i <= reservedAmount; i++) {
			_safeMint(msg.sender, beginSupply + i);
		}
	}

	// Used for Giveaways
	function reserveMint(uint256 reservedAmount, address mintAddress) public onlyOwner {
		uint beginSupply = totalSupply();
		for (uint256 i = 1; i <= reservedAmount; i++) {
			_safeMint(mintAddress, beginSupply + i);
		}
	}

	// Used for Giveaways
	function reserveMintBulk(address[] calldata mintAddresses) public onlyOwner {
		uint beginSupply = totalSupply();
		for (uint256 i = 0; i < mintAddresses.length; i++) {
			_safeMint(mintAddresses[i], beginSupply + 1 + i);
		}
	}

	function deposit() public payable reflectionsActive onlyOwner {
		uint256 tokenCount = totalSupply();
		uint256 amountPerToken = msg.value / tokenCount;

		for(uint256 i = 0; i < tokenCount; i++) {
			_availableDistributions[tokenByIndex(i)] += amountPerToken;
		}
		emit EthDeposited(msg.value);
	}

	function getAvailableDistributions(uint256 tokenId) public view returns (uint256) {
		return _availableDistributions[tokenId];
	}

	function getTotalAvailableDistributions(address owner) public view reflectionsActive returns (uint256) {
		uint256 totalDistrubtions = 0;
		uint256 numTokens = balanceOf(owner);

		for(uint256 i = 0; i < numTokens; i++) {
			totalDistrubtions += getAvailableDistributions(tokenOfOwnerByIndex(owner, i));
		}

		return totalDistrubtions;
	}

	function claim() public reflectionsActive {
		uint256 availableAmount = 0;
		uint256 numTokens = balanceOf(msg.sender);

		for(uint256 i = 0; i < numTokens; i++) {
			uint256 tokenId = tokenOfOwnerByIndex(msg.sender, i);
			availableAmount += _availableDistributions[tokenId];
			_availableDistributions[tokenId] = 0;
		}

		require(availableAmount > 0, "There are no available distributions left to claim");
		emit EthClaimed(msg.sender, availableAmount);
		Address.sendValue(payable(msg.sender), availableAmount);
	}

	function _baseURI() internal view override(ERC721) returns (string memory) {
		return _baseTokenURI;
	}

	function setBaseURI(string memory baseTokenURI) public onlyOwner {
		_baseTokenURI = baseTokenURI;
	}

	function _baseExtension() internal view returns (string memory) {
		return _baseTokenExtension;
	}

	function setBaseTokenExtension(string memory newBaseTokenExtension) public onlyOwner {
		_baseTokenExtension = newBaseTokenExtension;
	}

	function tokenURI(uint256 tokenId)
		public
		view
		override(ERC721)
		returns (string memory)
	{
		require(
			_exists(tokenId),
			"ERC721Metadata: URI query for nonexistent token"
		);
		uint256 adjustedTokenId = tokenId - 1;
		string memory currentBaseURI = _baseURI();
		string memory currentBaseExtension = _baseExtension();
		return
			string(
				abi.encodePacked(
					currentBaseURI,
					adjustedTokenId.toString(),
					currentBaseExtension
				)
			);
	}

	function toggleIsSaleActive() public onlyOwner {
		isSaleActive = !isSaleActive;
	}

	function toggleIsDestroyActive() public onlyOwner {
		isDestroyClockActive = !isDestroyClockActive;
	}

	function toggleIsReflectionsActive() public onlyOwner {
		isReflectionsActive = !isReflectionsActive;
	}

	function withdraw() public onlyOwner {
		require(payable(msg.sender).send(address(this).balance), "Withdraw failed");
	}

	function destroyClock(uint256 tokenId) public saleActive destroyClockActive {
		require(_isApprovedOrOwner(msg.sender, tokenId), "Caller is not approved nor token is approved");

		_burn(tokenId);
		_burnCounts[msg.sender] += 1;

		uint256 burnedTokenId = totalSupply() + 1;
		_safeMint(msg.sender, burnedTokenId);

		emit ClockDestroyed(tokenId);
	}

	function getBurnCount(address sender) public view returns (uint256) {
		return _burnCounts[sender];
	}

	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 reserveMintBulk(address[] calldata mintAddresses) public onlyOwner {
	uint beginSupply = totalSupply();
	for (uint256 i = 0; i < mintAddresses.length; i++) {
		_safeMint(mintAddresses[i], beginSupply + 1 + i);
	}
}

// Used for Giveaways