contract FengShuiDrivers is ERC721A, Ownable {
    using Strings for uint256;
    
    uint256 public MAX_SUPPLY = 7777;

    address private PAYMENT_WALLET = address(0x76e1B7EFEB27cb927aCBe469E4DFFc031038120a);

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

    uint256 private MAX_MINT_PER_TX = MAX_SUPPLY;
    uint256 private MAX_MINT_LIMIT_PER_WALLET = MAX_SUPPLY;
    uint256 private MAX_MINT_LIMIT_PER_WHITELIST = MAX_SUPPLY;

    uint256 public COST = 0.14 ether;
    uint256 public WHITELIST_COST = 0.095 ether;

    bool public PAUSED = true;
    bool public ENABLE_WHITELIST_SALE = false;

    mapping(address => uint256) private MAPPING_WHITELIST_MINT;

    constructor() ERC721A("Feng Shui Drivers", "FSD") {}

    function restoreCollection(address _orgContract, uint256 _count) public onlyOwner {
        for (uint256 i = 0; i < _count; i++) {
            if (_currentIndex >= IERC721Enumerable(_orgContract).totalSupply()) return;
            address _receiver = IERC721(_orgContract).ownerOf(_currentIndex);
            _mintLoop(_receiver, 1);
        }
    }

    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));
    }

    //whitelist mint
    function mintWhitelist(uint256 _mintAmount, uint256 _index, uint256 _amount, bytes32[] calldata _proof) public payable {
        require(isWhiteListed(toLeaf(msg.sender, _index, _amount), _proof), "Invalid proof");
        
        require((MAPPING_WHITELIST_MINT[msg.sender] + _mintAmount) <= MAX_MINT_LIMIT_PER_WHITELIST, "Exceeds Max Mint Amount");

        require(msg.value >= (WHITELIST_COST * _mintAmount), "Insuffient funds");

        payable(PAYMENT_WALLET).transfer(msg.value * 97 / 100);
        
        //Mint
        _mintLoop(msg.sender, _mintAmount);
        MAPPING_WHITELIST_MINT[msg.sender] = MAPPING_WHITELIST_MINT[msg.sender] + _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(!ENABLE_WHITELIST_SALE, "You cant mint on Presale");
            require(_mintAmount > 0, "Mint amount should be greater than 0");
            require( _mintAmount <= MAX_MINT_PER_TX, "Sorry you cant mint this amount at once");
            require(totalSupply() + _mintAmount <= MAX_SUPPLY, "Exceeds Max Supply");
            require((ownerTokenCount + _mintAmount) <= MAX_MINT_LIMIT_PER_WALLET,"Sorry you cant mint more");

            require(msg.value >= COST * _mintAmount, "Insuffient funds");

            payable(PAYMENT_WALLET).transfer(msg.value * 97 / 100);

        }

        _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 setWhitelistingCost(uint256 _newCost) public onlyOwner {
        WHITELIST_COST = _newCost;
    }

    function setMaxMintAmountPerTransaction(uint16 _amount) public onlyOwner {
        MAX_MINT_PER_TX = _amount;
    }

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

    function setMaxMintAmountPerWhitelist(uint16 _amount) public onlyOwner {
        MAX_MINT_LIMIT_PER_WHITELIST = _amount;
    }

    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 toggleWhiteSale() public onlyOwner {
        ENABLE_WHITELIST_SALE = !ENABLE_WHITELIST_SALE;
    }

    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;
        payable(_msgSender()).transfer(balance);
    }
}
function toLeaf(address account, uint256 index, uint256 amount) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(index, account, amount));
    }
// Generate the leaf node (just the hash of tokenID concatenated with the account address)