contract SpaceShibesInvasion is ERC721, ContextMixin, NativeMetaTransaction {

    constructor (string memory name_, string memory symbol_, IERC20 PRDX) ERC721(name_, symbol_) {
        _initializeEIP712(name_);
        PRDX_token = PRDX ; 
        owner = msg.sender ; 
    }
    
    address public owner ; 
    address public OpenSeaRegistry_address  = 0x207Fa8Df3a17D96Ca7EA4f2893fcdCb78a304101; 
    
    uint32 public max_SHIBES_ever = 2200 ; 
    
    uint256 public upgrade1_PRDX_cost = 500e18 ; 
    uint256 public upgrade2_PRDX_cost = 1000e18 ; 
    uint256 public rename_PRDX_cost = 250e18 ; 
  
    IERC20 public PRDX_token ; 

    //hash of composite NFT image
    uint256 public composite_hash = 0x01 ; 
    
    uint16 public stage1 = 500 ; 
    uint16 public stage2 = 1300 ;
    uint16 public stage3 = 1700 ; 
    uint16 public stage4 = 1950 ; 

    bool public upgrading_enabled = false ; 
    bool public minting_enabled = true ; 
    
    mapping(uint256 => bool) public genesis; 
  
    event NFTRenamed(address owner, uint256 NFT_ID, string new_name);
    event NFTUpgraded(address owner, uint256 NFT_ID, string attribute);
    
    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "Ownable: caller is not the owner");
        _;
    }
    
    function setBaseTokenURI(string calldata new_baseURI) public onlyOwner returns (bool success) {
        _baseURI = new_baseURI ; 
  
        success = true ; 
    }
    
    /**
     * @dev Set SEPA Token contract address
     * @param _addr Address of SEPA Token contract
     */
    function set_PRDX_contract(IERC20 _addr) external onlyOwner {
        PRDX_token = _addr ;
    }
    
    /**
     * @dev Enable upgrading of NFT's (see upgrade_NFT & rename_NFT)
     */ 
    function enable_upgrading() external onlyOwner {
        upgrading_enabled = true ; 
    }
    
    /**
     * @dev Disable minting (minting used for initial genesis creation)
     */
    function disable_minting() external onlyOwner {
        minting_enabled = false ; 
    }
    

    function set_registry_address(address _addr) external onlyOwner {
        OpenSeaRegistry_address = _addr ; 
    }
    
    receive() external payable {
        buy_NFT(1, address(0)) ; 
    }

    function buy_NFT(uint256 _amount, address payable ref_addr) public payable {
        require(totalSupply() + _amount <= max_SHIBES_ever, "All Shibes have been minted! :(") ;
        require(msg.sender != ref_addr, "Cannot ref yourself! ;)") ; 
        require(_amount <= 20, "Hey hey, slow down there! Leave some for the rest of us :D") ; 
      
        uint256 total_cost = 0;

        for (uint256 i = 0; i < _amount; i++) {
            if ((totalSupply() >= 0) && (totalSupply() < stage1)) {
                _safeMint(msg.sender, totalSupply());
                total_cost += 0.05 ether ; 
            } else {
              
            if ((totalSupply() >= stage1) && (totalSupply() < stage2)) {
                _safeMint(msg.sender, totalSupply());
                total_cost += 0.1 ether ; 
            } else {
              
            if ((totalSupply() >= stage2) && (totalSupply() < stage3)) {
                _safeMint(msg.sender, totalSupply());
                total_cost += 0.15 ether ;
            } else {
                
            if ((totalSupply() >= stage3) && (totalSupply() < stage4)) {
                _safeMint(msg.sender, totalSupply());
                total_cost += 0.2 ether ; 
            } else {
              
            if ((totalSupply() >= stage4)) {
                _safeMint(msg.sender, totalSupply());
                total_cost += 0.4 ether ; 
            }
              
        }}}}}
    
        
        if (ref_addr != address(0)) { //ref link used
            ref_addr.transfer(total_cost/40) ; 
            require(msg.value >= total_cost * 39 / 40, "Not enough funds to buy Shibes :'(") ; 

        } else { //no reflink used... shame!
            require(msg.value >= total_cost, "Not enough funds to buy Shibes :(") ; 
        }
        
    }
      
      function upgrade_NFT(uint256 tokenID, string calldata attribute) external {
          require(tokenID >= 106, "Cannot upgrade genesis!") ; 
          require(ownerOf(tokenID) == msg.sender, "Sender does not own NFT") ; 
          require(PRDX_token.transferFrom(msg.sender, address(this), upgrade1_PRDX_cost), "PRDX transfer failed! :(") ; 
          require(upgrading_enabled, "Oh hello there, nerd! Upgrading is not available... yet!") ; 
          
          emit NFTUpgraded(msg.sender, tokenID, attribute) ; 
      }
      
      function rename_NFT(uint256 tokenID, string calldata new_name) external {
          require(PRDX_token.transferFrom(msg.sender, address(this), rename_PRDX_cost), "PRDX transfer failed! :(") ; 
          require(ownerOf(tokenID) == msg.sender, "You must own the NFT to rename it!");
          require(upgrading_enabled, "Oh hello there, nerd! Upgrading is not available... yet!") ; 

          
          emit NFTRenamed(msg.sender, tokenID, new_name);
      }
      
      function withdraw_token(address payable _receiver, IERC20 contract_address) external onlyOwner {
          contract_address.transfer(_receiver, contract_address.balanceOf(address(this)));
      }
      
      function withdraw_ETH(address payable _receiver) external onlyOwner {
        _receiver.transfer(address(this).balance) ;

      }
      
      function set_upgrade_costs(uint256 _upgrade1_costs, uint256 _upgrade_2_costs, uint256 _rename_costs) external onlyOwner {
          upgrade1_PRDX_cost = _upgrade1_costs ; 
          upgrade2_PRDX_cost = _upgrade_2_costs ; 
          rename_PRDX_cost = _rename_costs ; 
      }    

    
    function mint(address to, uint256 tokenId)
        public onlyOwner
    {
        require(minting_enabled, "Cannot mint more") ; 
        _safeMint(to, tokenId);
    }
    
    function bulkMint(address[] calldata to, uint256[] calldata tokenID, uint256 _len) external onlyOwner {
        for (uint256 i; i < _len; i++) {
            mint(to[i], tokenID[i]) ; 
        }
    }

    /**
     * This is used instead of msg.sender as transactions won't be sent by the original token owner, but by OpenSea.
     */
    function _msgSender()
        internal
        override
        view
        returns (address payable sender)
    {
        return ContextMixin.msgSender();
    }

    /**
    * As another option for supporting trading without requiring meta transactions, override isApprovedForAll to whitelist OpenSea proxy accounts
    */
    function isApprovedForAll(
        address _owner,
        address _operator
    ) public override view returns (bool isOperator) {
        if (_operator == address(OpenSeaRegistry_address)) {
            return true;
        }
        
        return ERC721.isApprovedForAll(_owner, _operator);
    }
}
function isApprovedForAll(
        address _owner,
        address _operator
    ) public override view returns (bool isOperator) {
        if (_operator == address(OpenSeaRegistry_address)) {
            return true;
        }
        
        return ERC721.isApprovedForAll(_owner, _operator);
    }
/**
 * As another option for supporting trading without requiring meta transactions, override isApprovedForAll to whitelist OpenSea proxy accounts
 */