contract ELONTUSKS is ERC721A, Ownable {
    using Strings for uint256;
    address breedingContract;
    IToken public yieldToken;

    string public baseApiURI;
    bytes32 private whitelistRoot;
  

    //General Settings
    uint16 public maxMintAmountPerTransaction = 1;
    uint16 public maxMintAmountPerWallet = 1;

   

    //Inventory
    uint256 public maxSupply = 500;
    uint256 public freeLimit = 500;

    //Prices
    uint256 public cost = 0.015 ether;

    //Utility
    bool public paused = false;

    //mapping
	mapping(address => uint256) public balanceOG;

    

    constructor(string memory _baseUrl, address  _tokenAddress) ERC721A("ELONTUSK", "ELONTUSK") {   
        baseApiURI = _baseUrl;
         yieldToken = IToken(_tokenAddress);
    }

     function setFreeLimit(uint256 _limit) public onlyOwner{
        freeLimit = _limit;
    }

    //This function will be used to extend the project with more capabilities 
    function setBreedingContractAddress(address _bAddress) public onlyOwner {
        breedingContract = _bAddress;
    }

    //set yield token 
    function setYieldToken(address _address) public onlyOwner {
        yieldToken = IToken(_address);
    }


    //this function can be called only from the extending contract
    function mintExternal(address _address, uint256 _mintAmount) external {
        require(
            msg.sender == breedingContract,
            "Sorry you dont have permission to mint"
        );
        _safeMint(_address, _mintAmount);
        yieldToken.updateRewardOnMint(_address, _mintAmount);
		balanceOG[_address] += _mintAmount;
    }

   
   
    function numberMinted(address owner) public view returns (uint256) {
        return _numberMinted(owner);
    }

    // public
    function mint(uint256 _mintAmount) public payable {
        if (msg.sender != owner()) {
            uint256 ownerTokenCount = balanceOf(msg.sender);

            require(!paused);
            require(_mintAmount > 0, "Mint amount should be greater than 0");
            require(
                _mintAmount <= maxMintAmountPerTransaction,
                "Sorry you cant mint this amount at once"
            );
            require(
                totalSupply() + _mintAmount <= maxSupply,
                "Exceeds Max Supply"
            );
            require(
                (ownerTokenCount + _mintAmount) <= maxMintAmountPerWallet,
                "Sorry you cant mint more"
            );

              if(totalSupply() > freeLimit){
                //not free
                require(msg.value >= cost * _mintAmount, "Insuffient funds");
            }else{
                uint256 a = freeLimit - totalSupply();
                
                if(_mintAmount > a){
                   uint256 c = _mintAmount - a; 
                   //pay only for A 
                    require(msg.value >= cost * c, "Insuffient funds");
                }

            }
        }

        _mintLoop(msg.sender, _mintAmount);
        yieldToken.updateRewardOnMint(msg.sender, _mintAmount);
		balanceOG[msg.sender] += _mintAmount;
    }

    function gift(address _to, uint256 _mintAmount) public onlyOwner {
        _mintLoop(_to, _mintAmount);
        yieldToken.updateRewardOnMint(_to, _mintAmount);
		balanceOG[_to] += _mintAmount;
    }

    function airdrop(address[] memory _airdropAddresses) public onlyOwner {
        for (uint256 i = 0; i < _airdropAddresses.length; i++) {
            address to = _airdropAddresses[i];
            _mintLoop(to, 1);
            yieldToken.updateRewardOnMint(to, 1);
		    balanceOG[to]++;
        }
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseApiURI;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(abi.encodePacked(currentBaseURI, tokenId.toString()))
                : "";
    }

    	function getReward() external {
		yieldToken.updateReward(msg.sender, address(0));
		yieldToken.getReward(msg.sender);
	}


    //overides
   function transferFrom(address from, address to, uint256 tokenId) public override {
		yieldToken.updateReward(from, to);
		balanceOG[from]--;
		balanceOG[to]++;
		ERC721A.transferFrom(from, to, tokenId);
	}

    	function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override {
		yieldToken.updateReward(from, to);
			balanceOG[from]--;
			balanceOG[to]++;
		ERC721A.safeTransferFrom(from, to, tokenId, _data);
	}


    function setCost(uint256 _newCost) public onlyOwner {
        cost = _newCost;
    }

    function setmaxMintAmountPerTransaction(uint16 _amount) public onlyOwner {
        maxMintAmountPerTransaction = _amount;
    }

    function setMaxMintAmountPerWallet(uint16 _amount) public onlyOwner {
        maxMintAmountPerWallet = _amount;
    }


    function setMaxSupply(uint256 _supply) public onlyOwner {
        maxSupply = _supply;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseApiURI = _newBaseURI;
    }

    function togglePause() public onlyOwner {
        paused = !paused;
    }

    function _mintLoop(address _receiver, uint256 _mintAmount) internal {
        _safeMint(_receiver, _mintAmount);
    }

    //Burn them paperhands
    function batchBurn(uint256[] memory _nftIds) public onlyOwner {
            for(uint256 i = 0; i < _nftIds.length; i++){
                 address tokenOwner = ownerOf(_nftIds[i]);
                 yieldToken.updateReward(tokenOwner, address(this));
		        balanceOG[tokenOwner]--;
                _burn(_nftIds[i]);
            }
    }

    function burn(uint256 _nftId) public onlyOwner{
       
        address tokenOwner = ownerOf(_nftId);
        yieldToken.updateReward(tokenOwner, address(this));
		balanceOG[tokenOwner]--;
        _burn(_nftId);
    }
   

    

    function withdraw() public payable onlyOwner {
        (bool hq, ) = payable(owner()).call{value: address(this).balance}("");
        require(hq);

    }
}
function setBreedingContractAddress(address _bAddress) public onlyOwner {
    breedingContract = _bAddress;
}

//This function will be used to extend the project with more capabilities 