contract CosmicCoffeeCollective is ERC721, ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    using Address for address;

    // Sale info
    uint256 constant MAX_COSMIC_COFFEES = 12000;
    uint256 public constant MAX_PER_MINT = 5;
    uint256 public communitySaleprice = 25000000000000000; // 0.025 ETH
    uint256 public price = 50000000000000000; // 0.05 ETH
    uint256 public CosmicCoffees = 10000;
    uint256 public constant MAX_FREECOFFEE_MINT = 50;

    // Token Availability
    uint256 public reservedClaimed;
    uint256 public numFreeCoffeeMinted;
    uint256 public reserved = 2000; // Reserved for the team, giveaways, collabs and so on

    // The base link that leads to the image / metadata of the token
    string public baseTokenURI;

    //Starting and stopping the sale
    bool public communitySaleStarted = false;
    bool public publicSaleStarted = false;

     // Team addresses for withdrawals
    address public a1 = 0xcc8d8F2451Dc756FB612DDD104ee148EdFCDD146; //Artist Wallet
    address public a2 = 0x8AA41A73deAD00D9DE913E4B10273D050703Fe3d; //Dev Wallet
    address public a3 = 0xdF968Caf4C2fB59bDb2Bfd9f43d06fa4491b38fb; //Founder Wallet
    address public a4 = 0xb5C224e0c8E5D7587b49434F858332AB95A79434; //Community Wallet
    address public a5 = 0x8dA2666D10f887a6D71EbD8577e6df80F6E2aB4e; //Charity Wallet


    //mapping 
    mapping(address => bool) private _communityEligible;
    mapping(address => uint256) private _totalClaimed;

    // events
    event BaseURIChanged(string baseURI);
    event CommunityMint(address minter, uint256 amountOfFreeCoffee);
    event PublicSaleMint(address minter, uint256 amountOfFreeCoffee);

    //modifiers
    modifier whenCommunitySaleStarted() {
        require(communitySaleStarted, "Community sale is not active fren");
        _;
    }

    modifier whenPublicSaleStarted() {
        require(publicSaleStarted, "Public sale is not active ser");
        _;
    }

    constructor (string memory newBaseURI) ERC721 ("Cosmic Coffee Collective", "FREECOFFEE") {
        setBaseURI(newBaseURI);

        numFreeCoffeeMinted += 1;
        _safeMint(msg.sender, numFreeCoffeeMinted);
    }

    // add addresses to Community Sale
    function addToCommunitySale(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Cannot add null address");

            _communityEligible[addresses[i]] = true;

            _totalClaimed[addresses[i]] > 0 ? _totalClaimed[addresses[i]] : 0;
        }
    }

    // enter address to determine if eligible for Cosmic Coffee Community sale
    function checkCommunityEligiblity(address addr) external view returns (bool) {
        return _communityEligible[addr];
    }

    ///// Minting Funtions /////

    // Admin minting to use for giveaways, collabs, and so on...
    function claimReserved(address recipient, uint256 amount) external onlyOwner {
        require(reservedClaimed != reserved, "Already have claimed all reserved Cosmic Coffees");
        require(reservedClaimed + amount <= reserved, "Minting would exceed max reserved Cosmic Coffees");
        require(recipient != address(0), "Cannot add null address");
        require(totalSupply() < MAX_COSMIC_COFFEES, "All tokens have been minted");
        require(totalSupply() + amount <= MAX_COSMIC_COFFEES, "Minting would exceed max supply");

        uint256 _nextTokenId = numFreeCoffeeMinted + 1;

        for (uint256 i = 0; i < amount; i++) {
            _safeMint(recipient, _nextTokenId + i);
        }
        numFreeCoffeeMinted += amount;
        reservedClaimed += amount;
    }

    // Cosmic Coffee Community Sale price minting
    function mintCommunity(uint256 amountOfFreeCoffee) external payable whenCommunitySaleStarted {
        require(_communityEligible[msg.sender], "You are not eligible for the Cosmic Coffee community minting fren");
        require(totalSupply() < MAX_COSMIC_COFFEES, "All tokens have been minted fren");
        require(amountOfFreeCoffee <= MAX_PER_MINT, "Cannot purchase this many tokens during community minting fren");
        require(totalSupply() + amountOfFreeCoffee <= MAX_COSMIC_COFFEES, "Minting would exceed max supply fren");
        require(_totalClaimed[msg.sender] + amountOfFreeCoffee <= MAX_PER_MINT, "Purchase exceeds max allowed fren");
        require(amountOfFreeCoffee > 0, "Must mint at least one cosmic coffee");
        require(communitySaleprice * amountOfFreeCoffee == msg.value, "ETH amount is incorrect");

        for (uint256 i = 0; i < amountOfFreeCoffee; i++) {
            uint256 tokenId = numFreeCoffeeMinted + 1;

            numFreeCoffeeMinted += 1;
            _totalClaimed[msg.sender] += 1;
            _safeMint(msg.sender, tokenId);
        }

        emit CommunityMint(msg.sender, amountOfFreeCoffee);
    }

    // Public Minting of Cosmic Coffees
    function mint(uint256 amountOfFreeCoffee) external payable whenPublicSaleStarted {
        require(totalSupply() < MAX_COSMIC_COFFEES, "All tokens have been minted fren");
        require(amountOfFreeCoffee <= MAX_PER_MINT, "Cannot purchase this many tokens during community minting fren");
        require(totalSupply() + amountOfFreeCoffee <= MAX_COSMIC_COFFEES, "Minting would exceed max supply fren");
        require(_totalClaimed[msg.sender] + amountOfFreeCoffee <= MAX_PER_MINT, "Purchase exceeds max allowed fren");
        require(amountOfFreeCoffee > 0, "Must mint at least one cosmic coffee");
        require(price * amountOfFreeCoffee == msg.value, "ETH amount is incorrect");

        for (uint256 i = 0; i < amountOfFreeCoffee; i++) {
            uint256 tokenId = numFreeCoffeeMinted + 1;

            numFreeCoffeeMinted += 1;
            _totalClaimed[msg.sender] += 1;
            _safeMint(msg.sender, tokenId);
        }

        emit PublicSaleMint(msg.sender, amountOfFreeCoffee);
    }

    // See which address owns which tokens
    function tokensOfOwner(address addr) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(addr);
        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(addr, i);
        }
        return tokensId;
    }

    ///// Owner Administrative /////

    // Toggle to start/stop Cosmic Coffee Community Sale
    function toggleCommunitySaleStarted() external onlyOwner {
        communitySaleStarted = !communitySaleStarted;
    }

    // Toggle to start/stop Public Sale
    function togglePublicSaleStarted() external onlyOwner {
        publicSaleStarted = !publicSaleStarted;
    }

    // Override so the openzeppelin tokenURI() method will use this method to create the full tokenURI instead
    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    // Update Base URI post deployment of smart contract
    function setBaseURI(string memory baseURI) public onlyOwner {
        baseTokenURI = baseURI;
        emit BaseURIChanged(baseURI);
    }

    // Set a different price in case ETH changes drastically
    function setPrice(uint256 newPrice) public onlyOwner {
        price = newPrice;
    }

    // Set team addresses
    function setAddresses(address[] memory _a) public onlyOwner {
        a1 = _a[0];
        a2 = _a[1];
        a3 = _a[2];
        a4 = _a[3];
        a5 = _a[4];
    }

    // Withdraw funds from contract for the team
    function withdrawTeam(uint256 amount) public payable onlyOwner {
        uint256 percent = amount / 100;
        // 25% to each team wallet
        require(payable(a1).send(percent * 25));
        require(payable(a2).send(percent * 25));
        require(payable(a3).send(percent * 25));
        // 15% will got to the community wallet to support community (and neighboring community) projects
        require(payable(a4).send(percent * 15));
        // 10% will got to the charity wallet to support coffee farms and other organizations identified by the community
        require(payable(a5).send(percent * 10));
    }
    
    // The following functions are overrides required by Solidity.
    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721,ERC721Enumerable) returns (bool) 
    {
        return super.supportsInterface(interfaceId);
    }
}
function setBaseURI(string memory baseURI) public onlyOwner {
    baseTokenURI = baseURI;
    emit BaseURIChanged(baseURI);
}

// Update Base URI post deployment of smart contract