// Contract: SpaceAliens
// Target function: function mintSpecial(address _to) public onlyOwner
 /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index)
        public
        view
        virtual
        override
        returns (uint256)
    {
        require(
            index < ERC721Enumerable.totalSupply(),
            "ERC721Enumerable: global index out of bounds"
        );
        return _allTokens[index];
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId)
        private
    {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}
contract Initializable {
    bool inited = false;

    modifier initializer() {
        require(!inited, "already inited");
        _;
        inited = true;
    }
}
contract Migrations {
  address public owner;
  uint public last_completed_migration;

  constructor() {
    owner = msg.sender;
  }

  modifier restricted() {
    if (msg.sender == owner) _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }

  function upgrade(address new_address) public restricted {
    Migrations upgraded = Migrations(new_address);
    upgraded.setCompleted(last_completed_migration);
  }
}
contract EIP712Base is Initializable {
    struct EIP712Domain {
        string name;
        string version;
        address verifyingContract;
        bytes32 salt;
    }

    string public constant ERC712_VERSION = "1";

    bytes32 internal constant EIP712_DOMAIN_TYPEHASH =
        keccak256(
            bytes(
                "EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)"
            )
        );
    bytes32 internal domainSeperator;

    // supposed to be called once while initializing.
    // one of the contracts that inherits this contract follows proxy pattern
    // so it is not possible to do this in a constructor
    function _initializeEIP712(string memory name) internal initializer {
        _setDomainSeperator(name);
    }

    function _setDomainSeperator(string memory name) internal {
        domainSeperator = keccak256(
            abi.encode(
                EIP712_DOMAIN_TYPEHASH,
                keccak256(bytes(name)),
                keccak256(bytes(ERC712_VERSION)),
                address(this),
                bytes32(getChainId())
            )
        );
    }

    function getDomainSeperator() public view returns (bytes32) {
        return domainSeperator;
    }

    function getChainId() public view returns (uint256) {
        uint256 id;
        assembly {
            id := chainid()
        }
        return id;
    }

    /**
     * Accept message hash and returns hash message in EIP712 compatible form
     * So that it can be used to recover signer from signature signed using EIP712 formatted data
     * https://eips.ethereum.org/EIPS/eip-712
     * "\\x19" makes the encoding deterministic
     * "\\x01" is the version byte to make it compatible to EIP-191
     */
    function toTypedMessageHash(bytes32 messageHash)
        internal
        view
        returns (bytes32)
    {
        return
            keccak256(
                abi.encodePacked("\x19\x01", getDomainSeperator(), messageHash)
            );
    }
}
contract NativeMetaTransaction is EIP712Base {
    using SafeMath for uint256;
    bytes32 private constant META_TRANSACTION_TYPEHASH =
        keccak256(
            bytes(
                "MetaTransaction(uint256 nonce,address from,bytes functionSignature)"
            )
        );
    event MetaTransactionExecuted(
        address userAddress,
        address payable relayerAddress,
        bytes functionSignature
    );
    mapping(address => uint256) nonces;

    /*
     * Meta transaction structure.
     * No point of including value field here as if user is doing value transfer then he has the funds to pay for gas
     * He should call the desired function directly in that case.
     */
    struct MetaTransaction {
        uint256 nonce;
        address from;
        bytes functionSignature;
    }

    function executeMetaTransaction(
        address userAddress,
        bytes memory functionSignature,
        bytes32 sigR,
        bytes32 sigS,
        uint8 sigV
    ) public payable returns (bytes memory) {
        MetaTransaction memory metaTx = MetaTransaction({
            nonce: nonces[userAddress],
            from: userAddress,
            functionSignature: functionSignature
        });

        require(
            verify(userAddress, metaTx, sigR, sigS, sigV),
            "Signer and signature do not match"
        );

        // increase nonce for user (to avoid re-use)
        nonces[userAddress] = nonces[userAddress].add(1);

        emit MetaTransactionExecuted(
            userAddress,
            payable(msg.sender),
            functionSignature
        );

        // Append userAddress and relayer address at the end to extract it from calling context
        (bool success, bytes memory returnData) = address(this).call(
            abi.encodePacked(functionSignature, userAddress)
        );
        require(success, "Function call not successful");

        return returnData;
    }

    function hashMetaTransaction(MetaTransaction memory metaTx)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    META_TRANSACTION_TYPEHASH,
                    metaTx.nonce,
                    metaTx.from,
                    keccak256(metaTx.functionSignature)
                )
            );
    }

    function getNonce(address user) public view returns (uint256 nonce) {
        nonce = nonces[user];
    }

    function verify(
        address signer,
        MetaTransaction memory metaTx,
        bytes32 sigR,
        bytes32 sigS,
        uint8 sigV
    ) internal view returns (bool) {
        require(signer != address(0), "NativeMetaTransaction: INVALID_SIGNER");
        return
            signer ==
            ecrecover(
                toTypedMessageHash(hashMetaTransaction(metaTx)),
                sigV,
                sigR,
                sigS
            );
    }
}

interface ISpaceBoy {
    function balanceOf(address owner) external view returns (uint256);
    function tokenOfOwnerByIndex(address owner, uint256 tokenIndex) external view returns (uint256);
}


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();
    }
    
    ////////