contract ZombitContract is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    address admin;
    uint256 public maxZombitPurchase = 10;
    uint256 public presaleMaxZombitPurchase = 5;
    uint256 public zombitPrice = 80000000000000000; //0.08 ETH
    uint256 public maxZombits = 10000;
    uint256 public saleStatus = 0; //0 close purchase,1 Pre-sale,2 public sale,3 close purchase
    mapping(address => bool) public whitelist;
    mapping(address => uint256) public totalAmount;
    // Base URI
    string private m_BaseURI = "";

    constructor(
        string memory name,
        string memory symbol,
        string memory baseURI
    ) ERC721(name, symbol) {
        admin = msg.sender;
        m_BaseURI = baseURI;
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    /**
     * Mint Zombits
     */
    function mintZombits(uint256 numberOfTokens) public payable {
        require(
            numberOfTokens <= maxZombitPurchase,
            "Exceeds max number of Zombits in one transaction"
        );
        require(saleStatus == 1 || saleStatus == 2, "Purchase not open");
        require(
            totalSupply().add(numberOfTokens) <= maxZombits,
            "Purchase would exceed max supply of Zombits"
        );
        require(
            zombitPrice.mul(numberOfTokens) == msg.value,
            "Ether value sent is not correct"
        );
        // Pre-sale
        if (saleStatus == 1) {
            require(
                whitelist[_msgSender()],
                "No permission to participate in the pre-sale"
            );
            require(
                totalAmount[_msgSender()] + numberOfTokens <=
                    presaleMaxZombitPurchase,
                "Exceeded quantity limit"
            );
        }
        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = totalSupply();
            _safeMint(_msgSender(), tokenId);
        }
        totalAmount[_msgSender()] = totalAmount[_msgSender()] + numberOfTokens;
    }

    /**
     * Recycle Zombits
     */
    function recycleZombits(uint256 numberOfTokens) public onlyAdmin {
        require(
            totalSupply().add(numberOfTokens) <= maxZombits,
            "Purchase would exceed max supply of Zombits"
        );
        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 tokenId = totalSupply();
            _safeMint(_msgSender(), tokenId);
        }
    }

    /**
     * Add whitelist address
     */
    function addWhitelist(address[] memory addr) public onlyAdmin {
        for (uint256 i = 0; i < addr.length; i++) {
            whitelist[addr[i]] = true;
        }
    }

    /**
     * Remove whitelist address
     */
    function removeWhitelist(address[] memory addr) public onlyAdmin {
        for (uint256 i = 0; i < addr.length; i++) {
            whitelist[addr[i]] = false;
        }
    }

    /**
     * Withdraw all balance to the admin address
     */
    function withdraw() public onlyAdmin {
        uint256 balance = address(this).balance;
        require(balance > 0);
        payable(_msgSender()).transfer(balance);
    }

    //Sales switch
    function flipSaleState() public onlyAdmin {
        if (saleStatus < 3) {
            saleStatus = saleStatus + 1;
        }
    }

    function setAdmin(address addr) external onlyAdmin {
        admin = addr;
    }

    function setMaxToken(uint256 _value) external onlyAdmin {
        require(
            _value > totalSupply() && _value <= 10_000,
            "Wrong value for max supply"
        );

        maxZombits = _value;
    }

    function setPrice(uint256 _price) external onlyAdmin {
        zombitPrice = _price;
    }

    function setMaxPurchase(uint256 _value) external onlyAdmin {
        require(_value > 0, "Invalid value");
        maxZombitPurchase = _value;
    }

    function setPresaleMaxZombitPurchase(uint256 _value) external onlyAdmin {
        require(_value > 0, "Invalid value");
        presaleMaxZombitPurchase = _value;
    }

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

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

    //All tokens under this address
    function tokensOfOwner(address _owner)
        external
        view
        returns (uint256[] memory ownerTokens)
    {
        uint256 tokenCount = balanceOf(_owner);

        if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            for (uint256 i = 0; i < tokenCount; i++) {
                uint256 index = tokenOfOwnerByIndex(_owner, i);
                result[i] = index;
            }
            return result;
        }
    }
}
function addWhitelist(address[] memory addr) public onlyAdmin {
    for (uint256 i = 0; i < addr.length; i++) {
        whitelist[addr[i]] = true;
    }
}

/**
 * Add whitelist address
 */