contract SpaceAliens is
    ContextMixin,
    ERC721Enumerable,
    NativeMetaTransaction,
    Ownable
{
    using SafeMath for uint256;

    uint256 public special = 4;

    bool public reveal_special_1 = false;
    bool public reveal_special_2 = false;
    bool public reveal_special_3 = false;
    bool public reveal_special_4 = false;
    bool public reveal_all = false;

    uint256 public _currentGroup1TokenId = special;
    uint256 public _currentGroup2TokenId = 1000;

    uint256 MAX_SUPPLY =  3000;
    uint256 Group1_MAX_SUPPLY =  1000;
    
    string public baseTokenURI = "https://gateway.pinata.cloud/ipfs/QmTA1TvKGRGfeYKYWM87Bs2CrBauNmXkNm11Hk2di1rew8/";
    string public waiting_special_url = "https://gateway.pinata.cloud/ipfs/QmeeVrEW44rvVhhzUTJkPEv95h5sMArYRm59qxP4yxSEQA";
    string public waiting_url = "https://gateway.pinata.cloud/ipfs/QmS6wFpvTh8mMV6tmatjssz5ysp7rqCJ52ahxdmrzWENS6";
   
    uint256 public Startdate = 1634174383; // Oct 15, 16:00:00 UTC - 1634313600
    uint256 public Enddate = 1634197253; // Oct 16, 16:00:00 UTC - 1634400000

    string _name = "SpaceAliensNFT";
    string _symbol = "SANFT";
    
    address SB_address = 0x672C1f1C978b8FD1E9AE18e25D0E55176824989c; // mainnet SB address 
    ISpaceBoy SpaceBoyContract = ISpaceBoy(SB_address);
    
    address[] public whitelistedAddresses;
    mapping(uint256 => uint256) public SB_claimed_token_list;

    constructor() ERC721(_name, _symbol) {
        _initializeEIP712(_name);
        mintSpecial(_msgSender());
    }

    function set_reveal_special_1(bool result) external onlyOwner{
        reveal_special_1 = result;
    }
    function set_reveal_special_2(bool result) external onlyOwner{
        reveal_special_2 = result;
    }
    function set_reveal_special_3(bool result) external onlyOwner{
        reveal_special_3 = result;
    }
    function set_reveal_special_4(bool result) external onlyOwner{
        reveal_special_4 = result;
    }
    function set_reveal_all(bool result) external onlyOwner{
        reveal_all = result;
    }
    
    function _checkSBTokenClaim(uint256 SB_balance) private view returns (uint256[] memory) {
        uint256[] memory SB_unclaim_token_list = new uint[](SB_balance);
        uint256 SB_unclaim_token_index = 0;
        uint256 SB_claimed_token_count = 0;
        
        
        for (uint i = 0; i < SB_balance; i++) {
          uint256 SB_tokenID = SpaceBoyContract.tokenOfOwnerByIndex(_msgSender(), i);
          if (SB_tokenID > 0 && SB_claimed_token_list[SB_tokenID] == 0) { // never claim before
              SB_unclaim_token_list[SB_unclaim_token_index] = SB_tokenID;
              SB_unclaim_token_index++;
          } else {
              SB_claimed_token_count ++;
          }
        }
        
        if(SB_claimed_token_count > 0) {
            return SB_unclaim_token_list = new uint[](0);
        } else {
            return SB_unclaim_token_list;
        }
    }
    
    function _setSBTokenClaim(uint256[] memory SB_unclaimed_token_list) private {
        for (uint i = 0; i < SB_unclaimed_token_list.length; i++) {
            SB_claimed_token_list[SB_unclaimed_token_list[i]] = 1;  
        }
    }
    
    function mintGroup1() public {
        require(block.timestamp > Startdate, "The claim have not started yet.");
        require(block.timestamp < Enddate, "The claim have Ended.");
        require(_currentGroup1TokenId + 1 < Group1_MAX_SUPPLY, "Max Supply Reached");
        require(balanceOf(_msgSender()) == 0, "You have already Claimed Nft.");
        
        //only owner of >= 7 spaceboys
        uint256 SB_balance = SpaceBoyContract.balanceOf(_msgSender());
        uint256 [] memory SB_unclaimed_token_list = _checkSBTokenClaim(SB_balance);
        require(SB_unclaimed_token_list.length >= 7, 'only owners of 7 or more spaceboys could mint!');
        
        _setSBTokenClaim(SB_unclaimed_token_list);
        _mint(_msgSender(), _getNextGroup1TokenId());
        _incrementGroup1TokenId();
    }
    
    function mintGroup2() external {
        require(block.timestamp > Startdate, "The claim have not started yet.");
        require(block.timestamp < Enddate, "The claim have Ended.");
        require(_currentGroup2TokenId + 1 < MAX_SUPPLY, "Max Supply Reached");
        require(balanceOf(_msgSender()) == 0, "You have already Claimed Nft.");
        
        //only owner of 3-6 spaceboys
        uint256 SB_balance = SpaceBoyContract.balanceOf(_msgSender());
        uint256 [] memory SB_unclaimed_token_list = _checkSBTokenClaim(SB_balance);
        require(SB_unclaimed_token_list.length >= 3, 'only owners of 3 - 6 spaceboys could mint!');
        require(SB_unclaimed_token_list.length < 7, 'only owners of 3 - 6 spaceboys could mint!');
        
        _setSBTokenClaim(SB_unclaimed_token_list);
        _mint(_msgSender(), _getNextGroup2TokenId());
        _incrementGroup2TokenId();
    }
    
    function mintGroup3() external {
        require(block.timestamp > Startdate, "The claim have not started yet.");
        require(block.timestamp < Enddate, "The claim have Ended.");
        require(_currentGroup2TokenId + 1 < MAX_SUPPLY, "Max Supply Reached");
        require(isWhitelisted(_msgSender()) , "You are not in this group.");
        require(balanceOf(_msgSender()) == 0, "You have already Claimed Nft.");

        _mint(_msgSender(), _getNextGroup2TokenId());
        _incrementGroup2TokenId();
    }
    
    ////////
    
    //Owner Mint Functions
    function mintSpecial(address _to) public onlyOwner {
        for(uint256 i=0; i<special; i++){
            _mint(_to, i+1);
        }
    }
    
    function mintManyGroup1(uint256 num, address _to) public onlyOwner {
        require(_currentGroup1TokenId + num < Group1_MAX_SUPPLY, "Max Supply Reached");
        require(num <= 20, "Max 20 Allowed.");
        
        for(uint256 i=0; i<num; i++){
            _mint(_to, _getNextGroup1TokenId());
            _incrementGroup1TokenId();
        }
    }
    
    function mintManyGroup2(uint256 num, address _to) public onlyOwner {
        require(_currentGroup2TokenId + num < MAX_SUPPLY, "Max Supply Reached");
        require(num <= 20, "Max 20 Allowed.");
        
        for(uint256 i=0; i<num; i++){
            _mint(_to, _getNextGroup2TokenId());
            _incrementGroup2TokenId();
        }
    }
    
    
  function isWhitelisted(address _user) public view returns (bool) {
    for (uint i = 0; i < whitelistedAddresses.length; i++) {
      if (whitelistedAddresses[i] == _user) {
          return true;
      }
    }
    return false;
  }
  
  function whitelistUsers(address[] calldata _users) public onlyOwner {
    delete whitelistedAddresses;
    whitelistedAddresses = _users;
  }
  
  
  function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    function _getNextGroup1TokenId() private view returns (uint256) {
        return _currentGroup1TokenId.add(1);
    }


    function _incrementGroup1TokenId() private {
        require(_currentGroup1TokenId < Group1_MAX_SUPPLY);

        _currentGroup1TokenId++;
    }
    
    function _getNextGroup2TokenId() private view returns (uint256) {
        return _currentGroup2TokenId.add(1);
    }

    function _incrementGroup2TokenId() private {
        require(_currentGroup2TokenId < MAX_SUPPLY);

        _currentGroup2TokenId++;
    }

    function setBaseUri(string memory _uri) external onlyOwner {
        baseTokenURI = _uri;
    }

    function set_dates(uint256 start, uint256 end) external onlyOwner{
        Startdate = start;
        Enddate = end;
    }

    function tokenURI(uint256 _tokenId)
        public
        view
        override
        returns (string memory)
    {
        if (_tokenId == 1 && reveal_special_1 == false) {
            return waiting_special_url;
        }
        else if(_tokenId == 2 && reveal_special_2 == false) {
            return waiting_special_url;
        }
        else if(_tokenId == 3 && reveal_special_3 == false) {
            return waiting_special_url;
        }
        else if(_tokenId == 4 && reveal_special_4 == false) {
            return waiting_special_url;
        }
        else if(reveal_all == false) {
            return waiting_url;
        }
        
        return string(abi.encodePacked(baseTokenURI, Strings.toString(_tokenId), ".json"));
    }

    function _msgSender() internal view override returns (address sender) {
        return ContextMixin.msgSender();
    }
    function increaseMaxSupply(uint256 temp) external onlyOwner {
        MAX_SUPPLY = MAX_SUPPLY + temp;
    }
  
    
}
function mintSpecial(address _to) public onlyOwner {
    for(uint256 i=0; i<special; i++){
        _mint(_to, i+1);
    }
}

//Owner Mint Functions