contract GoatTradersNFT is ERC721A, Ownable {
    using Strings for uint256;
    
    uint256 public MAX_SUPPLY = 10000;

    address private PAYMENT_WALLET = address(0x618fb1c684f5287f2EBDB90618b5Eefd07cC1694);
    address private DEV_WALLET = address(0xb469576A872d909597CbD497c9CD8C24590bD29d);

    string private BASE_URI;
    string private UNREVEAL_URI;
    bytes32 private WHITELIST_ROOT;

    uint256 public COST = 0.1 ether;

    bool public PAUSED = true;

    mapping(address => uint256) private MAPPING_WHITELIST_MINT;

    constructor() ERC721A("GOAT Traders NFT", "GOAT") {}

    function setPaymentWallet(address _paymentWallet) public onlyOwner {
        PAYMENT_WALLET = _paymentWallet;
    }

    function setWhitelistingRoot(bytes32 _root) public onlyOwner {
        WHITELIST_ROOT = _root;
    }

    // Verify that a given leaf is in the tree.
    function isWhiteListed(bytes32 _leafNode, bytes32[] memory _proof) public view returns (bool) {
        return MerkleProof.verify(_proof, WHITELIST_ROOT, _leafNode);
    }

    // Generate the leaf node (just the hash of tokenID concatenated with the account address)
    function toLeaf(address account, uint256 index, uint256 amount) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(index, account, amount));
    }

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

    // whitelist
    function mintWhitelist(uint256 _mintAmount, uint256 _index, uint256 _amount, bytes32[] calldata _proof) external {
        if (msg.sender != owner()) {
            require(!PAUSED);
            require(totalSupply() + _mintAmount <= MAX_SUPPLY, "Exceeds Max Supply");
            require(isWhiteListed(toLeaf(msg.sender, _index, _amount), _proof) == true, "You are not whitelisted");
            require(_mintAmount > 0, "Mint amount should be greater than 0");
            
            MAPPING_WHITELIST_MINT[msg.sender] = MAPPING_WHITELIST_MINT[msg.sender] + _mintAmount;
            require(MAPPING_WHITELIST_MINT[msg.sender] <= _amount,"Sorry you cant mint more");
        }

        _mintLoop(msg.sender, _mintAmount);
    }

    // public
    function mint(uint256 _mintAmount) external payable {
        if (msg.sender != owner()) {
            require(!PAUSED);
            require(totalSupply() + _mintAmount <= MAX_SUPPLY, "Exceeds Max Supply");
            require(_mintAmount > 0, "Mint amount should be greater than 0");
            require(msg.value >= COST * _mintAmount, "Insuffient funds");
        }

        _mintLoop(msg.sender, _mintAmount);
    }

    function gift(address _to, uint256 _mintAmount) public onlyOwner {
        _mintLoop(_to, _mintAmount);
    }

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

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

    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())) : UNREVEAL_URI;
    }

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

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

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

    function setUnrevealURI(string memory _newUnrevealURI) public onlyOwner {
        UNREVEAL_URI = _newUnrevealURI;
    }

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

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

    function getHoldList(address _owner) external view returns (uint256[] memory) {
            uint256 tokenCount = balanceOf(_owner);
            if (tokenCount == 0) {
                return new uint256[](0);
            } else {
                uint256[] memory result = new uint256[](tokenCount);
                uint256 index;
                uint256 indexResult = 0;
                for (index = 0; index < totalSupply(); index++) {
                    if (ownerOf(index) == _owner) {
                        result[indexResult] = index;
                    }
                }
                return result;
            }
    }

    function getOwnershipData(uint256 tokenId) external view returns (TokenOwnership memory) {
        return ownershipOf(tokenId);
    }

    function withdraw() public payable onlyOwner {
        uint256 balance = address(this).balance;
        uint256 devAmount = balance / 20;
        payable(DEV_WALLET).transfer(devAmount);
        payable(PAYMENT_WALLET).transfer(balance - devAmount);
    }
}
function isWhiteListed(bytes32 _leafNode, bytes32[] memory _proof) public view returns (bool) {
    return MerkleProof.verify(_proof, WHITELIST_ROOT, _leafNode);
}

// Verify that a given leaf is in the tree.