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

    uint256 public MFPRICE; //0.01 Ξ = 10000000000000000 Wei
    uint256 public MFERS_MAX_SUPPLY; //MAX SUPPLY IS 10000
    uint256 public FREE_MINT_CAP_PER_WALLET_ADDRESS; //2 0xMFERS 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("0xmfers", "0xmfers") {
        MFPRICE = price;
        MFERS_MAX_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 MFERS **/

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

    /** SETTERS MFERS **/

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

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

    function setBaseURI(string memory customBaseURI_) external onlyOwner {
        require(!IS_METADATA_FROZEN, "Metadata is already frozen mfer!");
        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 MFERS **/

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

    /** MINT MFERS **/

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

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

        uint256 price = MFPRICE * _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 * MFPRICE;
                    updateFreeMintCount(msg.sender, remainingFreeMint);
                } else {
                    price -= _mintAmount * MFPRICE;
                    updateFreeMintCount(msg.sender, _mintAmount);
                }
            }
        }

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

        _safeMint(msg.sender, _mintAmount);
    }

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

    /** WITHDRAW MFERS **/
    
    //DAO WALLET
    address private constant payoutAddress1 = 0x5ABc60aA3c3978749DF8C6821e05fc28aeD71134;
    address private constant payoutAddress2 = 0x5b76F295498954B7CcAfA8D6a809a89f79c4A643;

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

        Address.sendValue(payable(payoutAddress1), balance / 2);
        Address.sendValue(payable(payoutAddress2), balance / 2);
    }
}
//  ________     ___    ___ _____ ______   ________ _______   ________  ________      
// |\   __  \   |\  \  /  /|\   _ \  _   \|\  _____\\  ___ \ |\   __  \|\   ____\     
// \ \  \|\  \  \ \  \/  / | \  \\\__\ \  \ \  \__/\ \   __/|\ \  \|\  \ \  \___|_    
//  \ \  \\\  \  \ \    / / \ \  \\|__| \  \ \   __\\ \  \_|/_\ \   _  _\ \_____  \   
//   \ \  \\\  \  /     \/   \ \  \    \ \  \ \  \_| \ \  \_|\ \ \  \\  \\|____|\  \  
//    \ \_______\/  /\   \    \ \__\    \ \__\ \__\   \ \_______\ \__\\ _\ ____\_\  \ 
//     \|_______/__/ /\ __\    \|__|     \|__|\|__|    \|_______|\|__|\|__|\_________\
//              |__|/ \|__|                                               \|_________|
function _baseURI() internal view virtual override returns (string memory) {
    return BASE_URI;
}

/** GETTERS MFERS **/