// Contract: GangsterAllStarEvolution
// Target function: function _isApprovedOrOwner(address spender_, uint256 tokenId_) internal
 }

    ///// ERC721G: Internal Single-Contract Staking Logic /////
    
    /** @dev explanation:
     *  _initializeTokenIf() is used as a beginning-hook to functions that require
     *  that the token is explicitly INITIALIZED before the function is able to be used.
     *  It will check if the _tokenData slot is initialized or not. 
     *  If it is not, it will initialize it.
     *  Used internally for staking logic.
     */
    function _initializeTokenIf(uint256 tokenId_, OwnerStruct memory _OwnerStruct) 
    internal virtual {
        // If the target _tokenData is not initialized, initialize it.
        if (_tokenData[tokenId_].owner == address(0)) {
            _tokenData[tokenId_] = _OwnerStruct;
        }
    }

    /** @dev explanation:
     *  _setStakeTimestamp() is our staking / unstaking logic.
     *  If timestamp_ is > 0, the action is "stake"
     *  If timestamp_ is == 0, the action is "unstake"
     * 
     *  We grab the tokenData using _getTokenDataOf and then read its values.
     *  As this function requires INITIALIZED tokens only, we call _initializeTokenIf()
     *  to initialize any token using this function first.
     * 
     *  Processing of the function is explained in in-line comments.
     */
    function _setStakeTimestamp(uint256 tokenId_, uint256 timestamp_)
    internal virtual returns (address) {
        // First, call _getTokenDataOf and grab the relevant tokenData
        OwnerStruct memory _OwnerStruct = _getTokenDataOf(tokenId_);
        address _owner = _OwnerStruct.owner;
        uint32 _stakeTimestamp = _OwnerStruct.stakeTimestamp;

        // _setStakeTimestamp requires initialization
        _initializeTokenIf(tokenId_, _OwnerStruct);

        // Clear any token approvals
        delete getApproved[tokenId_];

        // if timestamp_ > 0, the action is "stake"
        if (timestamp_ > 0) {
            // Make sure that the token is not staked already
            require(_stakeTimestamp == 0,
                "ERC721G: _setStakeTimestamp() already staked");
            
            // Callbrate balances between staker and stakingAddress
            unchecked { 
                _balanceData[_owner].balance--;
                _balanceData[stakingAddress()].balance++;
            }

            // Emit Transfer event from trueOwner
            emit Transfer(_owner, stakingAddress(), tokenId_);
        }

        // if timestamp_ == 0, the action is "unstake"
        else {
            // Make sure the token is not staked
            require(_stakeTimestamp != 0,
                "ERC721G: _setStakeTimestamp() already unstaked");
            
            // Callibrate balances between stakingAddress and staker
            unchecked {
                _balanceData[_owner].balance++;
                _balanceData[stakingAddress()].balance--;
            }
            
            // we add total time staked to the token on unstake
            uint32 _timeStaked = _getBlockTimestampCompressed() - _stakeTimestamp;
            _tokenData[tokenId_].totalTimeStaked += _timeStaked;

            // Emit Transfer event to trueOwner
            emit Transfer(stakingAddress(), _owner, tokenId_);
        }

        // Set the stakeTimestamp to timestamp_
        _tokenData[tokenId_].stakeTimestamp = _compressTimestamp(timestamp_);

        // We save internal gas by returning the owner for a follow-up function
        return _owner;
    }

    /** @dev explanation:
     *  _stake() works like an extended function of _setStakeTimestamp()
     *  where the logic of _setStakeTimestamp() runs and returns the _owner address
     *  afterwards, we do the post-hook required processing to finish the staking logic 
     *  in this function.
     * 
     *  Processing logic explained in in-line comments.
     */
    function _stake(uint256 tokenId_) internal virtual returns (address) {
        // set the stakeTimestamp to block.timestamp and return the owner
        return _setStakeTimestamp(tokenId_, block.timestamp);
    }

    /** @dev explanation:
     *  _unstake() works like an extended unction of _setStakeTimestamp()
     *  where the logic of _setStakeTimestamp() runs and returns the _owner address
     *  afterwards, we do the post-hook required processing to finish the unstaking logic
     *  in this function.
     * 
     *  Processing logic explained in in-line comments.
     */
    function _unstake(uint256 tokenId_) internal virtual returns(address) {
        // set the stakeTimestamp to 0 and return the owner
        return _setStakeTimestamp(tokenId_, 0);
    }

    /** @dev explanation:
     *  _mintAndStakeInternal() is the internal mintAndStake function that is called
     *  to mintAndStake tokens to users. 
     * 
     *  It populates mintIndex with the phantom-mint data (owner, lastTransferTime)
     *  as well as the phantom-stake data (stakeTimestamp)
     * 
     *  Then, it emits the necessary phantom events to replicate the behavior as canon.
     * 
     *  Further logic explained in in-line comments.
     */
    function _mintAndStakeInternal(address to_, uint256 amount_) internal virtual {
        // we cannot mint to 0x0
        require(to_ != address(0), "ERC721G: _mintAndStakeInternal to 0x0");

        // we limit max mints per SSTORE to prevent expensive gas lookup
        require(amount_ <= maxBatchSize, 
            "ERC721G: _mintAndStakeInternal over maxBatchSize");

        // process the required variables to write to mintIndex 
        uint256 _startId = tokenIndex;
        uint256 _endId = _startId + amount_;
        uint32 _currentTime = _getBlockTimestampCompressed();

        // write to the mintIndex to store the OwnerStruct for uninitialized tokenData
        mintIndex[_startId] = OwnerStruct(
            to_, // the address the token is minted to
            _currentTime, // the last transfer time
            _currentTime, // the curent time of staking
            0 // the accumulated time staked
        );

        unchecked { 
            // we add the balance to the stakingAddress through our staking logic
            _balanceData[stakingAddress()].balance += uint32(amount_);

            // we add the mintedAmount to the to_ through our minting logic
            _balanceData[to_].mintedAmount += uint32(amount_);

            // emit phantom mint to to_, then emit a staking transfer
            do { 
                emit Transfer(address(0), to_, _startId);
                emit Transfer(to_, stakingAddress(), _startId);

                // /** @dev testing:
                // *  emitting a TokenStaked event for testing
                // */
                // emit TokenStaked(_startId, to_, _currentTime);

            } while (++_startId < _endId);
        }

        // set the new tokenIndex to the _endId
        tokenIndex = _endId;
    }

    /** @dev explanation: 
     *  _mintAndStake() calls _mintAndStakeInternal() but calls it using a while-loop
     *  based on the required minting amount to stay within the bounds of 
     *  max mints per batch (maxBatchSize)
     */
    function _mintAndStake(address to_, uint256 amount_) internal virtual {
        uint256 _amountToMint = amount_;
        while (_amountToMint > maxBatchSize) {
            _amountToMint -= maxBatchSize;
            _mintAndStakeInternal(to_, maxBatchSize);
        }
        _mintAndStakeInternal(to_, _amountToMint);
    }

    ///// ERC721G Range-Based Internal Minting Logic /////
    
    /** @dev explanation:
     *  _mintInternal() is our internal batch minting logic. 
     *  First, we store the uninitialized pointer at mintIndex of _startId
     *  Then, we process the balances changes
     *  Finally, we phantom-mint the tokens using Transfer events loop.
     */
    function _mintInternal(address to_, uint256 amount_) internal virtual {
        // cannot mint to 0x0
        require(to_ != address(0), "ERC721G: _mintInternal to 0x0");

        // we limit max mints to prevent expensive gas lookup
        require(amount_ <= maxBatchSize, 
            "ERC721G: _mintInternal over maxBatchSize");

        // process the token id data
        uint256 _startId = tokenIndex;
        uint256 _endId = _startId + amount_;

        // push the required phantom mint data to mintIndex
        mintIndex[_startId].owner = to_;
        mintIndex[_startId].lastTransfer = _getBlockTimestampCompressed();

        // process the balance changes and do a loop to phantom-mint the tokens to to_
        unchecked { 
            _balanceData[to_].balance += uint32(amount_);
            _balanceData[to_].mintedAmount += uint32(amount_);

            do { emit Transfer(address(0), to_, _startId); } while (++_startId < _endId);
        }

        // set the new token index
        tokenIndex = _endId;
    }

    /** @dev explanation:
     *  _mint() is the function that calls _mintInternal() using a while-loop
     *  based on the maximum batch size (maxBatchSize)
     */
    function _mint(address to_, uint256 amount_) internal virtual {
        uint256 _amountToMint = amount_;
        while (_amountToMint > maxBatchSize) {
            _amountToMint -= maxBatchSize;
            _mintInternal(to_, maxBatchSize);
        }
        _mintInternal(to_, _amountToMint);
    }

    /** @dev explanation:
     *  _transfer() is the internal function that transfers the token from_ to to_
     *  it has ERC721-standard require checks
     *  and then uses solmate-style approval clearing
     * 
     *  afterwards, it sets the _tokenData to the data of the to_ (transferee) as well as
     *  set the balanceData.
     *  
     *  this results in INITIALIZATION of the token, if it has not been initialized yet. 
     */
    function _transfer(address from_, address to_, uint256 tokenId_) internal virtual {
        // the from_ address must be the ownerOf
        require(from_ == ownerOf(tokenId_), "ERC721G: _transfer != ownerOf");
        // cannot transfer to 0x0
        require(to_ != address(0), "ERC721G: _transfer to 0x0");

        // delete any approvals
        delete getApproved[tokenId_];

        // set _tokenData to to_
        _tokenData[tokenId_].owner = to_;
        _tokenData[tokenId_].lastTransfer = _getBlockTimestampCompressed();

        // update the balance data
        unchecked { 
            _balanceData[from_].balance--;
            _balanceData[to_].balance++;
        }

        // emit a standard Transfer
        emit Transfer(from_, to_, tokenId_);
    }

    ///// ERC721G: User-Enabled Out-of-the-box Staking Functionality /////
    /** @dev clarification:
     *  As a developer, you DO NOT have to enable these functions, or use them
     *  in the way defined in this section. 
     * 
     *  The functions in this section are just out-of-the-box plug-and-play staking
     *  which is enabled IMMEDIATELY.
     *  (As well as some useful view-functions)
     * 
     *  You can choose to call the internal staking functions yourself, to create 
     *  custom staking logic based on the section (n-2) above.
     */

    /** @dev explanation:
     *  this is a staking function that receives calldata tokenIds_ array
     *  and loops to call internal _stake in a gas-efficient way 
     *  written in a shorthand-style syntax
     */
    function stake(uint256[] calldata tokenIds_) public virtual {
        uint256 i;
        uint256 l = tokenIds_.length;
        while (i < l) { 
            // stake and return the owner's address
            address _owner = _stake(tokenIds_[i]); 
            // make sure the msg.sender is the owner
            require(msg.sender == _owner, "You are not the owner!");
            unchecked {++i;}
        }
    }
    /** @dev explanation:
     *  this is an unstaking function that receives calldata tokenIds_ array
     *  and loops to call internal _unstake in a gas-efficient way 
     *  written in a shorthand-style syntax
     */
    function unstake(uint256[] calldata tokenIds_) public virtual {
        uint256 i;
        uint256 l = tokenIds_.length;
        while (i < l) { 
            // unstake and return the owner's address
            address _owner = _unstake(tokenIds_[i]); 
            // make sure the msg.sender is the owner
            require(msg.sender == _owner, "You are not the owner!");
            unchecked {++i;}
        }
    }

    ///// ERC721G: User-Enabled Out-of-the-box Staking View Functions /////
    /** @dev explanation:
     *  balanceOfStaked loops through the entire tokens using 
     *  startTokenId as the start pointer, and 
     *  tokenIndex (current-next tokenId) as the end pointer
     * 
     *  it checks if the _trueOwnerOf() is the address_ or not
     *  and if the owner() is not the address, indicating the 
     *  state that the token is staked.
     * 
     *  if so, it increases the balance. after the loop, it returns the balance.
     * 
     *  this is mainly for external view only. 
     *  !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
     */
    function balanceOfStaked(address address_) public virtual view 
    returns (uint256) {
        uint256 _balance;
        uint256 i = startTokenId;
        uint256 max = tokenIndex;
        while (i < max) {
            if (ownerOf(i) != address_ && _trueOwnerOf(i) == address_) {
                _balance++;
            }
            unchecked { ++i; }
        }
        return _balance;
    }

    /** @dev explanation:
     *  walletOfOwnerStaked calls balanceOfStaked to get the staked 
     *  balance of a user. Afterwards, it runs staked-checking logic
     *  to figure out the tokenIds that the user has staked
     *  and then returns it in walletOfOwner fashion.
     * 
     *  this is mainly for external view only.
     *  !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
     */
    function walletOfOwnerStaked(address address_) public virtual view
    returns (uint256[] memory) {
        uint256 _balance = balanceOfStaked(address_);
        uint256[] memory _tokens = new uint256[] (_balance);
        uint256 _currentIndex;
        uint256 i = startTokenId;
        while (_currentIndex < _balance) {
            if (ownerOf(i) != address_ && _trueOwnerOf(i) == address_) {
                _tokens[_currentIndex++] = i;
            }
            unchecked { ++i; }
        }
        return _tokens;
    }

    /** @dev explanation:
     *  balanceOf of the address returns UNSTAKED tokens only.
     *  to get the total balance of the user containing both STAKED and UNSTAKED tokens,
     *  we use this function. 
     * 
     *  this is mainly for external view only.
     *  !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
     */
    function totalBalanceOf(address address_) public virtual view returns (uint256) {
        return balanceOf(address_) + balanceOfStaked(address_);
    }

    /** @dev explanation:
     *  totalTimeStakedOfToken returns the accumulative total time staked of a tokenId
     *  it reads from the totalTimeStaked of the tokenId_ and adds it with 
     *  a calculation of pending time staked and returns the sum of both values.
     * 
     *  this is mainly for external view / use only.
     *  this function can be interfaced with contract writes.
     */
    function totalTimeStakedOfToken(uint256 tokenId_) public virtual view 
    returns (uint256) {
        OwnerStruct memory _OwnerStruct = _getTokenDataOf(tokenId_);
        uint256 _totalTimeStakedOnToken = _expandTimestamp(_OwnerStruct.totalTimeStaked);
        uint256 _totalTimeStakedPending = 
            _OwnerStruct.stakeTimestamp > 0 ?
            _expandTimestamp(
                _getBlockTimestampCompressed() - _OwnerStruct.stakeTimestamp) : 
                0;

        return _totalTimeStakedOnToken + _totalTimeStakedPending;
    }

    /** @dev explanation:
     *  totalTimeStakedOfTokens just returns an array of totalTimeStakedOfToken
     *  based on tokenIds_ calldata.
     *  
     *  this is mainly for external view / use only.
     *  this function can be interfaced with contract writes... however
     *  BE CAREFUL and USE IT CORRECTLY. 
     *  (dont pass in 5000 tokenIds_ in a write function)
     */
    function totalTimeStakedOfTokens(uint256[] calldata tokenIds_) public
    virtual view returns (uint256[] memory) {
        uint256 i;
        uint256 l = tokenIds_.length;
        uint256[] memory _totalTimeStakeds = new uint256[] (l);
        while (i < l) {
            _totalTimeStakeds[i] = totalTimeStakedOfToken(tokenIds_[i]);
            unchecked { ++i; }
        }
        return _totalTimeStakeds;
    }

    ///// ERC721G: ERC721 Standard Logic /////