contract TheLeadingCrew is ERC721Enumerable, ERC721URIStorage, Ownable{
	using Strings for uint256;
	using SafeMath for uint256;

	uint256 costPerToken = 0.06 ether; // 0.05
	uint256 maxTokens = 10000;
	uint256 maxMintAmount = 6;

	uint256 public preSaleDuration = 12 hours;
	uint256 public preSaleStartTime = 1639188000; // 11 Dec 2021 10:00:00 GMT+08
	uint256 public publicSaleStartTime = 1639231200; // 11 Dec 2021 22:00:00 GMT+08

	bool public pauseContract = true;
	bool public revealToken = false;

	string private baseURI;
	string public notRevealedURI;
	string public extensionURI = ".json";

	address public stakingContractAddress;

	mapping(uint256 => bool) public lockedTokens;
	mapping(address => bool) public whitelistedAddresses;

	modifier onlyStakingContract() {
		require(msg.sender == stakingContractAddress,"Only callable from staking contract");
		_;
	}

	constructor(
		string memory _name,
		string memory _symbol,
		string memory _initNotRevealedURI
	) ERC721(_name, _symbol) {
		setNotRevealedURI(_initNotRevealedURI);
	}

	// ==================================================
	// override functions
	// ==================================================
	function _burn(uint256 tokenID)
	internal
	override(ERC721, ERC721URIStorage)
	{
		super._burn(tokenID);
	}

	function tokenURI(uint256 tokenID)
	public
	view
	override(ERC721, ERC721URIStorage)
	returns (string memory)
	{
		require(_exists(tokenID),"ERC721Metadata: URI query for nonexistent token");

		if(revealToken == false)
		{
			return notRevealedURI;
		}

		return super.tokenURI(tokenID);
	}

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

	// ==================================================
	// setter functions
	// ==================================================
	function setBaseURI(string memory _baseURI)
	public
	onlyOwner
	{
		baseURI = _baseURI;
	}

	function setNotRevealedURI(string memory _notRevealedURI)
	public
	onlyOwner
	{
		notRevealedURI = _notRevealedURI;
	}

	function setStakingContractAddress(address _stakingContractAddress)
	public
	onlyOwner
	{
		stakingContractAddress = _stakingContractAddress;
	}

	// ==================================================
	// state functions
	// ==================================================
	function pause(bool _state)
	public
	onlyOwner
	{
		pauseContract = _state;
	}

	function reveal()
	public
	onlyOwner
	{
		revealToken = true;
	}

	function isPreSaleOpen()
	public
	view
	returns (bool)
	{
		return block.timestamp >= preSaleStartTime && block.timestamp < (preSaleStartTime + preSaleDuration);
	}

	function isPublicSaleOpen()
	public
	view
	returns (bool)
	{
		return block.timestamp >= publicSaleStartTime;
	}

	// ==================================================
	// public functions
	// ==================================================
	function tokensOwned(address _owner)
	public
	view
	returns (uint256[] memory)
	{
		uint256 ownerTokenCount = balanceOf(_owner);
		uint256[] memory tokenIDs = new uint256[](ownerTokenCount);
		for (uint256 i; i < ownerTokenCount; i++)
		{
			tokenIDs[i] = tokenOfOwnerByIndex(_owner, i);
		}
		return tokenIDs;
	}

	function presaleMint(uint256 _mintAmount)
	public
	payable
	{
		uint256 supply = totalSupply();

		require(isPreSaleOpen(), "Presale has not started.");
		require(whitelistedAddresses[msg.sender], "You are not in the whitelist :(");

		require(!pauseContract, "Contract is paused.");
		require(_mintAmount <= maxMintAmount, "You can only mint 6 tokens.");
		require(supply + _mintAmount <= maxTokens, "Max supply of tokens exceeded. Please reduce mint amount.");
		require(msg.value >= costPerToken * _mintAmount, "Not enough ETH sent.");

		for (uint256 i = 1; i <= _mintAmount; i++)
		{
			_safeMint(msg.sender, supply + i);
			_setTokenURI(supply + i, string(abi.encodePacked(baseURI, (supply + i).toString(), extensionURI)));
		}
	}

	function publicMint(uint256 _mintAmount)
	public
	payable
	{
		uint256 supply = totalSupply();

		require(isPublicSaleOpen(), "Sale has not started.");
		require(!pauseContract, "Contract is paused.");
		require(_mintAmount <= maxMintAmount, "You can only mint 6 tokens.");
		require(supply + _mintAmount <= maxTokens, "Max supply of tokens exceeded. Please reduce mint amount.");
		require(msg.value >= costPerToken * _mintAmount, "Not enough ETH sent.");

		for (uint256 i = 1; i <= _mintAmount; i++) {
			_safeMint(msg.sender, supply + i);
			_setTokenURI(supply + i, string(abi.encodePacked(baseURI, (supply + i).toString(), extensionURI)));
		}
	}

	// ==================================================
	// onlyOwner functions
	// ==================================================
	function devMint(uint256 _mintAmount)
	public
	onlyOwner
	{
		uint256 supply = totalSupply();
		require(supply + _mintAmount <= maxTokens, "Max supply of tokens exceeded. Please reduce mint amount.");

		for (uint256 i = 1; i <= _mintAmount; i++)
		{
			_safeMint(msg.sender, supply + i);
			_setTokenURI(supply + i, string(abi.encodePacked(baseURI, (supply + i).toString(), extensionURI)));
		}
	}

	function addWhitelistAddress(address[] calldata _newWhiteListUsers)
	public
	onlyOwner
	{
		for (uint256 i = 0; i < _newWhiteListUsers.length; i++)
		{
			whitelistedAddresses[_newWhiteListUsers[i]] = true;
		}
	}

	function batchUpdateTokenURI(uint256[] calldata _tokenIDs, string memory _newTokenURI)
	public
	onlyOwner
	{
		for (uint256 i = 1; i <= _tokenIDs.length; i++)
		{
			_setTokenURI(_tokenIDs[i-1], string(abi.encodePacked(_newTokenURI, i.toString(), extensionURI)));
		}
	}

	function airdrop(address[] memory giveawayList)
	public
	onlyOwner
	{
		require(balanceOf(msg.sender) >= giveawayList.length, "Not enough in wallet for airdrop amount");

		uint256[] memory giveawayTokens = tokensOwned(msg.sender);

		for (uint256 i = 0; i < giveawayList.length; i++)
		{
			_safeTransfer(msg.sender, giveawayList[i], giveawayTokens[i], "0x00");
		}
	}

	function withdraw()
	public
	payable
	onlyOwner
	{
		(bool success, ) = payable(msg.sender).call{value: address(this).balance}(""); // returns boolean and data
		require(success);
	}

	// ==================================================
	// staking functions
	// ==================================================
	function lockStakedToken(uint256[] memory _tokenIDs)
	external
	onlyStakingContract
	{
		for (uint256 i = 0; i < _tokenIDs.length; i++)
		{
			lockedTokens[_tokenIDs[i]] = true;
		}
	}

	function unlockStakedToken(uint256[] memory _tokenIDs)
	external
	onlyStakingContract
	{
		for (uint256 i = 0; i < _tokenIDs.length; i++)
		{
			lockedTokens[_tokenIDs[i]] = false;
		}
	}

	function _beforeTokenTransfer(address from, address to, uint256 tokenID)
	internal
	virtual
	override(ERC721, ERC721Enumerable)
	{
		require(lockedTokens[tokenID] == false, "Token is Locked");
		super._beforeTokenTransfer(from, to, tokenID);
	}
}
function setBaseURI(string memory _baseURI)
public
onlyOwner
{
	baseURI = _baseURI;
}

// ==================================================
// setter functions
// ==================================================