contract FastaMAX is ERC721Enumerable, Ownable {

    using SafeMath for uint256;

    // Private members
    enum MemberClaimStatus { Invalid, Listed }                                  // Members, listed?
    mapping (address => MemberClaimStatus) private _whiteListedMembers;         // Whitelisted members for presale
    mapping (address => uint256) private _whiteListMints;                       // Whitelisted mints per address
    string private m_BaseURI = "";                                              // https://api.fastamax.art/fasta/

    // Supply / sale variables
    uint256 public MAX_FASTA = 10000;                                           // Maximum supply
    uint256 public MAX_PRESALE_FASTA_PER_ADDRESS = 300;                         // Total maximum fasta for whitelisted wallet
    uint256 public fastaPrice = 70000000000000000;                              // 0.07 ETH

    // Per TX
    uint public maxFastaPerMint = 20;                                            // Max fastamax in one go !
    uint public maxFastaPerPresaleMint = 25;                                     // Max pre-sale fastamax in one go !

    // Active?!
    bool public mintingActive = false;                                          // Minting active
    bool public isPresaleActive = false;                                        // Presale active

    // Provenance
    string public provenanceHash = "";                                          // Provencance, data integrity

    constructor(
        string memory name,                                                     // FastaMAX
        string memory symbol,                                                   // FASTA
        string memory baseURI                                                   // baseURI
    ) ERC721(name, symbol) {
        m_BaseURI = baseURI;
    }

    // withdrawBalance
    //  -   Withdraws balance to contract owner
    function withdrawBalance() public onlyOwner {
        // Get the total balance
        uint256 balance = address(this).balance;

        // Transfer respective amounts
        payable(msg.sender).transfer(balance);
    }

    // reserveFasta
    //  -   Reserves fastamax for owner
    //  -   Used for giveaways/etc
    function reserveFasta(uint256 quantity) public onlyOwner {
        for(uint i = 0; i < quantity; i++) {
            uint mintIndex = totalSupply();
            if (mintIndex < MAX_FASTA) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }

     // bulkTransfer
    function bulkTransfer(address to, uint256[] memory tokenIds) public {
        for(uint i = 0; i < tokenIds.length; i++) {
            safeTransferFrom(msg.sender, to, tokenIds[i]);
        }
    }

    // Mint fasta
    //  -   Mints fasta by quantities
    function mintFasta(uint numberOfFasta) public payable  {
        require(mintingActive, "Minting is not activated yet.");
        require(numberOfFasta > 0, "Why are you minting less than zero fasta.");
        require(
            totalSupply().add(numberOfFasta) <= MAX_FASTA,
            'Only 10,000 fasta are available'
        );
        require(numberOfFasta <= maxFastaPerMint, "Cannot mint this number of fasta in one go !");
        require(fastaPrice.mul(numberOfFasta) <= msg.value, 'Ethereum sent is not sufficient.');

        for(uint i = 0; i < numberOfFasta; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_FASTA) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }

    // mintFastaAsMember
    //  -   Mints fasta as whitelisted member
     function mintFastaAsMember(uint numberOfFasta) public payable {
        require(isPresaleActive, "Presale is not active yet.");
        require(numberOfFasta > 0, "Why are you minting less than zero fasta.");
        require(_whiteListedMembers[msg.sender] == MemberClaimStatus.Listed, "You are not a whitelisted member !");
        require(_whiteListMints[msg.sender].add(numberOfFasta) <= MAX_PRESALE_FASTA_PER_ADDRESS, "You are minting more than your allowed presale fasta!");
        require(totalSupply().add(numberOfFasta) <= MAX_FASTA, "Only 10,000 fasta are available");
        require(numberOfFasta <= maxFastaPerPresaleMint, "Cannot mint this number of presale fasta in one go !");
        require(fastaPrice.mul(numberOfFasta) <= msg.value, 'Ethereum sent is not sufficient.');

        for(uint i = 0; i < numberOfFasta; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_FASTA) {
                _safeMint(msg.sender, mintIndex);
                _whiteListMints[msg.sender] = _whiteListMints[msg.sender].add(1);
            }
        }
    }

    // addToWhitelist
    //  -   Adds discord/invited members to presale whitelist
    function addToWhitelist(address[] memory members) public onlyOwner {
        for (uint256 i = 0; i < members.length; i++) {
            _whiteListedMembers[members[i]] = MemberClaimStatus.Listed;
            _whiteListMints[members[i]] = 0;
        }
    }

    // isWhitelisted
    //  -   Public helper to check if an address is whitelisted
    function isWhitelisted (address addr) public view returns (bool) {
        return _whiteListedMembers[addr] == MemberClaimStatus.Listed;
    }

    // switchMinting
    //  -   Allows, disallows minting
    function switchMinting() public onlyOwner {
        mintingActive = !mintingActive;
    }

    // switchPresale
    //  -   Allows, disallows presale
    function switchPresale() public onlyOwner {
        isPresaleActive = !isPresaleActive;
    }

    // setMaxQuantityPerMint
    //  -   Sets the maximum mints per tx
    function setMaxQuantityPerMint (uint256 quantity) public onlyOwner {
        maxFastaPerMint = quantity;
    }

     // setMaxQuantityPerPresaleMint
    //  -   Sets the maximum mints per tx for presale
    function setMaxQuantityPerPresaleMint (uint256 quantity) public onlyOwner {
        maxFastaPerPresaleMint = quantity;
    }

    // setPresaleMaxPerWallet
    //  -   Emergency function to update the max per presale wallet
    function setMaxPerPresaleWallet (uint256 quantity) public onlyOwner {
        MAX_PRESALE_FASTA_PER_ADDRESS = quantity;
    }

    // setBaseURI
    //  -  Metadata lives here
    function setBaseURI(string memory baseURI) external onlyOwner() {
        m_BaseURI = baseURI;
    }

    // _baseURI
    function _baseURI() internal view override returns (string memory) {
        return m_BaseURI;
    }

    // setProvenance
    //  -   Provenance for data integrity
    function setProvenance(string memory _provenance)
        external
        onlyOwner
    {
        provenanceHash = _provenance;
    }

}
function switchPresale() public onlyOwner {
    isPresaleActive = !isPresaleActive;
}

// switchPresale
//  -   Allows, disallows presale