contract PixlrGenesis is ERC721, Ownable {

    event MemberAdded(address member);
    event MemberRemoved(address member);

    using SafeMath for uint256;

    string public GENESIS_PROVENANCE = "";

    uint256 public genesisPrice;

    uint public maxGenesisPerTxn;

    uint256 public constant MAX_GENESIS = 10000;

    bool public saleIsActivePreSale = false;
    bool public saleIsActiveTierA = false;
    bool public saleIsActiveTierB = false;
    bool public saleIsActiveTierC = false;
    bool public saleIsActiveTierD = false;
    bool public saleIsActiveTierE = false;

    uint256 MintCycleTierA = 2499;
    uint256 MintCycleTierB = 4499;
    uint256 MintCycleTierC = 6499;
    uint256 MintCycleTierD = 8499;
    uint256 MintCycleTierE = 9999;

    mapping (address => bool) members;

    constructor(string memory baseURI) ERC721("Pixlr Genesis", "PG") {
        _setBaseURI(baseURI);
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        require(balance > 0);

        msg.sender.transfer(balance);
    }

    /**
     * Set some genesis aside
     */
    function reserveGenesis(uint256 _maxMint) public onlyOwner {
        uint supply = totalSupply();
        uint i;
        for (i = 0; i < _maxMint; i++) {
            if (totalSupply() < MAX_GENESIS) {
                _safeMint(msg.sender, supply + i);
            }
        }
    }

    /*
      * Set provenance once it's calculated
    */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        GENESIS_PROVENANCE = provenanceHash;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    function setMaxperTransaction(uint256 _maxNFTPerTransaction) internal onlyOwner {
        maxGenesisPerTxn = _maxNFTPerTransaction;
    }

    /**
     * @dev A method to verify whether an address is a member of the whitelist
     * @param _member The address to verify.
     * @return Whether the address is a member of the whitelist.
     */
    function isMember(address _member) public view returns(bool)
    {
        return members[_member];
    }

    /**
     * @dev A method to add a member to the whitelist
     * @param addresses The member to add as a member.
     */
    function addMember(address[] calldata addresses) public onlyOwner
    {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Cannot add null address");

            members[addresses[i]] = true;
            
            emit MemberAdded(addresses[i]);
        }
    }

    /**
     * @dev A method to remove a member from the whitelist
     * @param _member The member to remove as a member.
     */
    function removeMember(address _member) public onlyOwner
    {
        require(
            isMember(_member),
            "Not member of whitelist."
        );

        delete members[_member];
        emit MemberRemoved(_member);
    }

    /*
    * Pause sale if active, make active if paused
    */
    function flipSaleStatePreSale(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActivePreSale = !saleIsActivePreSale;
    }

    function flipSaleStateTierA(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActiveTierA = !saleIsActiveTierA;
    }

    function flipSaleStateTierB(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActiveTierB = !saleIsActiveTierB;
    }

    function flipSaleStateTierC(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActiveTierC = !saleIsActiveTierC;
    }

    function flipSaleStateTierD(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActiveTierD = !saleIsActiveTierD;
    }

    function flipSaleStateTierE(uint256 price, uint256 _maxMint) public onlyOwner {
        genesisPrice = price;
        setMaxperTransaction(_maxMint);
        saleIsActiveTierE = !saleIsActiveTierE;
    }

    /**
    * Pre Sale Mints
    */
    function mintPreSale(uint numberOfTokens) public payable {
        require(saleIsActivePreSale, "Sale must be active to mint Pixlr Genesis");
        require(maxGenesisPerTxn > 0);
        require(totalSupply() <= MintCycleTierA);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        require(members[msg.sender], "You are not in the whitelist to perform minting");

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

    /**
    * Tier A Mints
    */
    function mintTierA(uint numberOfTokens) public payable {
        require(saleIsActiveTierA, "Sale must be active to mint Pixlr Genesis");
        require(maxGenesisPerTxn > 0);
        require(totalSupply() <= MintCycleTierA);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

    /**
    * Tier B Mints
    */
    function mintTierB(uint numberOfTokens) public payable {
        require(saleIsActiveTierB, "Sale must be active to mint Pixlr Genesis");
        require(maxGenesisPerTxn > 0);
        require(totalSupply() <= MintCycleTierB);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

    /**
    * Tier C Mints
    */
    function mintTierC(uint numberOfTokens) public payable {
        require(saleIsActiveTierC, "Sale must be active to mint Pixlr Genesis");
        require(maxGenesisPerTxn > 0);
        require(totalSupply() <= MintCycleTierC);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

    /**
    * Tier D Mints
    */
    function mintTierD(uint numberOfTokens) public payable {
        require(saleIsActiveTierD, "Sale must be active to mint Pixlr Genesis");
        require(maxGenesisPerTxn > 0);
        require(totalSupply() <= MintCycleTierD);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

    /**
    * Tier E Mints
    */
    function mintTierE(uint numberOfTokens) public payable {
        require(saleIsActiveTierE, "Sale must be active to mint Samurai Doges");
        require(maxGenesisPerTxn > 0);
        require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
        require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Samurai Doges");
        require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_GENESIS) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }
}
/**
 * @title Pixlr Genesis contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function mintTierB(uint numberOfTokens) public payable {
    require(saleIsActiveTierB, "Sale must be active to mint Pixlr Genesis");
    require(maxGenesisPerTxn > 0);
    require(totalSupply() <= MintCycleTierB);
    require(numberOfTokens <= maxGenesisPerTxn, "Cannot purchase this many tokens in a transaction");
    require(totalSupply().add(numberOfTokens) <= MAX_GENESIS, "Purchase would exceed max supply of Pixlr Genesis");
    require(genesisPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

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

/**
 * Tier B Mints
 */