contract Croakz is ERC721Enumerable, PaymentSplitter, Ownable {
    using Counters for Counters.Counter;
    using Strings for uint256;
    using ECRecoverLib for ECRecoverLib.ECDSAVariables;
    
    Counters.Counter private _tokenIds;
    Counters.Counter private _specialTokenIds;

    uint256 private constant maxTokens = 6969;
    uint256 private constant maxMintsPerTx = 10;
    uint256 private constant maxMintsPerAddr = 20;
    uint256 private constant mintPrice = 0.069 ether;
    uint8 private constant maxPresaleMintPerAddr = 3;
    mapping (address => uint8) private _mintsPerAddress;


    address crypToadzAddr; // 0x1CB1A5e65610AEFF2551A50f76a87a7d3fB649C6;
    address signingAuth;

    // 0 = DEV, 1 = PRESALE, 2 = PUBLIC, 3 = CLOSED
    uint8 private mintPhase = 0;

    bool private devMintLocked = false;

    // Optional mapping for token URIs
    mapping (uint256 => string) private _tokenURIs;

    // Base URI
    string private _baseURIextended;

    constructor(string memory name, string memory symbol, address[] memory payees, uint256[] memory shares_) ERC721(name, symbol) PaymentSplitter(payees, shares_) {

    }

    function setCrypToadzAddress(address _crypToadzAddr) external onlyOwner {
        crypToadzAddr = _crypToadzAddr;
    }

    function setSigningAuth(address _signingAuth) external onlyOwner {
        signingAuth = _signingAuth;
    }

    function setBaseURI(string memory baseURI_) external onlyOwner {
        _baseURIextended = baseURI_;
    }
    
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }
    
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseURIextended;
    }
    
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();
        
        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString()));
    }

    function _isHolder(address _wallet) internal view returns (bool) {
        ERC721 cryptoadzToken = ERC721(crypToadzAddr);
        uint256 _quantityHeld = cryptoadzToken.balanceOf(_wallet);
        return _quantityHeld > 0;
    }

    function changeMintPhase(uint8 _mintPhase) public onlyOwner {
        require(_mintPhase > mintPhase, "No back-stepping!");
        require(_mintPhase <= 3, "Final phase is CLOSED");
        mintPhase = _mintPhase;
    }

    function getMintPhase() public view returns (uint8) {
        return mintPhase;
    }

    function mint(uint256 quantity) internal {
        require(mintPhase == 1 || mintPhase == 2, "Minting is not open.");
        require(msg.sender == tx.origin, "No contracts!");
        require(msg.value >= mintPrice * quantity, "Not enough ether sent!");
        require(_tokenIds.current() + quantity <= maxTokens, "Minting this many would exceed supply!");

        if (mintPhase == 1) { // Holder Mint or WL
            require(_mintsPerAddress[msg.sender] + quantity <= maxPresaleMintPerAddr, "Presale cannot mint more than 3 CROAKZ!");
        } else { // Public Mint
            require(quantity <= maxMintsPerTx, "There is a limit on minting too many at a time!");
            require(this.balanceOf(msg.sender) + quantity <= maxMintsPerAddr, "Minting this many would exceed max mints per address!");
        }

        for (uint256 i = 0; i < quantity; i++) {
            _tokenIds.increment();
            _mintsPerAddress[msg.sender]++;
            _safeMint(msg.sender, _tokenIds.current());
        }
    }

    function toadMint(uint256 quantity) external payable {
        bool isHolder = _isHolder(msg.sender);
        require(isHolder, "Must own at least one CryptoToadz to mint in the presale!");
        mint(quantity);
    }

    function wlMint(uint256 quantity, ECRecoverLib.ECDSAVariables memory wlECDSA) external payable {
        // Prove to contract that seller is WL
        bytes32 senderHash = keccak256(abi.encodePacked(msg.sender));
        wlECDSA.verifySig(signingAuth, senderHash);
        mint(quantity);
    }

    function publicMint(uint256 quantity) external payable {
        require(mintPhase == 2, "Minting is not open to public.");
        mint(quantity);
    }

    // dev mint special 1/1s
    function mintSpecial(address [] memory recipients) external onlyOwner {        
        require(!devMintLocked, "Dev Mint Permanently Locked");
        for (uint256 i = 0; i < recipients.length; i++) {
            _specialTokenIds.increment();
            _safeMint(recipients[i], _specialTokenIds.current() * 1000000);
        }
    }

    function lockDevMint() public onlyOwner {
        devMintLocked = true;
    }

}
function mintSpecial(address [] memory recipients) external onlyOwner {        
    require(!devMintLocked, "Dev Mint Permanently Locked");
    for (uint256 i = 0; i < recipients.length; i++) {
        _specialTokenIds.increment();
        _safeMint(recipients[i], _specialTokenIds.current() * 1000000);
    }
}

// dev mint special 1/1s