contract v4apes is ERC721A, Ownable, ReentrancyGuard {
    using Strings for uint256;

    uint256 public V4APRICE; //0.01 Ξ = 10000000000000000 Wei
    uint256 public V4AMAX_SUPPLY; //MAX SUPPLY IS 10000
    uint256 public FREE_MINT_CAP_PER_WALLET_ADDRESS; //2 Free Mints PER WALLET 
    uint256 public MAX_MINT_QUANTITY_PER_TX; //MAX 100 PER WALLET
    uint256 public FREE_MINT_IS_ALLOWED_UNTIL; // FREE MINT UNTIL X AMOUNT HAVE BEEN CLAIMED
    bool public IS_SALE_UP_AND_RUNNING; //ALL THINGS ARE A GO
    bool public IS_METADATA_FROZEN; //FREEZE METADATA AFTER MINTED HAS BEEN REVEALED 
    string private BASE_URI; //METADATA
    string public baseExtension = ".json";

    mapping(address => uint256) private freeMintCountMap;

    constructor(uint256 price, uint256 maxSupply, string memory baseUri, uint256 freeMintAllowance, uint256 maxMintPerTx, bool isSaleActive, uint256 freeMintIsAllowedUntil) ERC721A("V4Apes", "V4Apes") {
        V4APRICE = price;
        V4AMAX_SUPPLY = maxSupply;
        BASE_URI = baseUri;
        FREE_MINT_CAP_PER_WALLET_ADDRESS = freeMintAllowance;
        MAX_MINT_QUANTITY_PER_TX = maxMintPerTx;
        IS_SALE_UP_AND_RUNNING = isSaleActive;
        FREE_MINT_IS_ALLOWED_UNTIL = freeMintIsAllowedUntil;
    }

    /** GETTERS **/

    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),"ERC721AMetadata: URI query for nonexistent token");
    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "ipfs://QmbqCxYz4Ai9J1Zt1AdzvYAgMSJREq8UQWNs2WQs5NfHX1/";
        }

    /** SETTERS  **/

    function setPrice(uint256 customPrice) external onlyOwner {
        V4APRICE = customPrice;
    }

    function lowerMaxSupply(uint256 newMaxSupply) external onlyOwner {
        require(newMaxSupply < V4AMAX_SUPPLY, "Invalid new max supply");
        require(newMaxSupply >= _currentIndex, "Invalid new max supply");
        V4AMAX_SUPPLY = newMaxSupply;
    }

    function setBaseURI(string memory customBaseURI_) external onlyOwner {
        require(!IS_METADATA_FROZEN, "Metadata is already frozen!");
        BASE_URI = customBaseURI_;
    }

    function setFreeMintAllowance(uint256 freeMintAllowance) external onlyOwner {
        FREE_MINT_CAP_PER_WALLET_ADDRESS = freeMintAllowance;
    }

    function setMaxMintPerTx(uint256 maxMintPerTx) external onlyOwner {
        MAX_MINT_QUANTITY_PER_TX = maxMintPerTx;
    }

    function setSaleActive(bool saleIsActive) external onlyOwner {
        IS_SALE_UP_AND_RUNNING = saleIsActive;
    }

    function setFreeMintAllowedUntil(uint256 freeMintIsAllowedUntil) external onlyOwner {
        FREE_MINT_IS_ALLOWED_UNTIL = freeMintIsAllowedUntil;
    }

    function freezeMetadata() external onlyOwner {
        IS_METADATA_FROZEN = true;
    }

    /** FREE MINT FOR THE LUCKY APES**/

    function updateFreeMintCount(address minter, uint256 count) private {
        freeMintCountMap[minter] += count;
    }

    /** MINT APES **/

    modifier mintCompliance(uint256 _mintAmount) {
        require(_mintAmount > 0 && _mintAmount <= MAX_MINT_QUANTITY_PER_TX, "Invalid mint amount!");
        require(_currentIndex + _mintAmount <= V4AMAX_SUPPLY, "Max supply exceeded!");
        _;
    }

    function mint(uint256 _mintAmount) public payable mintCompliance(_mintAmount) {
        require(IS_SALE_UP_AND_RUNNING, "Sale is not active!");

        uint256 price = V4APRICE * _mintAmount;

        if (_currentIndex < FREE_MINT_IS_ALLOWED_UNTIL) {
            uint256 remainingFreeMint = FREE_MINT_CAP_PER_WALLET_ADDRESS - freeMintCountMap[msg.sender];
            if (remainingFreeMint > 0) {
                if (_mintAmount >= remainingFreeMint) {
                    price -= remainingFreeMint * V4APRICE;
                    updateFreeMintCount(msg.sender, remainingFreeMint);
                } else {
                    price -= _mintAmount * V4APRICE;
                    updateFreeMintCount(msg.sender, _mintAmount);
                }
            }
        }

        require(msg.value >= price, "Insufficient funds!");

        _safeMint(msg.sender, _mintAmount);
    }

    function mintOwner(address _to, uint256 _mintAmount) public mintCompliance(_mintAmount) onlyOwner {
        _safeMint(_to, _mintAmount);
    }

    /** WITHDRAW **/
    
    //DAO WALLET
    address private constant payoutAddress1 = 0xc144647c80F49d8CF5a96872D86B9fA19943cFB8;
    

    function withdraw() public onlyOwner nonReentrant {
        uint256 balance = address(this).balance;

        Address.sendValue(payable(payoutAddress1), balance / 2);
        
    }
}
//contract forked from 0xmfers
function updateFreeMintCount(address minter, uint256 count) private {
    freeMintCountMap[minter] += count;
}

/** FREE MINT FOR THE LUCKY APES**/