library TokenDataOps {
    function getTokenData(uint256 tokenData) internal view returns (TokenData memory) {
        return
            TokenData({
                owner: TokenDataOps.owner(tokenData),
                lastTransfer: TokenDataOps.lastTransfer(tokenData),
                ownerCount: TokenDataOps.ownerCount(tokenData),
                staked: TokenDataOps.staked(tokenData),
                mintAndStake: TokenDataOps.mintAndStake(tokenData),
                nextTokenDataSet: TokenDataOps.nextTokenDataSet(tokenData),
                level: TokenDataOps.level(tokenData),
                role: TokenDataOps.role(tokenData),
                rarity: TokenDataOps.rarity(tokenData)
            });
    }

    function newTokenData(
        address owner_,
        uint256 lastTransfer_,
        bool stake_
    ) internal pure returns (uint256) {
        uint256 tokenData = (uint256(uint160(owner_)) | (lastTransfer_ << 160) | (uint256(1) << 200));
        return stake_ ? setstaked(setMintAndStake(tokenData)) : tokenData;
    }

    function copy(uint256 tokenData) internal pure returns (uint256) {
        // tokenData minus the token specific flags (4/2bits), i.e. only owner, lastTransfer, ownerCount
        // stake flag (& mintAndStake flag) carries over if mintAndStake was called
        return tokenData & (RESTRICTED_TOKEN_DATA >> (mintAndStake(tokenData) ? 2 : 4));
    }

    function owner(uint256 tokenData) internal view returns (address) {
        if (staked(tokenData)) return address(this);
        return trueOwner(tokenData);
    }

    function setOwner(uint256 tokenData, address owner_) internal pure returns (uint256) {
        return (tokenData & 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000) | uint160(owner_);
    }

    function staked(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 220) & uint256(1)) > 0; // Note: this can carry over when calling 'ownerOf'
    }

    function setstaked(uint256 tokenData) internal pure returns (uint256) {
        return tokenData | (uint256(1) << 220);
    }

    function unsetstaked(uint256 tokenData) internal pure returns (uint256) {
        return tokenData & ~(uint256(1) << 220);
    }

    function mintAndStake(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 221) & uint256(1)) > 0;
    }

    function setMintAndStake(uint256 tokenData) internal pure returns (uint256) {
        return tokenData | (uint256(1) << 221);
    }

    function unsetMintAndStake(uint256 tokenData) internal pure returns (uint256) {
        return tokenData & ~(uint256(1) << 221);
    }

    function nextTokenDataSet(uint256 tokenData) internal pure returns (bool) {
        return ((tokenData >> 222) & uint256(1)) > 0;
    }

    function flagNextTokenDataSet(uint256 tokenData) internal pure returns (uint256) {
        return tokenData | (uint256(1) << 222); // nextTokenDatatSet flag (don't repeat the read/write)
    }

    function trueOwner(uint256 tokenData) internal pure returns (address) {
        return address(uint160(tokenData));
    }

    function ownerCount(uint256 tokenData) internal pure returns (uint256) {
        return (tokenData >> 200) & 0xFFFFF;
    }

    function incrementOwnerCount(uint256 tokenData) internal pure returns (uint256) {
        uint256 newOwnerCount = min(ownerCount(tokenData) + 1, 0xFFFFF);
        return (tokenData & ~(uint256(0xFFFFF) << 200)) | (newOwnerCount << 200);
    }

    function resetOwnerCount(uint256 tokenData) internal pure returns (uint256) {
        uint256 count = min(ownerCount(tokenData), 2); // keep minter status
        return (tokenData & ~(uint256(0xFFFFF) << 200)) | (count << 200);
    }

    function lastTransfer(uint256 tokenData) internal pure returns (uint256) {
        return (tokenData >> 160) & 0xFFFFFFFFFF;
    }

    function setLastTransfer(uint256 tokenData, uint256 timestamp) internal pure returns (uint256) {
        return (tokenData & 0xFFFFFFFFFFFFFF0000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) | (timestamp << 160);
    }

    // MadMouse
    function level(uint256 tokenData) internal pure returns (uint256) {
        unchecked {
            return 1 + (tokenData >> 252);
        }
    }

    function increaseLevel(uint256 tokenData) internal pure returns (uint256) {
        unchecked {
            return tokenData + (uint256(1) << 252);
        }
    }

    function role(uint256 tokenData) internal pure returns (uint256) {
        return (tokenData >> 248) & 0xF;
    }

    function rarity(uint256 tokenData) internal pure returns (uint256) {
        return (tokenData >> 244) & 0xF;
    }

    // these slots should be are already 0
    function setRoleAndRarity(uint256 tokenData, uint256 dna) internal pure returns (uint256) {
        return ((tokenData & ~(uint256(0xFF) << 244)) | (DNAOps.toRole(dna) << 248) | (DNAOps.toRarity(dna) << 244));
    }
}
function level(uint256 tokenData) internal pure returns (uint256) {
    unchecked {
        return 1 + (tokenData >> 252);
    }
}

// MadMouse