contract TheWildBunch is NFTokenMetadata, Ownable 
{
    // Properties
    address payable feeAddress;
    uint constant public wildOnePrice = 0.1 ether;
    uint constant public maxWildOnes = 4000;    // Total WildOne Supply

    // Switches
    bool public isMintingActive = false;        // Is minting open? Yes or no.
    bool public isWhiteListActive = true;       // Is the public sale open or is it only whitelisted?

    // Mappings
    mapping(address => bool) private whiteList; // Map of addresses on the whitelist.
    mapping(address => uint256) public wildOnesClaimed; // Map of how many Wild Ones are minted per address.

    // Vars
    uint256 public current_minted = 0;
    uint public mintLimit = 5;
    
    // URI Data
    string private metaAddress = "https://api.thewildbunch.io/metadata/";
    string constant private jsonAppend = ".json";

    // Events
    event Minted(address sender, uint256 count);

    constructor()
    {
        nftName = "The Wild Bunch";
        nftSymbol = "TWB";
        feeAddress = payable(msg.sender);
    }

    function tokenURI(uint tokenID) external view returns (string memory)
    {   // @dev Token URIs are generated dynamically on view requests. 
        // This is to allow easy server changes and reduce gas fees for minting. -ssa2
        require(tokenID > 0, "Token does not exist.");
        require(tokenID <= current_minted, "Token hasn't been minted yet.");

        bytes32 thisToken;
        bytes memory concat;
        thisToken = uintToBytes(tokenID);
        concat = abi.encodePacked(metaAddress, thisToken, jsonAppend);
        return string(concat);
    }

    // Toggle whether any gals can be minted at all.
    function toggleMinting() public onlyOwner
    {
        isMintingActive = !isMintingActive;
    }

    // Toggle if we're in the Whitelist or Public Sale.
    function toggleWhiteList() public onlyOwner
    {
        isWhiteListActive = !isWhiteListActive;
    }

    // Add a list of wallet addresses to the Whitelist.
    function addToWhiteList(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Cannot add the null address");

            whiteList[addresses[i]] = true;
        }
    }

    // Tells the world if a given address is whitelisted or not.
    function onWhiteList(address addr) external view returns (bool) {
        return whiteList[addr];
    }

    // }:)
    function removeFromwhiteList(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Cant add null address");

            whiteList[addresses[i]] = false;
        }
    }

    // Tells the world how many Wild Ones a given address has minted.
    function claimedBy(address owner) external view returns (uint256) {
        require(owner != address(0), 'The zero address cannot mint anything.');

        return wildOnesClaimed[owner];
    }

    // Address  ETH gets sent to when withdrawing.
    function updateRecipient(address payable _newAddress) public onlyOwner
    {
        feeAddress = _newAddress;
    }

    // Takes care of converting an integer into the raw bytes of it's abi-encodable string.
    function uintToBytes(uint v) private pure returns (bytes32 ret) {
        if (v == 0)
        {
            ret = '0';
        }
        else
        {
            while (v > 0) 
            {
                ret = bytes32(uint(ret) / (2 ** 8));
                ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
                v /= 10;
            }
        }
        return ret;
    }

    // Public Sale minting function
    function mint(uint8 _mintNum) public payable
    {
        require(isMintingActive, "Yall cant mint yet.");
        require(_mintNum > 0, "Youre gonna have to mint at least one...");
        require(_mintNum + wildOnesClaimed[msg.sender] <= mintLimit, "Cant mint more than 5 Wild Ones per Wallet.");
        require(msg.value >= wildOnePrice * _mintNum, "Yall are gonna need more ETH to afford that.");
        require(current_minted + _mintNum <= maxWildOnes, "Sorry partner, we dont got enough of those left in stock.");
        if(isWhiteListActive)
        {
            require(whiteList[msg.sender], "I reckon youre not on the whitelist...");
        }

        for(uint i = 0; i < _mintNum; i++)
        {
            current_minted += 1;
            super._mint(msg.sender, current_minted);
            wildOnesClaimed[msg.sender] += 1;
        }

        emit Minted(msg.sender, _mintNum);
    }

    // Emergency Devmint function if something gets messed up.
    function devMint(uint8 _mintNum) external onlyOwner 
    {
        require(_mintNum + current_minted <= maxWildOnes, "Cannot mint more the total supply.");
        for(uint256 i = 0; i < _mintNum; i++) 
        {
            current_minted += 1;
            wildOnesClaimed[msg.sender] += 1;
            super._mint(msg.sender, current_minted);
        }
    }

    // Withdraw the ETH stored in the contract.
    function withdrawETH() external onlyOwner 
    {
        feeAddress.transfer(address(this).balance);
    }

    // Update the metadata URI to a new server or IPFS if needed.
    function updateURI(string calldata _URI) external onlyOwner 
    {
        metaAddress = _URI;
    }

    // Update how many can be purchased at a time if need be.
    function updateLimit(uint newLimit) external onlyOwner
    {
        mintLimit = newLimit;
    }

    // Get how many Wild Ones are minted right now.
    function totalSupply() public view returns(uint) 
    {
        return current_minted;
    }

    // Get how many Wild Ones MAX can be minted.    
    function maxSupply() public pure returns(uint) 
    {
        return maxWildOnes;
    }
}
function onWhiteList(address addr) external view returns (bool) {
    return whiteList[addr];
}

// Tells the world if a given address is whitelisted or not.