contract MetaBogans is ERC721, Ownable, ERC721Enumerable, ReentrancyGuard {
    using Strings for uint256;
	
	uint256 public GIVEAWAY_NFT = 100;
	uint256 public RESERVED_NFT = 100;
	uint256 public EARLYSALE_NFT = 600;
	uint256 public PRESALE_NFT = 1400;
	uint256 public SALE_NFT_BATCH_ONE = 1400;
	uint256 public SALE_NFT_BATCH_TWO = 1400;
	uint256 public BREEDING_NFT = 1600;
	
	uint256 public GOLD_NFT_EARLYSALE = 25;
	uint256 public GOLD_NFT_PRESALE = 25;
	uint256 public GOLD_NFT_SALE_BATCH_ONE = 25;
	uint256 public GOLD_NFT_SALE_BATCH_TWO = 25;

	uint256 public MAX_MINT_EARLYSALE = 3;
	uint256 public MAX_MINT_PRESALE = 2;
	uint256 public MAX_MINT_SALE_BATCH_ONE = 3;
	uint256 public MAX_MINT_SALE_BATCH_TWO = 3;
	
	uint256 public MAX_BY_MINT_IN_TRANSACTION_EARLYSALE = 3;
	uint256 public MAX_BY_MINT_IN_TRANSACTION_PRESALE = 2;
	uint256 public MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_ONE = 3;
	uint256 public MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_TWO = 3;
	
	uint256 public EARLYSALE_PRICE = 1 * 10**17;
	uint256 public PRESALE_PRICE = 12 * 10**16;
	uint256 public SALE_PRICE_BATCH_ONE = 13 * 10**16;
	uint256 public SALE_PRICE_BATCH_TWO = 15 * 10**16;
	
	uint256 public GIVEAWAY_MINTED;
	uint256 public RESERVED_MINTED;
	uint256 public EARLYSALE_MINTED;
	uint256 public PRESALE_MINTED;
	uint256 public SALE_MINTED_BATCH_ONE;
	uint256 public SALE_MINTED_BATCH_TWO;
	
	bool public earlysaleEnable = false;
    bool public presaleEnable = false;
	bool public saleEnableBatchOne = false;
	bool public saleEnableBatchTwo = false;
	bool public breedingEnable = false;

	uint256[] public goldNFT;
	
	bytes32 public merkleRootEarlySale;
	bytes32 public merkleRootPreSale;

    string private baseURI;
	
	struct User {
	    uint256 earlySaleMint;
		uint256 preSaleMint;
		uint256 saleMintBatchOne;
		uint256 saleMintBatchTwo;
    }
	mapping (address => User) public users;
	mapping (uint256 => bool) public isGold;

    constructor() ERC721('MetaBogans', 'MB') {}
	
	
	/**************** Giveaway NFT Minting ******************************/
	function mintGiveawayNFT(address _to, uint256 _count) public onlyOwner{
        require(
            GIVEAWAY_MINTED + _count <= GIVEAWAY_NFT, 
            "max limit reached"
        );
		for (uint256 i = 0; i < _count; i++) {
		     uint256 tokenId = _randMod();
            _safeMint(_to, tokenId);
			GIVEAWAY_MINTED++;
        }
    }
	
	/**************** Reserved NFT Minting ******************************/
	function mintReservedNFT(uint256 _count) public onlyOwner{
        require(
            RESERVED_MINTED + _count <= RESERVED_NFT, 
            "max limit reached"
        );
		for (uint256 i = 0; i < _count; i++) {
		     uint256 tokenId = _randMod();
            _safeMint(msg.sender, tokenId);
			RESERVED_MINTED++;
			goldNFT.push(tokenId);
			isGold[tokenId] = true;
        }
    }
	
	/**************** Early Sale Minting ******************************/
	function mintEarlySaleNFT(uint256 _count, bytes32[] calldata merkleProof) public payable{
		bytes32 node = keccak256(abi.encodePacked(msg.sender));
		require(
			earlysaleEnable, 
			"Early sale is not enable"
		);
		require(
			EARLYSALE_MINTED + _count <= EARLYSALE_NFT, 
			"Exceeds max limit"
		);
		require(
			MerkleProof.verify(merkleProof, merkleRootEarlySale, node), 
			"MerkleDistributor: Invalid proof."
		);
		require(
			users[msg.sender].earlySaleMint + _count <= MAX_MINT_EARLYSALE,
			"Exceeds max mint limit per wallet"
		);
		require(
			_count <= MAX_BY_MINT_IN_TRANSACTION_EARLYSALE,
			"Exceeds max mint limit per tnx"
		);
		require(
			msg.value >= EARLYSALE_PRICE * _count,
			"Value below price"
		);
		for (uint256 i = 0; i < _count; i++) {
			uint256 tokenId = _randMod();
			_safeMint(msg.sender, tokenId);
			EARLYSALE_MINTED++;
			
			if(GOLD_NFT_EARLYSALE > 0)
			{
			   if((EARLYSALE_NFT - EARLYSALE_MINTED) <= GOLD_NFT_EARLYSALE)
			   {
			       goldNFT.push(tokenId);
				   GOLD_NFT_EARLYSALE--;
				   isGold[tokenId] = true;
			   }
			   else
			   {
			      uint256 goldNumber = pickgold(i);
			      if(goldNumber==1 || goldNumber==10 || goldNumber==15)
				  {
				      goldNFT.push(tokenId);
					  GOLD_NFT_EARLYSALE--;
					  isGold[tokenId] = true;
				  }
			   }
			}
		}
		users[msg.sender].earlySaleMint +=  _count;
	}
	
	/**************** Pre-Sale Minting ******************************/
	function mintPreSaleNFT(uint256 _count, bytes32[] calldata merkleProof) public payable{
		bytes32 node = keccak256(abi.encodePacked(msg.sender));
		require(
			presaleEnable, 
			"Pre-sale is not enable"
		);
		require(
			PRESALE_MINTED + _count <= PRESALE_NFT, 
			"Exceeds max limit"
		);
		require(
			MerkleProof.verify(merkleProof, merkleRootPreSale, node), 
			"MerkleDistributor: Invalid proof."
		);
		require(
			users[msg.sender].preSaleMint + _count <= MAX_MINT_PRESALE,
			"Exceeds max mint limit per wallet"
		);
		require(
			_count <= MAX_BY_MINT_IN_TRANSACTION_PRESALE,
			"Exceeds max mint limit per tnx"
		);
		require(
			msg.value >= PRESALE_PRICE * _count,
			"Value below price"
		);
		for (uint256 i = 0; i < _count; i++) {
			uint256 tokenId = _randMod();
			_safeMint(msg.sender, tokenId);
			PRESALE_MINTED++;
			
			if(GOLD_NFT_PRESALE > 0)
			{
			   if((PRESALE_NFT - PRESALE_MINTED) <= GOLD_NFT_PRESALE)
			   {
			       goldNFT.push(tokenId);
				   GOLD_NFT_PRESALE--;
				   isGold[tokenId] = true;
			   }
			   else
			   {
			      uint256 goldNumber = pickgold(i);
			      if(goldNumber==14)
				  {
				      goldNFT.push(tokenId);
					  GOLD_NFT_PRESALE--;
					  isGold[tokenId] = true;
				  }
			   }
			}
			
		}
		users[msg.sender].preSaleMint += _count;
	}
	
	/**************** Sale Minting Batch One******************************/
	function mintSaleNFTBatchOne(uint256 _count) public payable{
		require(
			saleEnableBatchOne, 
			"Sale is not enable"
		);
		require(
			SALE_MINTED_BATCH_ONE + _count <= SALE_NFT_BATCH_ONE, 
			"Exceeds max limit"
		);
		require(
			users[msg.sender].saleMintBatchOne + _count <= MAX_MINT_SALE_BATCH_ONE,
			"Exceeds max mint limit per wallet"
		);
		require(
			_count <= MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_ONE,
			"Exceeds max mint limit per tnx"
		);
		require(
			msg.value >= SALE_PRICE_BATCH_ONE * _count,
			"Value below price"
		);
		for (uint256 i = 0; i < _count; i++) {
			uint256 tokenId = _randMod();
			_safeMint(msg.sender, tokenId);
			SALE_MINTED_BATCH_ONE++;
			
			if(GOLD_NFT_SALE_BATCH_ONE > 0)
			{
			   if((SALE_NFT_BATCH_ONE - SALE_MINTED_BATCH_ONE) <= GOLD_NFT_SALE_BATCH_ONE)
			   {
			       goldNFT.push(tokenId);
				   GOLD_NFT_SALE_BATCH_ONE--;
				   isGold[tokenId] = true;
			   }
			   else
			   {
			      uint256 goldNumber = pickgold(i);
			      if(goldNumber==10)
				  {
				      goldNFT.push(tokenId);
					  GOLD_NFT_SALE_BATCH_ONE--;
					  isGold[tokenId] = true;
				  }
			   }
			}
		}
		users[msg.sender].saleMintBatchOne += _count;
	}
	
	/**************** Sale Minting Batch Two ******************************/
	function mintSaleNFTBatchTwo(uint256 _count) public payable {
		require(
			saleEnableBatchTwo, 
			"Sale is not enable"
		);
		require(
			SALE_MINTED_BATCH_TWO + _count <= SALE_NFT_BATCH_TWO, 
			"Exceeds max limit"
		);
		require(
			users[msg.sender].saleMintBatchTwo + _count <= MAX_MINT_SALE_BATCH_TWO,
			"Exceeds max mint limit per wallet"
		);
		require(
			_count <= MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_TWO,
			"Exceeds max mint limit per tnx"
		);
		require(
			msg.value >= SALE_PRICE_BATCH_TWO * _count,
			"Value below price"
		);
		
		for (uint256 i = 0; i < _count; i++) {
			uint256 tokenId = _randMod();
			_safeMint(msg.sender, tokenId);
			SALE_MINTED_BATCH_TWO++;
			
			if(GOLD_NFT_SALE_BATCH_TWO > 0)
			{
			   if((SALE_NFT_BATCH_TWO - SALE_MINTED_BATCH_TWO) <= GOLD_NFT_SALE_BATCH_TWO)
			   {
			       goldNFT.push(tokenId);
				   GOLD_NFT_SALE_BATCH_TWO--;
				   isGold[tokenId] = true;
			   }
			   else
			   {
			      uint256 goldNumber = pickgold(i);
			      if(goldNumber==1)
				  {
				      goldNFT.push(tokenId);
					  GOLD_NFT_SALE_BATCH_TWO--;
					  isGold[tokenId] = true;
				  }
			   }
			}
		}
		users[msg.sender].saleMintBatchTwo += _count;
	}
	
	/**************** Breeding Minting ******************************/
	function mintBreedingNFT(uint256 NFTOne, uint256 NFTTwo, uint256 NFTThree) public{
	    require(
			breedingEnable, 
			"Breeding is not enable"
		);
		require(
			!isGold[NFTOne] && !isGold[NFTTwo] && !isGold[NFTThree], 
			"Gold NFT can't Burn"
		);
		require(
			ownerOf(NFTOne) == msg.sender && ownerOf(NFTTwo) == msg.sender && ownerOf(NFTThree) == msg.sender, 
			"Invalid NFT"
		);
		
		_burn(NFTOne);
		_burn(NFTTwo);
		_burn(NFTThree);
		
		uint256 tokenId = _randMod();
		_safeMint(msg.sender, tokenId);
		
		 goldNFT.push(tokenId);
	    isGold[tokenId] = true;
	}
	
	function pickgold(uint256 loopNumber) public view returns (uint256) {
	   return uint256(uint256(keccak256(abi.encodePacked(loopNumber, block.timestamp, block.difficulty)))%20);
	}
	
    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 tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token");
        return string(abi.encodePacked(baseURI, _tokenId.toString(), ".json"));
    }
	
    function setURI(string memory _URI) external onlyOwner {
        baseURI = _URI;
    }
	
	function withdraw() external onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }
	
	function setBaseURI(string memory newBaseURI) public onlyOwner {
        baseURI = newBaseURI;
    }
	
	function updatePrice(uint256 earlySalePrice, uint256 preSalePrice, uint256 salePriceBatchOne, uint256 salePriceBatchTwo) external onlyOwner {
        EARLYSALE_PRICE = earlySalePrice;
		PRESALE_PRICE = preSalePrice;
		SALE_PRICE_BATCH_ONE = salePriceBatchOne;
		SALE_PRICE_BATCH_TWO = salePriceBatchTwo;
    }
	
	function setEarlySaleStatus(bool status) public onlyOwner {
        require(earlysaleEnable != status);
		earlysaleEnable = status;
    }
	
	function setPreSaleStatus(bool status) public onlyOwner {
	   require(presaleEnable != status);
       presaleEnable = status;
    }
	
	function setSaleStatusBatchOne(bool status) public onlyOwner {
	   require(saleEnableBatchOne != status);
       saleEnableBatchOne = status;
    }
	
	function setSaleStatusBatchTwo(bool status) public onlyOwner {
	   require(saleEnableBatchTwo != status);
       saleEnableBatchTwo = status;
    }
	
	function setBreedingStatus(bool status) public onlyOwner {
	   require(breedingEnable != status);
       breedingEnable = status;
    }
	
	function updateMintLimit(uint256 maxMintEarlySale, uint256 maxMintPreSale, uint256 maxMintSaleBatchOne, uint256 maxMintSaleBatchTwo) external onlyOwner {
        MAX_MINT_EARLYSALE = maxMintEarlySale;
		MAX_MINT_PRESALE = maxMintPreSale;
		MAX_MINT_SALE_BATCH_ONE = maxMintSaleBatchOne;
		MAX_MINT_SALE_BATCH_TWO = maxMintSaleBatchTwo;
    }
	
	function updateMintLimitPerTransection(uint256 mintPerTransectionEarlySale, uint256 mintPerTransectionPreSale, uint256 mintPerTransectionSaleBatchOne, uint256 mintPerTransectionSaleBatchTwo) external onlyOwner {
        MAX_BY_MINT_IN_TRANSACTION_EARLYSALE = mintPerTransectionEarlySale;
		MAX_BY_MINT_IN_TRANSACTION_PRESALE = mintPerTransectionPreSale;
		MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_ONE = mintPerTransectionSaleBatchOne;
		MAX_BY_MINT_IN_TRANSACTION_SALE_BATCH_TWO = mintPerTransectionSaleBatchTwo;
    }
	
	function updateMerkleRootEarlySale(bytes32 newRoot) external onlyOwner {
	   merkleRootEarlySale = newRoot;
	}
	
	function updateMerkleRootPreSale(bytes32 newRoot) external onlyOwner {
	   merkleRootPreSale = newRoot;
	}
	
	function updateGoldSupply(uint256 EarlySaleSupply, uint256 PreSaleSupply, uint256 SaleBatchOneSupply, uint256 SaleBatchTwoSupply) external onlyOwner {
        require(EarlySaleSupply + PreSaleSupply + SaleBatchOneSupply + SaleBatchTwoSupply == 4800);
		
		EARLYSALE_NFT = EarlySaleSupply;
		PRESALE_NFT = PreSaleSupply;
		SALE_NFT_BATCH_ONE = SaleBatchOneSupply;
		SALE_NFT_BATCH_TWO = SaleBatchTwoSupply;
    }
	
	function updateMintSupply(uint256 EarlySaleGoldSupply, uint256 PreSaleGoldSupply, uint256 SaleBatchOneGoldSupply, uint256 SaleBatchTwoGoldSupply) external onlyOwner {
        require(EarlySaleGoldSupply + PreSaleGoldSupply + SaleBatchOneGoldSupply + SaleBatchTwoGoldSupply == 100);
		
		GOLD_NFT_EARLYSALE = EarlySaleGoldSupply;
		GOLD_NFT_PRESALE = PreSaleGoldSupply;
		GOLD_NFT_SALE_BATCH_ONE = SaleBatchOneGoldSupply;
		GOLD_NFT_SALE_BATCH_TWO = SaleBatchTwoGoldSupply;
    }
	
    uint256 constant private MAX_NFT = 6600;
	uint[MAX_NFT] public indices;
    uint nonce;
	
    function _randMod() private returns (uint) {
        uint totalSize = MAX_NFT - nonce;
        uint index = uint(keccak256(abi.encodePacked(nonce, msg.sender, block.difficulty, block.timestamp))) % totalSize;
        uint value = 0;
        if (indices[index] != 0) {
            value = indices[index];
        } else {
            value = index;
        }
 
        if (indices[totalSize - 1] == 0) {
            indices[index] = totalSize - 1;
        } else {
            indices[index] = indices[totalSize - 1];
        }
        nonce++;
        return value+1;
    }
}
function mintEarlySaleNFT(uint256 _count, bytes32[] calldata merkleProof) public payable{
	bytes32 node = keccak256(abi.encodePacked(msg.sender));
	require(
		earlysaleEnable, 
		"Early sale is not enable"
	);
	require(
		EARLYSALE_MINTED + _count <= EARLYSALE_NFT, 
		"Exceeds max limit"
	);
	require(
		MerkleProof.verify(merkleProof, merkleRootEarlySale, node), 
		"MerkleDistributor: Invalid proof."
	);
	require(
		users[msg.sender].earlySaleMint + _count <= MAX_MINT_EARLYSALE,
		"Exceeds max mint limit per wallet"
	);
	require(
		_count <= MAX_BY_MINT_IN_TRANSACTION_EARLYSALE,
		"Exceeds max mint limit per tnx"
	);
	require(
		msg.value >= EARLYSALE_PRICE * _count,
		"Value below price"
	);
	for (uint256 i = 0; i < _count; i++) {
		uint256 tokenId = _randMod();
		_safeMint(msg.sender, tokenId);
		EARLYSALE_MINTED++;
			
		if(GOLD_NFT_EARLYSALE > 0)
		{
		   if((EARLYSALE_NFT - EARLYSALE_MINTED) <= GOLD_NFT_EARLYSALE)
		   {
		       goldNFT.push(tokenId);
			   GOLD_NFT_EARLYSALE--;
			   isGold[tokenId] = true;
		   }
		   else
		   {
		      uint256 goldNumber = pickgold(i);
		      if(goldNumber==1 || goldNumber==10 || goldNumber==15)
			  {
			      goldNFT.push(tokenId);
				  GOLD_NFT_EARLYSALE--;
				  isGold[tokenId] = true;
			  }
		   }
		}
	}
	users[msg.sender].earlySaleMint +=  _count;
}

/**************** Early Sale Minting ******************************/