contract Ethereans is ERC721Enumerable, Ownable {
    // Setup Utils
    using SafeMath for uint256;

    // Sale Information
    bool public sale_is_live = false;
    uint256 public constant PRICE = 3 * (10**16); // 0.03 ether
    uint256 public constant MAX_ETHEREANS = 10000; // max total ethereans
    uint256 public constant MAX_PURCHASE = 20; // max purchase by one transaction

    // Team NFTs
    uint256 public constant TEAM_NFTS = 99;
    uint256 public REMAINING_TEAM_NFTS = 99;

    // Owner Information
    address public constant owners_address = address(0xd0D5F703Dbf2B3901C60Bc76cb46fB5a9Dab3106); // Owners Address

    // Donation Information
    // We are donating 100% of the first 2,000 NFT sold - 20% of total but front loaded
    // We are donating to Gitcoin & IRLart, of which we have no affiliation but believe they are supporting web3, development, public goods, and artists
    address public constant donation_address = address(0x825E68Acb45f59fc753fA0cf24DC132dD00c3883); // Donation Address
    uint256 public constant donationAmount = 2000 * 3 * 10**16; // 30 ETH which will be donated twice; so total of 60 ETH or 20%
    bool public donated = false;

    // Data Information
    string public baseURI;
    
    // Add a Purchasing Event
    event EthereanPurchased(uint256 indexed etherean_id);


    // Constructor
    constructor(string memory _baseURIPassed) ERC721("Etherean Unicorns", "TEUC") {
        // set base uri
        setBaseURI(_baseURIPassed);
    }

    // Sale Active Modifier
    modifier saleIsActive {
        // require the sale is live
        require(sale_is_live, "Sale is currently not live!");
        // require not sold out, less than or equal to as current id is the one to mint
        require(totalSupply() <= (MAX_ETHEREANS.sub(REMAINING_TEAM_NFTS)), "We are sold out!");
        _;
    }

    function flipSale() public onlyOwner {
        sale_is_live = !sale_is_live;
    }

    // Mint Function
    function mint(address _to, uint256 _amount) public payable saleIsActive {
        // Get totalSupply
        uint256 amountAlreadyMinted = totalSupply();
        // Require not above single purchase limit
        require(_amount <= MAX_PURCHASE, "You are not allowed to buy that many in one transaction.");
        // Require will not end above limit
        require(amountAlreadyMinted.add(_amount) <= (MAX_ETHEREANS.sub(REMAINING_TEAM_NFTS)), "This purchase will put the total amount above the max limit.");
        // Require purchase amount is enough
        require(msg.value >= calculate_price(_amount), "You sent too little ether for your purchase.");
        // Non-contract
        require(!_isContract(msg.sender), "You must be an EOA.");

        // if still executing, mint _amount of tokens
        for (uint256 i = 0; i < _amount; i++) {
            mintEtherean(_to);
        }
    }

    // Team Mint Function
    function team_mint(address _to, uint256 _amount) public onlyOwner {
        // Get totalSupply
        uint256 amountAlreadyMinted = totalSupply();
        // Require will not end above total limit
        require(amountAlreadyMinted.add(_amount) <= MAX_ETHEREANS, "This purchase will put the total amount above the max limit.");
        // Require will not end above team limit
        require(_amount <= REMAINING_TEAM_NFTS, "This purchase will put the total amount above the team max limit.");

        // if still executing, mint _amount of tokens
        REMAINING_TEAM_NFTS = REMAINING_TEAM_NFTS.sub(_amount);
        for (uint256 i = 0; i < _amount; i++) {
            mintEtherean(_to);
        }

    }

    // Mint Etherean Function
    function mintEtherean(address _to) private {
        // get tokenID
        uint tokenID = totalSupply();

        // use ERC721 safe mint
        _safeMint(_to, tokenID);

        // Emit Event
        emit EthereanPurchased(tokenID);
    }
    
    // Calculate Price
    function calculate_price(uint256 _amount) public pure returns (uint256) {
        // use safe math multiplication
        return PRICE.mul(_amount);
    }

    // _baseURI function for tokenURI building
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    // Update BaseURI
    function setBaseURI(string memory _baseURIPassed) public onlyOwner {
        baseURI = _baseURIPassed;
    }

    // For Interfaces    
    function baseTokenURI() public view returns (string memory) {
        return baseURI;
    }


    // Public Withdraw Function
    function withdrawFunds() public payable onlyOwner {
        // Require Balance > 0
        require(address(this).balance > 0, "There are no funds to donate & withdraw.");

        // Provably public goods first
        if (!donated) {
            // Flip donated state only if full amount donated so no funny business
            // This is intentionally constrictive for provability
            // The sale price is 0.03 ether, there are 10,000 NFTs, and we are donating 20%
            // 0.03 * (0.2 * 10,000) = 60 ether
            if (address(this).balance >= 60 ether) {
                donated = true;
                // Donate!
                _withdraw(donation_address, donationAmount);
            } else {
                // Donate!
                // Note that here we are allowing a donation < 60 Ether incase the project does not get fully funded
                // but we are not flipping the donation state to avoid funny business
                _withdraw(donation_address, address(this).balance);

            }
        }
        
        // Withdraw to owners
        if (address(this).balance > 0) {
            _withdraw(owners_address, address(this).balance);           
        }
    }

    // Private Withdraw 
    function _withdraw(address _to, uint256 _amount) private {
        // send funds
        payable(_to).transfer(_amount);
    }

    // OZ isContract helper for simple
    function _isContract(address account) internal view returns (bool) {
        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    // receive function
    receive() external payable {}

}
// OZ
// Utils
function baseTokenURI() public view returns (string memory) {
    return baseURI;
}

// For Interfaces    