contract NiftyBaseMerkle is ERC721Enumerable, PaymentSplitter, Ownable {
    using SafeMath for uint256;
    using SafeMath for uint8;

    // Token Data
    uint256 public constant TOKEN_PRICE = 0.05 ether;
    uint256 public constant RESERVED_TOKENS = 69;
    uint256 public constant MAX_TOKENS = 6969;
    
    uint256 public MAX_MINTS;

    // White List Token Data
    uint256 public MAX_PRE_MINTS;
    uint256 public MAX_OG_MINTS;
    
    // Contract Data
    string public PROVENANCE;
    string private _contractURI;
    string public _baseTokenURI;

    // Sale Switches
    bool public mainSaleActive = false;
    bool public ogSaleActive = false;
    bool public preSaleActive = false;

    // White List Token Counters
    mapping(address => uint8) private _preSaleList;
    mapping(address => uint8) private _ogSaleList;

    // Merkle Roots
    bytes32 public preSaleRoot;
    bytes32 public ogSaleRoot;

    constructor(
        string memory name, 
        string memory symbol, 
        string memory contractURI_, 
        string memory baseURI,
        uint256 maxMints,
        uint256 maxPreMints,
        uint256 maxOGMints,
        address[] memory payees, 
        uint256[] memory shares,
        bytes32 preSaleRoot_,
        bytes32 ogSaleRoot_
    ) 
    ERC721(name, symbol) 
    PaymentSplitter(payees, shares) 
    {
        setContractURI(contractURI_);
        setBaseURI(baseURI);
        preSaleRoot = preSaleRoot_;
        ogSaleRoot = ogSaleRoot_;
        MAX_MINTS = maxMints;
        MAX_PRE_MINTS = maxPreMints;
        MAX_OG_MINTS = maxOGMints;
    }

    /* Reserves */
    function reserveTokens() public onlyOwner {        
        uint supply = totalSupply();
        for (uint i = 0; i < RESERVED_TOKENS; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    /* Setters */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        PROVENANCE = provenanceHash;
    }

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

    function setContractURI(string memory contractURI_) public onlyOwner {
        _contractURI = contractURI_;
    }

    function setMaxMints(uint256 maxMints_) public onlyOwner {
        MAX_MINTS = maxMints_;
    }

    function setMaxPreMints(uint256 maxPreMints_) public onlyOwner {
        MAX_PRE_MINTS = maxPreMints_;
    }

    function setMaxOGMints(uint256 maxOGMints_) public onlyOwner {
        MAX_OG_MINTS = maxOGMints_;
    }

    function setPreSaleRoot(bytes32 _preSaleRoot) public onlyOwner {
        preSaleRoot = _preSaleRoot;
    }

    function setOGSaleRoot(bytes32 _ogSaleRoot) public onlyOwner {
        ogSaleRoot = _ogSaleRoot;
    }

    /* Getters */
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }
    
    function contractURI() public view returns (string memory) {
        return _contractURI;
    }
    
    /* Sale Switches */
    function flipPreSaleState() public onlyOwner {
        preSaleActive = !preSaleActive;
    }

    function flipOGSaleState() public onlyOwner {
        ogSaleActive = !ogSaleActive;
    }

    function flipMainSaleState() public onlyOwner {
        mainSaleActive = !mainSaleActive;
    }
    
    /* Pre Sale */
    function mintPreSaleTokens(uint8 numberOfTokens, bytes32[] calldata _merkleProof) external payable {
        require(preSaleActive, "Pre mint is not active");
        require(_preSaleList[msg.sender].add(numberOfTokens) <= MAX_PRE_MINTS, "Exceeded max available to purchase");
        require(numberOfTokens > 0, "Must mint more than 0 tokens");
        require(totalSupply().add(numberOfTokens) <= MAX_TOKENS, "Purchase would exceed max supply of Tokens");
        require(TOKEN_PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        // check proof
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, preSaleRoot, leaf), "Invalid MerkleProof");
        
        _preSaleList[msg.sender] = _preSaleList[msg.sender] + numberOfTokens;

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

    function numPreSaleMinted(address addr) external view returns (uint8) {
        return _preSaleList[addr]; 
    }

    /* Og Sale */
    function mintOGSaleTokens(uint8 numberOfTokens, bytes32[] calldata _merkleProof) external payable {
        require(ogSaleActive, "OG mint is not active");
        require(_ogSaleList[msg.sender].add(numberOfTokens) <= MAX_OG_MINTS, "Exceeded max available to purchase");
        require(numberOfTokens > 0, "Must mint more than 0 tokens");
        require(totalSupply().add(numberOfTokens) <= MAX_TOKENS, "Purchase would exceed max supply of Tokens");
        require(TOKEN_PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        // check proof
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(_merkleProof, ogSaleRoot, leaf), "Invalid MerkleProof");
        
        _ogSaleList[msg.sender] = _ogSaleList[msg.sender] + numberOfTokens;

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

    function numOGSaleMinted(address addr) external view returns (uint8) {
        return _ogSaleList[addr]; 
    }
    
    /* Main Sale */
    function mintTokens(uint numberOfTokens) public payable {
        require(mainSaleActive, "Sale must be active to mint token");
        require(numberOfTokens <= MAX_MINTS, "Can only mint max purchase of tokens at a time");
        require(numberOfTokens > 0, "Must mint more than 0 tokens");
        require(totalSupply().add(numberOfTokens) <= MAX_TOKENS, "Purchase would exceed max supply of Tokens");
        require(TOKEN_PRICE.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_TOKENS) {
                _safeMint(msg.sender, mintIndex);
            }
        }
    }

    /* Utility Functions */
    function walletOfOwner(address _owner) external view returns(uint256[] memory) {
        uint tokenCount = balanceOf(_owner);
        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint i = 0; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }
}
function walletOfOwner(address _owner) external view returns(uint256[] memory) {
    uint tokenCount = balanceOf(_owner);
    uint256[] memory tokensId = new uint256[](tokenCount);
    for(uint i = 0; i < tokenCount; i++){
        tokensId[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokensId;
}

/* Utility Functions */