contract MadMeerkatBurrow is ERC721, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    uint256 public constant MAX_MEERKAT_PURCHASE = 20; // max purchase per txn
    uint256 public constant MAX_MEERKAT = 5000; // max of 5,000 meerkats

    string public MMB_PROVENANCE = "";
    uint public meerkatPrice = 5 ether / 100;
    uint public reservedMeerkats = 400;

    bool public saleIsActive = false; // determines whether sales is active
    bool public presale1Active = false; // determines whether presale 1 is active
    bool public presale2Active = false; // determines whether presale 2 is active
    uint public maxPurchasePresale1 = 5;
    uint public maxPurchasePresale2 = 3;

    mapping(address => bool) public whitelist1;
    mapping(address => bool) public whitelist2;
    mapping(address => uint) public presaleMints1;
    mapping(address => uint) public presaleMints2;

    event AssetMinted(address account, uint256 tokenId);

    constructor() ERC721("MadMeerkatBurrow", "MMB") {}

    /* ========== Public views ========== */

    function isWhitelistedPresale1(address addr) public view returns (bool) {
        return whitelist1[addr];
    }

    function isWhitelistedPresale2(address addr) public view returns (bool) {
        return whitelist2[addr];
    }

    /* ========== External public sales functions ========== */

    // @dev mints meerkat for the general public
    function mintMeerkat(uint numberOfTokens) external payable nonReentrant {
        require(saleIsActive); // Sale must be active
        require(numberOfTokens <= MAX_MEERKAT_PURCHASE); // Max mint of 20
        require(totalSupply().add(numberOfTokens) <= MAX_MEERKAT.sub(reservedMeerkats)); // Max supply exceeded
        require(meerkatPrice.mul(numberOfTokens) <= msg.value); // Value sent is not correct

        for (uint i = 0; i < numberOfTokens; i++) {
            if (totalSupply() < MAX_MEERKAT) {
                emit AssetMinted(msg.sender, totalSupply() + 1);

                _mint(msg.sender, totalSupply() + 1);
            }
        }
    }

    function presaleMintMeerkat1(uint256 numberOfTokens) public payable nonReentrant {
        require(presale1Active);
        require(numberOfTokens <= maxPurchasePresale1); // max of 5 can be bought at one time during presale
        require(isWhitelistedPresale1(msg.sender));
        require(numberOfTokens.mul(meerkatPrice) <= msg.value);
        require(presaleMints1[msg.sender] + numberOfTokens <= maxPurchasePresale1); // each user can only get 5 items in presale max
        require(totalSupply().add(numberOfTokens) <= MAX_MEERKAT.sub(reservedMeerkats)); // Max supply exceeded

        for (uint256 i = 0; i < numberOfTokens; i++) {
            presaleMints1[msg.sender] += 1;
            emit AssetMinted(msg.sender, totalSupply() + 1);

            _mint(msg.sender, totalSupply() + 1);
        }
    }

    function presaleMintMeerkat2(uint256 numberOfTokens) public payable nonReentrant {
        require(presale2Active);
        require(numberOfTokens <= maxPurchasePresale2); // max of 3 can be bought at one time during presale
        require(isWhitelistedPresale2(msg.sender));
        require(numberOfTokens.mul(meerkatPrice) <= msg.value);
        require(presaleMints2[msg.sender] + numberOfTokens <= maxPurchasePresale2); // each user can only get 3 items in presale max
        require(totalSupply().add(numberOfTokens) <= MAX_MEERKAT.sub(reservedMeerkats)); // Max supply exceeded

        for (uint256 i = 0; i < numberOfTokens; i++) {
            presaleMints2[msg.sender] += 1;
            emit AssetMinted(msg.sender, totalSupply() + 1);

            _mint(msg.sender, totalSupply() + 1);
        }
    }

    /* ========== External owner functions ========== */

    // @dev add user to whitelist for presale
    function addToWhitelist1(address[] memory addrs) public onlyOwner {
        for (uint256 i = 0; i < addrs.length; i++) {
            whitelist1[addrs[i]] = true;
            presaleMints1[addrs[i]] = 0;
        }
    }

    function addToWhitelist2(address[] memory addrs) public onlyOwner {
        for (uint256 i = 0; i < addrs.length; i++) {
            whitelist2[addrs[i]] = true;
            presaleMints2[addrs[i]] = 0;
        }
    }
    
    function setMaxPurchasePresale1(uint256 max) public onlyOwner {
        maxPurchasePresale1 = max;
    }

    function setMaxPurchasePresale2(uint256 max) public onlyOwner {
        maxPurchasePresale2 = max;
    }

    // @dev mints meerkat as giveaways
    function giveAway(address to, uint numberOfTokens) external nonReentrant onlyOwner {
        require(numberOfTokens <= reservedMeerkats); // Max supply exceeded

        for (uint i = 0; i < numberOfTokens; i++) {
            if (totalSupply() < MAX_MEERKAT) {
                emit AssetMinted(to, totalSupply() + 1);

                _mint(to, totalSupply() + 1);
            }
        }

        reservedMeerkats = reservedMeerkats.sub(numberOfTokens);
    }
    
    // @dev sets base URI
    function setBaseURI(string memory baseURI) external onlyOwner {
        _setBaseURI(baseURI);
    }

    // @dev withdraw funds
    function withdraw() external onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    // @dev withdraw funds
    function withdrawBEP20(address token) external onlyOwner {
        uint balance = IBEP20(token).balanceOf(address(this));
        IBEP20(token).transfer(msg.sender, balance);
    }

    // @dev flips the state for sales
    function flipSaleState() external onlyOwner {
        saleIsActive = !saleIsActive;
    }

    // @dev flips the state for presale 1
    function flipPreSale1State() public onlyOwner {
        presale1Active = !presale1Active;
    }
    
    // @dev flips the state for presale 2
    function flipPreSale2State() public onlyOwner {
        presale2Active = !presale2Active;
    }

    // @dev sets price of meerkat
    function setMeerkatPrice(uint256 _price) external onlyOwner {
        meerkatPrice = _price;
    }
    
    // @dev set provenance once it's calculated
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        MMB_PROVENANCE = provenanceHash;
    }
}
function giveAway(address to, uint numberOfTokens) external nonReentrant onlyOwner {
    require(numberOfTokens <= reservedMeerkats); // Max supply exceeded

    for (uint i = 0; i < numberOfTokens; i++) {
        if (totalSupply() < MAX_MEERKAT) {
            emit AssetMinted(to, totalSupply() + 1);

            _mint(to, totalSupply() + 1);
        }
    }

    reservedMeerkats = reservedMeerkats.sub(numberOfTokens);
}

// @dev mints meerkat as giveaways