contract DodgersNFT is CollectibleMinting {
    
    /// @dev Set in case the DodgersNFT contract requires an upgrade
    address public newContractAddress;

    string public constant MLB_Legal = "Major League Baseball trademarks and copyrights are used with permission of the applicable MLB entity.  All rights reserved.";

    // Time LS Oracle has to respond to detach requests
    uint32 public detachmentTime = 0;

    // Indicates if attached system is Active (Transfers will be blocked if attached and active)
    bool public attachedSystemActive;

    // Sale Manager Contract
    SaleManager public saleManagerAddress;

    /**
     * @dev DodgersNFT constructor.
     */
    constructor() public {
        // Starts paused.
        paused = true;
        managerPrimary = msg.sender;
        managerSecondary = msg.sender;
        bankManager = msg.sender;
        name_ = "LucidSight-DODGERS-NFT";
        symbol_ = "DNFTCB";
    }

    /**
     * @dev        Sets the address for the NFT Contract
     * @param      _saleManagerAddress  The nft address
     */
    function setSaleManagerAddress(address _saleManagerAddress) public onlyManager {
        require (_saleManagerAddress != address(0));
        saleManagerAddress = SaleManager(_saleManagerAddress);
    }

    /**
    * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator
    * @param _tokenId uint256 ID of the token to validate
    */
    modifier canTransfer(uint256 _tokenId) {
        uint256 isAttached = checkIsAttached(_tokenId);
        if(isAttached == 2) {
            //One-Time Auth for Physical Card Transfers
            require (msg.sender == managerPrimary ||
                msg.sender == managerSecondary ||
                msg.sender == bankManager ||
                otherManagers[msg.sender] == 1
            );
            updateIsAttached(_tokenId, 0);
        } else if(attachedSystemActive == true && isAttached >= 1) {
            require (msg.sender == managerPrimary ||
                msg.sender == managerSecondary ||
                msg.sender == bankManager ||
                otherManagers[msg.sender] == 1
            );
        }
        else {
            require (isApprovedOrOwner(msg.sender, _tokenId));
        }
    _;
    }

    /**
     * @dev Used to mark the smart contract as upgraded, in case of a issue
     * @param _v2Address    The new contract address
     */
    function setNewAddress(address _v2Address) external onlyManager {
        require (_v2Address != address(0));
        newContractAddress = _v2Address;
        emit ContractUpgrade(_v2Address);
    }

    /**
     * @dev Returns all the relevant information about a specific Collectible.
     * @notice Get details about your collectible
     * @param _tokenId              The token identifier
     * @return isAttached           Is Object attached
     * @return teamId               team identifier of the asset/token/collectible
     * @return positionId           position identifier of the asset/token/collectible
     * @return creationTime         creation timestamp
     * @return attributes           attribute of the asset/token/collectible
     * @return currentGameCardId    current game card of the asset/token/collectible
     * @return mlbGameID            mlb game identifier in which the asset/token/collectible was generated
     * @return playerOverrideId     player override identifier of the asset/token/collectible
     * @return playerStatus         status of the player (Rookie/Veteran/Historical)
     * @return playerHandedness     handedness of the asset
     * @return mlbPlayerId          official MLB Player Identifier
     */
    function getCollectibleDetails(uint256 _tokenId)
        external
        view
        returns (
        uint256 isAttached,
        uint32 sequenceId,
        uint8 teamId,
        uint8 positionId,
        uint64 creationTime,
        uint256 attributes,
        uint256 playerOverrideId,
        uint256 mlbGameId,
        uint256 currentGameCardId,
        uint256 mlbPlayerId,
        uint256 earnedBy,
        uint256 generationSeason
        ) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        
        attributes = obj.attributes;
        currentGameCardId = obj.currentGameCardId;
        mlbGameId = obj.mlbGameId;
        playerOverrideId = obj.playerOverrideId;
        mlbPlayerId = obj.mlbPlayerId;

        creationTime = uint64(obj.assetDetails);
        sequenceId = uint32(obj.assetDetails>>64);
        teamId = uint8(obj.assetDetails>>96);
        positionId = uint8(obj.assetDetails>>104);
        isAttached = obj.isAttached;
        earnedBy = obj.earnedBy;

        generationSeason = generationSeasonDict[(obj.attributes % 1000000) / 1000];
    }

    
    /**
     * @dev This is public rather than external so we can call super.unpause
     * without using an expensive CALL.
     */
    function unpause() public onlyManager {
        /// Actually unpause the contract.
        super.unpause();
    }

    /**
     * @dev Helper function to get the teamID of a collectible.To avoid using getCollectibleDetails
     * @notice Returns the teamID associated with the asset/collectible/token
     * @param _tokenId  The token identifier
     */
    function getTeamId(uint256 _tokenId) external view returns (uint256) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);

        uint256 teamId = uint256(uint8(obj.assetDetails>>96));
        return uint256(teamId);
    }

    /**
     * @dev Helper function to get the position of a collectible.To avoid using getCollectibleDetails
     * @notice Returns the position of the asset/collectible/token
     * @param _tokenId  The token identifier
     */
    function getPositionId(uint256 _tokenId) external view returns (uint256) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);

        uint256 positionId = uint256(uint8(obj.assetDetails>>104));

        return positionId;
    }

    /**
     * @dev Helper function to get the game card. To avoid using getCollectibleDetails
     * @notice Returns the gameCard associated with the asset/collectible/token
     * @param _tokenId  The token identifier
     */
    function getGameCardId(uint256 _tokenId) public view returns (uint256) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        return obj.currentGameCardId;
    }

    /**
     * @dev Returns isAttached property value for an asset/collectible/token
     * @param _tokenId  The token identifier
     */
    function checkIsAttached(uint256 _tokenId) public view returns (uint256) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        return obj.isAttached;
    }

    /**
     * @dev Helper function to get the attirbute of the collectible.To avoid using getCollectibleDetails
     * @notice Returns the ability of an asset/collectible/token from attributes.
     * @param _tokenId  The token identifier
     * @return ability  ability of the asset
     */
    function getAbilitiesForCollectibleId(uint256 _tokenId) external view returns (uint256 ability) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        uint256 _attributes = uint256(obj.attributes);
        ability = (_attributes % 1000);
    }

    /**
     * @dev Only allows trasnctions to go throught if the msg.sender is in the apporved list
     * @notice Updates the gameCardID properrty of the asset
     * @param _gameCardNumber  The game card number
     * @param _playerId        The player identifier
     */
    function updateCurrentGameCardId(uint256 _gameCardNumber, uint256 _playerId) public whenNotPaused {
        require (contractsApprovedList[msg.sender]);

        NFT memory obj  = _getAttributesOfToken(_playerId);
        
        obj.currentGameCardId = _gameCardNumber;
        
        if ( _gameCardNumber == 0 ) {
            obj.isAttached = 0;
        } else {
            obj.isAttached = 1;
        }

        allNFTs[_playerId] = obj;
    }

    /**
     * @dev Only Manager can add an attachment (special events) to the collectible
     * @notice Adds an attachment to collectible.
     * @param _tokenId  The token identifier
     * @param _attachment  The attachment
     */
    function addAttachmentToCollectible ( 
        uint256 _tokenId,
        uint256 _attachment)
        external
        onlyManager
        whenNotPaused {
        require (exists(_tokenId));

        nftCollectibleAttachments[_tokenId].push(_attachment);
        emit AssetUpdated(_tokenId);
    }

    /**
     * @dev It will remove the attachment form the collectible. We will need to re-add all attachment(s) if removed.
     * @notice Removes all attachments from collectible.
     * @param _tokenId  The token identifier
     */
    function removeAllAttachmentsFromCollectible(uint256 _tokenId)
        external
        onlyManager
        whenNotPaused {

        require (exists(_tokenId));
        
        delete nftCollectibleAttachments[_tokenId];
        emit AssetUpdated(_tokenId);
    }

    /**
     * @notice Transfers the ownership of NFT from one address to another address
     * @dev responsible for gifting assets to other user.
     * @param _to       to address
     * @param _tokenId  The token identifier
     */
    function giftAsset(address _to, uint256 _tokenId) public whenNotPaused {        
        safeTransferFrom(msg.sender, _to, _tokenId);
    }
    
    /**
     * @dev responsible for setting the tokenURI.
     * @notice The user/developper needs to add the tokenID, in the end of URL, to 
     * use the URI and get all details. Ex. www.<apiURL>.com/token/<tokenID>
     * @param _tokenURI  The token uri
     */
    function setTokenURIBase (string _tokenURI) public anyOperator {
        _setTokenURIBase(_tokenURI);
    }

    /**
     * @dev Allowed to be called by onlyGameManager to update a certain collectible playerOverrideID
     * @notice Sets the player override identifier.
     * @param _tokenId      The token identifier
     * @param _newOverrideId     The new player override identifier
     */
    function setPlayerOverrideId(uint256 _tokenId, uint256 _newOverrideId) public onlyManager whenNotPaused {
        require (exists(_tokenId));

        _updatePlayerOverrideId(_tokenId, _newOverrideId);
    }

    /**
     * @notice Updates the Generation Season Controller.
     * @dev Allowed to be called by onlyGameManager to update the generation season.
     * this helps to control the generation of collectible.
     * @param _season    Season UINT representation
     * @param _value    0-Not allowed, 1-open, >=2 Locked Forever
     */
    function updateGenerationStopTime(uint256 _season, uint8 _value ) public  onlyManager whenNotPaused {
        require (generationSeasonController[_season] == 1 && _value != 0);
        _updateGenerationSeasonFlag(_season, _value);
    }

    /**
     * @dev set Generation Season Controller, can only be called by Managers._season can be [0,1,2,3..] and 
     * _value can be [0,1,N].
     * @notice _value of 1: means generation of collectible is allowed. anything, apart from 1, wont allow generating assets for that season.
     * @param _season    Season UINT representation
     */
    function setGenerationSeasonController(uint256 _season) public onlyManager whenNotPaused {
        require (generationSeasonController[_season] == 0);
        _updateGenerationSeasonFlag(_season, 1);
    }

    /**
     * @dev Adding value to DICT helps in showing the season value in getCollectibleDetails
     * @notice Updates the Generation Season Dict.
     * @param _season    Season UINT representation
     * @param _value    0-Not allowed,1-allowed
     */
    function updateGenerationDict(uint256 _season, uint64 _value) public onlyManager whenNotPaused {
        require (generationSeasonDict[_season] <= 1);
        generationSeasonDict[_season] = _value;
    }

    /**
     * @dev Helper function to avoid calling getCollectibleDetails
     * @notice Gets the MLB player Id from the player attributes
     * @param _tokenId  The token identifier
     * @return playerId  MLB Player Identifier
     */
    function getPlayerId(uint256 _tokenId) external view returns (uint256 playerId) {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        playerId = ((obj.attributes.div(100000000000000000)) % 1000);
    }
    
    /**
     * @dev Helper function to avoid calling getCollectibleDetails
     * @notice Gets the attachments for an asset
     * @param _tokenId  The token identifier
     * @return attachments
     */
    function getAssetAttachment(uint256 _tokenId) external view returns (uint256[]) {
        uint256[] _attachments = nftCollectibleAttachments[_tokenId];
        uint256[] attachments;
        for(uint i=0;i<_attachments.length;i++){
            attachments.push(_attachments[i]);
        }
        
        return attachments;
    }

    /**
     * @dev Can only be trigerred by Managers. Updates the earnedBy property of the NFT
     * @notice Helps in updating the earned _by property of an asset/token.
     * @param  _tokenId        asser/token identifier
     * @param  _earnedBy       New asset/token DNA
     */
    function updateEarnedBy(uint256 _tokenId, uint256 _earnedBy) public onlyManager whenNotPaused {
        require (exists(_tokenId));

        _updateEarnedBy(_tokenId, _earnedBy);
    }

    /**
     * @dev A batch function to facilitate batching of asset creation. canCreate modifier
     * helps in controlling who can call the function
     * @notice Batch Function to Create Assets
     * @param      _teamId            The team identifier
     * @param      _attributes        The attributes
     * @param      _playerOverrideId  The player override identifier
     * @param      _mlbPlayerId       The mlb player identifier
     * @param      _to                To Address
     */
    function batchCreateAsset(
        uint8[] _teamId,
        uint256[] _attributes,
        uint256[] _playerOverrideId,
        uint256[] _mlbPlayerId,
        address[] _to)
        external
        canCreate
        whenNotPaused {
            require (isBatchSupported);

            require (_teamId.length > 0 && _attributes.length > 0 && 
                _playerOverrideId.length > 0 && _mlbPlayerId.length > 0 && 
                _to.length > 0);

            uint256 assetDetails;
            uint256[5] memory _nftData;
            
            for(uint ii = 0; ii < _attributes.length; ii++){
                require (_to[ii] != address(0) && _teamId[ii] != 0 && _attributes.length != 0 && 
                    _mlbPlayerId[ii] != 0);
                
                assetDetails = uint256(uint64(now));
                assetDetails |= uint256(getSequenceId(_teamId[ii]))<<64;
                assetDetails |= uint256(_teamId[ii])<<96;
                assetDetails |= uint256((_attributes[ii]/1000000000000000000000000000000000000000)-800)<<104;
        
                _nftData = [assetDetails, _attributes[ii], 0, _playerOverrideId[ii], _mlbPlayerId[ii]];
                
                _createNFTCollectible(_teamId[ii], _attributes[ii], _to[ii], 0, _nftData);
            }
        }

    /**
     * @dev A batch function to facilitate batching of asset creation for ETH Cards. canCreate modifier
     * helps in controlling who can call the function
     * @notice        Batch Function to Create Assets
     * @param      _teamId            The team identifier
     * @param      _attributes        The attributes
     * @param      _playerOverrideId  The player override identifier
     * @param      _mlbPlayerId       The mlb player identifier
     * @param      _to                { parameter_description }
     */
    function batchCreateETHCardAsset(
        uint8[] _teamId,
        uint256[] _attributes,
        uint256[] _playerOverrideId,
        uint256[] _mlbPlayerId,
        address[] _to)
        external
        canCreate
        whenNotPaused {
            require (isBatchSupported);

            require (_teamId.length > 0 && _attributes.length > 0
                        && _playerOverrideId.length > 0 &&
                        _mlbPlayerId.length > 0 && _to.length > 0);

            uint256 assetDetails;
            uint256[5] memory _nftData;

            for(uint ii = 0; ii < _attributes.length; ii++){

                require (_to[ii] != address(0) && _teamId[ii] != 0 && _attributes.length != 0 && 
                    _mlbPlayerId[ii] != 0);
        
                assetDetails = uint256(uint64(now));
                assetDetails |= uint256(getSequenceId(_teamId[ii]))<<64;
                assetDetails |= uint256(_teamId[ii])<<96;
                assetDetails |= uint256((_attributes[ii]/1000000000000000000000000000000000000000)-800)<<104;
        
                _nftData = [assetDetails, _attributes[ii], 0, _playerOverrideId[ii], _mlbPlayerId[ii]];
                
                _createNFTCollectible(_teamId[ii], _attributes[ii], _to[ii], 2, _nftData);
            }
        }

    /**
     * @dev        Overriden TransferFrom, with the modifier canTransfer which uses our attachment system
     * @notice     Helps in trasnferring assets
     * @param      _from     the address sending from
     * @param      _to       the address sending to
     * @param      _tokenId  The token identifier
     */
    function transferFrom(
        address _from,
        address _to,
        uint256 _tokenId
    )
        public
        canTransfer(_tokenId)
    {
        // Asset should not be in play
        require (checkIsAttached(_tokenId) == 0);
        
        require (_from != address(0));

        require (_to != address(0));

        clearApproval(_from, _tokenId);
        removeTokenFrom(_from, _tokenId);
        addTokenTo(_to, _tokenId);

        emit Transfer(_from, _to, _tokenId);
    }

    /**
     * @dev     Facilitates batch trasnfer of collectible with multiple TO Address, depending if batch is supported on contract.
     * @notice  Batch Trasnfer with multpple TO addresses
     * @param      _tokenIds  The token identifiers
     * @param      _fromB     the address sending from
     * @param      _toB       the address sending to
     */
    function multiBatchTransferFrom(
        uint256[] _tokenIds, 
        address[] _fromB, 
        address[] _toB) 
        public
    {
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _fromB.length > 0 && _toB.length > 0);

        uint256 _id;
        address _to;
        address _from;
        
        for (uint256 i = 0; i < _tokenIds.length; ++i) {

            require (_tokenIds[i] != 0 && _fromB[i] != 0 && _toB[i] != 0);

            _id = _tokenIds[i];
            _to = _toB[i];
            _from = _fromB[i];

            transferFrom(_from, _to, _id);
        }
        
    }
    
    /**
     * @dev     Facilitates batch trasnfer of collectible, depending if batch is supported on contract
     * @notice        Batch TransferFrom with the same to & from address
     * @param      _tokenIds  The asset identifiers
     * @param      _from      the address sending from
     * @param      _to        the address sending to
     */
    function batchTransferFrom(uint256[] _tokenIds, address _from, address _to) 
        public
    {
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _from != address(0) && _to != address(0));

        uint256 _id;
        
        for (uint256 i = 0; i < _tokenIds.length; ++i) {
            
            require (_tokenIds[i] != 0);

            _id = _tokenIds[i];

            transferFrom(_from, _to, _id);
        }
    }
    
    /**
     * @dev     Facilitates batch trasnfer of collectible, depending if batch is supported on contract.
     * Checks for collectible 0,address 0 and then performs the transfer
     * @notice        Batch SafeTransferFrom with multiple From and to Addresses
     * @param      _tokenIds  The asset identifiers
     * @param      _fromB     the address sending from
     * @param      _toB       the address sending to
     */
    function multiBatchSafeTransferFrom(
        uint256[] _tokenIds, 
        address[] _fromB, 
        address[] _toB
        )
        public
    {
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _fromB.length > 0 && _toB.length > 0);

        uint256 _id;
        address _to;
        address _from;
        
        for (uint256 i = 0; i < _tokenIds.length; ++i) {

            require (_tokenIds[i] != 0 && _fromB[i] != 0 && _toB[i] != 0);

            _id = _tokenIds[i];
            _to  = _toB[i];
            _from  = _fromB[i];

            safeTransferFrom(_from, _to, _id);
        }
    }

    /**
     * @dev     Facilitates batch trasnfer of collectible, depending if batch is supported on contract.
     * Checks for collectible 0,address 0 and then performs the transfer
     * @notice        Batch SafeTransferFrom from a single address to another address
     * @param      _tokenIds  The asset identifiers
     * @param      _from     the address sending from
     * @param      _to       the address sending to
     */
    function batchSafeTransferFrom(
        uint256[] _tokenIds, 
        address _from, 
        address _to
        )
        public
    {   
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _from != address(0) && _to != address(0));

        uint256 _id;
        for (uint256 i = 0; i < _tokenIds.length; ++i) {
            require (_tokenIds[i] != 0);
            _id = _tokenIds[i];
            safeTransferFrom(_from, _to, _id);
        }
    }

    /**
     * @notice     Batch Function to approve the spender
     * @dev        Helps to approve a batch of collectibles 
     * @param      _tokenIds  The asset identifiers
     * @param      _spender   The spender
     */
    function batchApprove(
        uint256[] _tokenIds, 
        address _spender
        )
        public
    {   
        require (isBatchSupported);

        require (_tokenIds.length > 0 && _spender != address(0));
        
        uint256 _id;
        for (uint256 i = 0; i < _tokenIds.length; ++i) {

            require (_tokenIds[i] != 0);
            
            _id = _tokenIds[i];
            approve(_spender, _id);
        }
        
    }

    /**
     * @dev        Batch Function to mark spender for approved for all. Does a check
     * for address(0) and throws if true
     * @notice     Facilitates batch approveAll
     * @param      _spenders  The spenders
     * @param      _approved  The approved
     */
    function batchSetApprovalForAll(
        address[] _spenders,
        bool _approved
        )
        public
    {   
        require (isBatchSupported);

        require (_spenders.length > 0);

        address _spender;
        for (uint256 i = 0; i < _spenders.length; ++i) {        

            require (address(_spenders[i]) != address(0));
                
            _spender = _spenders[i];
            setApprovalForAll(_spender, _approved);
        }
    }  
    
    /**
     * @dev        Function to request Detachment from our Contract
     * @notice     a wallet can request to detach it collectible, so, that it can be used in other third-party contracts.
     * @param      _tokenId  The token identifier
     */
    function requestDetachment(
        uint256 _tokenId
    )
        public
    {
        //Request can only be made by owner or approved address
        require (isApprovedOrOwner(msg.sender, _tokenId));

        uint256 isAttached = checkIsAttached(_tokenId);

        //If collectible is on a gamecard prevent detachment
        require(getGameCardId(_tokenId) == 0);

        require (isAttached >= 1);

        if(attachedSystemActive == true) {
            //Checks to see if request was made and if time elapsed
            if(isAttached > 1 && block.timestamp - isAttached > detachmentTime) {
                isAttached = 0;
            } else if(isAttached > 1) {
                //Forces Tx Fail if time is already set for attachment and not less than detachmentTime
                require (isAttached == 1);
            } else {
                //Is attached, set detachment time and make request to detach
                // emit AssetUpdated(_tokenId);
                isAttached = block.timestamp;
            }
        } else {
            isAttached = 0;
        }

        updateIsAttached(_tokenId, isAttached);
    }

    /**
     * @dev        Function to attach the asset, thus, restricting transfer
     * @notice     Attaches the collectible to our contract
     * @param      _tokenId  The token identifier
     */
    function attachAsset(
        uint256 _tokenId
    )
        public
        canTransfer(_tokenId)
    {
        uint256 isAttached = checkIsAttached(_tokenId);

        require (isAttached == 0);
        isAttached = 1;

        updateIsAttached(_tokenId, isAttached);

        emit AssetUpdated(_tokenId);
    }

    /**
     * @dev        Batch attach function
     * @param      _tokenIds  The identifiers
     */
    function batchAttachAssets(uint256[] _tokenIds) public {
        require (isBatchSupported);

        for(uint i = 0; i < _tokenIds.length; i++) {
            attachAsset(_tokenIds[i]);
        }
    }

    /**
     * @dev        Batch detach function
     * @param      _tokenIds  The identifiers
     */
    function batchDetachAssets(uint256[] _tokenIds) public {
        require (isBatchSupported);

        for(uint i = 0; i < _tokenIds.length; i++) {
            requestDetachment(_tokenIds[i]);
        }
    }

    /**
     * @dev        Function to facilitate detachment when contract is paused
     * @param      _tokenId  The identifiers
     */
    function requestDetachmentOnPause (uint256 _tokenId) public whenPaused {
        //Request can only be made by owner or approved address
        require (isApprovedOrOwner(msg.sender, _tokenId));

        updateIsAttached(_tokenId, 0);
    }

    /**
     * @dev        Toggle the Attachment Switch
     * @param      _state  The state
     */
    function toggleAttachedEnforcement (bool _state) public onlyManager {
        attachedSystemActive = _state;
    }

    /**
     * @dev        Set Attachment Time Period (this restricts user from continuously trigger detachment)
     * @param      _time  The time
     */
    function setDetachmentTime (uint256 _time) public onlyManager {
        //Detactment Time can not be set greater than 2 weeks.
        require (_time <= 1209600);
        detachmentTime = uint32(_time);
    }

    /**
     * @dev        Detach Asset From System
     * @param      _tokenId  The token iddentifier
     */
    function setNFTDetached(uint256 _tokenId) public anyOperator {
        require (checkIsAttached(_tokenId) > 0);

        updateIsAttached(_tokenId, 0);
    }

    /**
     * @dev        Batch function to detach multiple assets
     * @param      _tokenIds  The token identifiers
     */
    function setBatchDetachCollectibles(uint256[] _tokenIds) public anyOperator {
        uint256 _id;
        for(uint i = 0; i < _tokenIds.length; i++) {
            _id = _tokenIds[i];
            setNFTDetached(_id);
        }
    }

    /**
     * @dev        Function to update attach value
     * @param      _tokenId     The asset id
     * @param      _isAttached  Indicates if attached
     */
    function updateIsAttached(uint256 _tokenId, uint256 _isAttached) internal {
        NFT memory obj  = _getAttributesOfToken(_tokenId);
        
        obj.isAttached = _isAttached;
    
        allNFTs[_tokenId] = obj;
        emit AssetUpdated(_tokenId);
    }

    /**
    * @dev   Facilitates Creating Sale using the Sale Contract. Forces owner check & collectibleId check
    * @notice Helps a wallet to create a sale using our Sale Contract
    * @param      _tokenId        The token identifier
    * @param      _startingPrice  The starting price
    * @param      _endingPrice    The ending price
    * @param      _duration       The duration
    */
    function initiateCreateSale(uint256 _tokenId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration) external {
        require (_tokenId != 0);
        
        // If DodgersNFT is already on any sale, this will throw
        // because it will be owned by the sale contract.
        address owner = ownerOf(_tokenId);
        require (owner == msg.sender);

        // Sale contract checks input sizes
        require (_startingPrice == _startingPrice);
        require (_endingPrice == _endingPrice);
        require (_duration == _duration);

        require (checkIsAttached(_tokenId) == 0);
        
        // One time approval for the tokenID
        _approveForSale(msg.sender, address(saleManagerAddress), _tokenId);

        saleManagerAddress.createSale(_tokenId, _startingPrice, _endingPrice, _duration, msg.sender);
    }

    /**
     * @dev        Facilitates batch auction of collectibles, and enforeces strict checking on the collectibleId,starting/ending price, duration.
     * @notice     Batch function to put 10 or less collectibles on sale
     * @param      _tokenIds        The token identifier
     * @param      _startingPrices  The starting price
     * @param      _endingPrices    The ending price
     * @param      _durations       The duration
     */
    function batchCreateAssetSale(uint256[] _tokenIds, uint256[] _startingPrices, uint256[] _endingPrices, uint256[] _durations) external whenNotPaused {

        require (_tokenIds.length > 0 && _startingPrices.length > 0 && _endingPrices.length > 0 && _durations.length > 0);
        
        // Sale contract checks input sizes
        for(uint ii = 0; ii < _tokenIds.length; ii++){

            // Do not process for tokenId 0
            require (_tokenIds[ii] != 0);
            
            require (_startingPrices[ii] == _startingPrices[ii]);
            require (_endingPrices[ii] == _endingPrices[ii]);
            require (_durations[ii] == _durations[ii]);

            // If DodgersNFT is already on any sale, this will throw
            // because it will be owned by the sale contract.
            address _owner = ownerOf(_tokenIds[ii]);
            address _msgSender = msg.sender;
            require (_owner == _msgSender);

            // Check whether the collectible is inPlay. If inPlay cant put it on Sale
            require (checkIsAttached(_tokenIds[ii]) == 0);
            
            // approve token to for Sale creation
            _approveForSale(msg.sender, address(saleManagerAddress), _tokenIds[ii]);
            
            saleManagerAddress.createSale(_tokenIds[ii], _startingPrices[ii], _endingPrices[ii], _durations[ii], msg.sender);
        }
    }
}
/**
 * DodgersNFT manages all aspects of the Lucid Sight, Inc. CryptoBaseball.
 * @title DodgersNFT
 * @author Fazri Zubair & Farhan Khwaja (Lucid Sight, Inc.)
 */
function updateEarnedBy(uint256 _tokenId, uint256 _earnedBy) public onlyManager whenNotPaused {
    require (exists(_tokenId));

    _updateEarnedBy(_tokenId, _earnedBy);
}

/**
 * @dev Can only be trigerred by Managers. Updates the earnedBy property of the NFT
 * @notice Helps in updating the earned _by property of an asset/token.
 * @param  _tokenId        asser/token identifier
 * @param  _earnedBy       New asset/token DNA
 */