contract MaskedMeerkatGang is 
	ContextMixin,
	ERC721Enumerable, 
	Ownable 
{

    //using Strings for uint256;
    using SafeMath for uint256;

    string baseTokenURI;
    string public notRevealedURI;
    string public baseExtension = ".json";
    uint256 private currentTokenId = 0;
    uint256 maxSupply = 7777;
    uint256 internal fee;
    bool public revealed = false;
    uint256 public totalMint = 0;
    uint256 public transactionLimit = 5;
    uint256 public presaleTime = 1665708454; 
    uint256 public presaleEndTime = 1665708454;
    
    // time where general sale occurs
    uint256 public pledgeTime = 1665708454; 

    uint256 public price = 0.03 ether;
    uint256 public whitelistPrice = 0.02 ether;
    uint256 private reservedGiveaways = 111;
    
    // reserve presale amount for whitelist address
    uint256 private reservedPresale = 1665;
    
    // dev address, will be used to withdraw first 111 meerkats which will be distributed during give away
    address deployer = 0x32df815fE8b54CAc4f440767d7d428acC3853F00;
    address dev1 = 0x04E6Df24A84aF32765109c462226b70Ce1A3f40E;
    address dev2 = 0x6EDFE00dcF7d7133d3b827ECB22bf198B207d081;   

    mapping(address => bool) public whitelist;
    mapping(address => uint8) public presaleNumOfPlayer;
    mapping(address => uint8) public pledgeNumOfPlayer;
    mapping(address => uint8) public claimed;

    event WhitelistedAddressRemoved(address addr);
    event GivenName(uint256 indexed tokenId, string name);

    /**
        * @dev Throws if called by any account that is not whitelisted 
    */
    modifier onlyWhitelisted() {
        require(
            whitelist[msg.sender],
            "You are not on the whitelist"
        );
        _;
    }
    
    constructor(
        string memory _name, 
        string memory _symbol,
        string memory _initBaseURI, 
        string memory _initNotRevealedURI
    ) ERC721(_name, _symbol) 
    {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedURI);
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function mintTo(address[] memory _recipients) external onlyOwner {
        for (uint256 i = 0; i < _recipients.length; i++) {
            uint256 newTokenId = _getNextTokenId();
            _mint(_recipients[i], newTokenId);
            _incrementTokenId();
        }
        totalMint += _recipients.length;
    }

    /**
      * @dev add first 111 to one wallet
    */
    function giveAway() external onlyOwner() {
        for (uint256 i = 1; i <= reservedGiveaways; i++) {
            _safeMint(deployer, i);
            _incrementTokenId(); 
        }
        totalMint = reservedGiveaways;
        reservedGiveaways=0;
        
    }

    /**
      * @dev Mint to msg.sender. Only whitelisted users can participate
      * @param _num Quantity to purchase 
    */
    function presaleMint(uint8 _num) external payable onlyWhitelisted {
        require(
            block.timestamp >= presaleTime && block.timestamp < presaleEndTime,
            "Presale has not yet started."
        );
        //presale has a transaction limit and wallet limit
        require(
            (_num + presaleNumOfPlayer[msg.sender] + claimed[msg.sender]) <= transactionLimit,
            "Only up to 5 Meerkats can be purchased at this time."
        );
        require(totalMint != reservedPresale + reservedGiveaways,
            "Sorry all presale Meerkats are sold out!"
        );
        require(
            totalMint + uint256(_num) <= reservedPresale + reservedGiveaways ,
            "Sorry, please enter a lower amount."
        );
        require(
            msg.value == uint256(_num) * whitelistPrice,
            "You need to pay the exact price"
        );
        _mintList(_num);
        presaleNumOfPlayer[msg.sender] = presaleNumOfPlayer[msg.sender] + _num;
        totalMint += uint256(_num);
    }
    
    /**
     * @dev 6001 meerkats available for the general sale
     */
    function generalSaleMint(uint8 _num) external payable { 
       require(
            block.timestamp >= pledgeTime,
            "Pledge has not yet started!"
        );
        require(
            // general sale has no wallet limit, transaction limit = 5
            (_num + pledgeNumOfPlayer[msg.sender] ) <= transactionLimit,
            "Each address can only purchase up to 5 Meerkats per tx"
        );
        require(
            totalMint != maxSupply,
            "Sorry all Meerkats are sold out!"
        );   
        require(
            totalMint + uint256(_num) <= maxSupply,
            "Sorry, please enter a lower amount."
        );
        require(
            msg.value == uint256(_num) * price,
            "You need to pay the exact price."
        );
        _mintList(_num);
        claimed[msg.sender] += pledgeNumOfPlayer[msg.sender];
        pledgeNumOfPlayer[msg.sender] = 0;
        totalMint += uint256(_num);  
    }

    function _mintList(uint8 _num) private {
        for (uint8 i = 0; i < _num; i++) {
            uint256 newTokenId = _getNextTokenId();
            _mint(msg.sender, newTokenId);
            _incrementTokenId();
        }
    }

    /**
      * @dev calculates the next token ID based on value of currentTokenId
      * @return uint256 for the next token ID
    */
    function _getNextTokenId() public view returns (uint256) {
        return currentTokenId.add(1);
    }

    /**
      * @dev generates a random number based on block info
    */
    function random() private view returns (uint256) {
        // Creating a deterministic unique ID 
        bytes32 randomHash = keccak256(
            abi.encode(
                // current block timestamp as seconds since unix epoch
                block.timestamp,
                // current block difficulty
                block.difficulty,
                // current block miners address
                block.coinbase,
                msg.sender
            )
        );

        return uint256(randomHash);
    }

    /**
      * @dev increments the value of _currentTokenId
    */
    function _incrementTokenId() private {
        require(currentTokenId < maxSupply);
        currentTokenId++;
    }

    /**
      * @dev change the baseTokenURI only by Admin
     */
    function setBaseURI(string memory _uri) public onlyOwner {
        baseTokenURI = _uri;
    }

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedURI = _notRevealedURI;
    }
    
    function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
       baseExtension = _newBaseExtension;
    }

    /**
      * @dev set the presale and pledge time only by admin
     */
    function setAllTime(uint256 _preSaleTime, uint256 _preSaleEndTime, uint256 _pledgeTime)
    external onlyOwner 
    {
        presaleTime = _preSaleTime;
        presaleEndTime = _preSaleEndTime;
        pledgeTime = _pledgeTime;
    }

    function setName(uint256 _tokenId, string memory _name) external {
        require(
            ownerOf(_tokenId) == msg.sender,
            "You don't own this Meerkat"
        );

        emit GivenName(_tokenId, _name);
    }

    function tokenURI(uint256 _tokenId) public view override 
    returns (string memory)  
    {
        require(
            _exists(_tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return notRevealedURI;
        }

        string memory currentBaseURI = _baseURI();

        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, Strings.toString(_tokenId), baseExtension))
            : "";
    }

    function reveal() public onlyOwner() {
        revealed = true;
    }
    
    function unreveal() public onlyOwner() {
        revealed = false;
    }

    /**
      * @dev indstead of msg.sender as transactions won't be sent by the original token owner, but by OpeanSea
    */ 
    function _msgSender() internal view override returns (address sender) {
        return ContextMixin.msgSender();
    }

    function addAddressToWhitelist(address[] memory addrs) public onlyOwner 
    returns (address[] memory)
    {
        for (uint256 i = 0; i < addrs.length; i++) {
            whitelist[addrs[i]] = true;
        }

        return addrs;
    }

    function removeAddressFromWhitelist(address[] memory addrs) public onlyOwner {
        for (uint256 i = 0; i < addrs.length; i++) {
            whitelist[addrs[i]] = false;
            emit WhitelistedAddressRemoved(addrs[i]);
        }
    }

    function withdrawAll() public payable onlyOwner {
        uint256 _each = address(this).balance / 5;
        require(payable(dev1).send(_each));
        require(payable(dev1).send(_each));
        require(payable(dev1).send(_each));
        require(payable(dev2).send(_each));
        require(payable(dev2).send(_each));    
    }
}
function giveAway() external onlyOwner() {
    for (uint256 i = 1; i <= reservedGiveaways; i++) {
        _safeMint(deployer, i);
        _incrementTokenId(); 
    }
    totalMint = reservedGiveaways;
    reservedGiveaways=0;
        
}

/**
 * @dev add first 111 to one wallet
 */