contract ZinjaNFT is ERC721, ERC721Enumerable, ERC721URIStorage, Pausable, Ownable, ERC721Burnable {
    bytes4 internal constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;
    using Strings for uint256;

    uint256 public currentId = 1;

    uint256 gas = 5000000;

    address payable dev;
    address payable project = payable(0xB07B3d85aac55034e90bAAD28b2C1AE622517749);
    uint256 devFee = 10;
    
    uint256 public mintingLimit = 112;
    uint256 public price = 2 * 10**17;
    uint256 public whitelistPrice = 2 * 10**17;
    mapping(address => bool) feeWhitelist;
    mapping(address => bool) public whitelist;
    bool public whitelistEnabled = true;
    uint256 public maxMint = 1;
    string baseURI = "ipfs://QmaE8yTNZAWkyjc9xGVkwYX2Dxj1xRDdrJrHDdfFBB856d/";
    string placeholderURI;
    uint256 public revealedTo = 112;
    string constant baseExtension = ".json";

    constructor(address _dev) ERC721("Zinja NFT", "Zinja NFT") {
        dev = payable(_dev);
    }

    function setPrice(uint256 _price) external onlyOwner {
        price = _price;
    }

    function setFeeWhitelistPrice(uint256 _price) external onlyOwner {
        whitelistPrice = _price;
    }

    function setWhitelist(address _wallet, bool _toggle) external onlyOwner {
        feeWhitelist[_wallet] = _toggle;
    }
    
    function setBaseURI(string calldata _base) external onlyOwner {
        baseURI = _base;
    }

    function setPlaceholderURI(string calldata _placeholder) external onlyOwner {
        placeholderURI = _placeholder;
    }
    
    function setMintLimit(uint256 _maxMint) external onlyOwner {
        maxMint = _maxMint;
    }
    
    function loadNFTs(uint256 _uris) external onlyOwner {
        mintingLimit += _uris;
    }

    function pushCurrentMintId(uint256 _amount) external onlyOwner {
        currentId += _amount;
    }

    function pushRevealedTo(uint256 _amount) external onlyOwner {
        revealedTo += _amount;
    }

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

    function updateURIs(uint256[] calldata ids, string[] calldata _uris) external onlyOwner {
        require(ids.length == _uris.length, "Wrong array size");
        for (uint i = 0; i < ids.length; i++){
            _setTokenURI(ids[i], _uris[i]); 
        }
    }
    
    function updateURIs(uint256[] calldata ids, string calldata _base) external onlyOwner {
        for (uint i = 0; i < ids.length; i++){
            _setTokenURI(ids[i], concatenate(concatenate(_base, ids[i].toString()), baseExtension)); 
        }
    }
    
    function updateURI(uint256 id, string memory _uri) external onlyOwner {
        _setTokenURI(id, _uri); 
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

   function mint(uint256 amount) external payable whenNotPaused {
        require(amount <= maxMint && amount > 0 && balanceOf(msg.sender) < maxMint, "Minting limits exceeded");
        if(whitelistEnabled) require(whitelist[msg.sender], "Sorry, whitelist minting only");

        unchecked{
        uint256 totalCost = feeWhitelist[msg.sender] ? 0 : (whitelistEnabled && whitelist[msg.sender] ? whitelistPrice : price) * amount;

        require (msg.value == totalCost, "Incorrect amount paid");
        
        uint256 mintId = currentId;

        require ((mintId-1) + amount <= mintingLimit, "Not enough tokens remaining");
        
        for (uint i = 0; i < amount; i++){
            _safeMint(msg.sender, mintId);
            mintId++;
        }
        currentId = mintId;
        }
        _multiMint(amount, msg.sender);
    }

    function claimFunds() external onlyOwner {
        dev.transfer((address(this).balance * devFee) / 100);
        project.transfer(address(this).balance);         
    } 

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        whenNotPaused
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    // The following functions are overrides required by Solidity.

    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), "ERC721URIStorage: URI query for nonexistent token");
        string memory _tokenURI = _tokenURIs[_tokenId];
        string memory base = _baseURI();

        if (bytes(_tokenURI).length > 0) {
            return _tokenURI;
        }

        if ((revealedTo >= _tokenId) && bytes(base).length > 0) {
            return concatenate(base, concatenate(_tokenId.toString(), baseExtension));
        }

        if (bytes(placeholderURI).length > 0) {
            return placeholderURI;
        }

        return super.tokenURI(_tokenId);
    } 

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

    function getMyNFTs(address me, uint256 start, uint256 limit, uint256 _gas) external view returns (uint256[] memory myNFTIDs, string[] memory myNFTuris, uint256 lastId) {
        uint256 balance = balanceOf(me);
        if (limit == 0) limit = 1;
        if (balance > 0) {
            if (start >= balance) start = balance-1;
            if (start + limit > balance) limit = balance - start;

            myNFTIDs = new uint256[](limit);
            myNFTuris = new string[](limit);
            
            uint256 gasUsed = 0;
            uint256 gasLeft = gasleft();
            
            for (uint256 i=0; gasUsed < (_gas > 0 ? _gas : gas) && i < limit; i++) {
                lastId = i+start;
                uint256 id = tokenOfOwnerByIndex(me, lastId);
                myNFTIDs[i] = id;
                myNFTuris[i] = tokenURI(id);
                
                gasUsed = gasUsed + (gasLeft - gasleft());
                gasLeft = gasleft();
            }
        }
    }

    function remaining() external view returns(uint256) {
        return mintingLimit - (currentId-1);
    }

    function enabledWhitelist(bool _enabled) external onlyOwner {
        whitelistEnabled = _enabled;
    }

    function loadWhitelist(address[] calldata addressList, bool _listed) external onlyOwner {
      for (uint256 i = 0; i < addressList.length; i++) {
          whitelist[addressList[i]] = _listed;
      }
    }

    function concatenate(string memory a, string memory b) internal pure returns (string memory) {
        return string(abi.encodePacked(a, b));
    }
    //C U ON THE MOON
}
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
    super._burn(tokenId);
}

// The following functions are overrides required by Solidity.