contract ToyCreation is Ownable, ExternalTokenHandler, ToyInterfaceSupport {
    //-------------------------------------------------------------------------
    /// @dev Link emits when an empty TOY Token gets assigned to a valid RFID.
    //-------------------------------------------------------------------------
    event Link(uint _oldUid, uint _newUid);

    // PLAY needed to mint one TOY Token (in pWei)
    uint public priceToMint = 1000 * 10**18;
    // Buffer added to the front of every TOY Token at time of creation. TOY
    //  Tokens with a uid greater than the buffer are unlinked.
    uint constant uidBuffer = 0x0100000000000000; // 14 zeroes
    // PLAY Token Contract object to interface with.
    PlayInterface play = PlayInterface(0xe2427cfEB5C330c007B8599784B97b65b4a3A819);

    //-------------------------------------------------------------------------
    /// @notice Update PLAY Token contract variable with new contract address.
    /// @dev Throws if `_newAddress` is the zero address.
    /// @param _newAddress Updated contract address.
    //-------------------------------------------------------------------------
    function updatePlayTokenContract(address _newAddress) external onlyOwner {
        play = PlayInterface(_newAddress);
    }

    //-------------------------------------------------------------------------
    /// @notice Change the number of PLAY tokens needed to mint a new TOY Token
    ///  (in pWei).
    /// @dev Throws if `_newPrice` is zero.
    /// @param _newPrice The new price to mint (in pWei)
    //-------------------------------------------------------------------------
    function changeToyPrice(uint _newPrice) external onlyOwner {
        priceToMint = _newPrice;
    }

    //-------------------------------------------------------------------------
    /// @notice Send and lock PLAY to mint a new empty TOY Token for yourself.
    /// @dev Sender must have approved this contract address as an authorized
    ///  spender with at least "priceToMint" PLAY. Throws if the sender has
    ///  insufficient PLAY. Throws if sender has not granted this contract's
    ///  address sufficient allowance.
    //-------------------------------------------------------------------------
    function mint() external {
        play.lockFrom (msg.sender, 2, priceToMint);

        uint uid = uidBuffer + toyArray.length;
        uint index = toyArray.push(ToyToken(msg.sender, uid, 0, 0, ""));
        uidToToyIndex[uid] = index - 1;

        emit Transfer(0, msg.sender, uid);
    }

    //-------------------------------------------------------------------------
    /// @notice Send and lock PLAY to mint a new empty TOY Token for 'to'.
    /// @dev Sender must have approved this contract address as an authorized
    ///  spender with at least "priceToMint" PLAY. Throws if the sender has
    ///  insufficient PLAY. Throws if sender has not granted this contract's
    ///  address sufficient allowance.
    /// @param _to The address to deduct PLAY Tokens from and send new TOY Token to.
    //-------------------------------------------------------------------------
    function mintAndSend(address _to) external {
        play.lockFrom (msg.sender, 2, priceToMint);

        uint uid = uidBuffer + toyArray.length;
        uint index = toyArray.push(ToyToken(_to, uid, 0, 0, ""));
        uidToToyIndex[uid] = index - 1;

        emit Transfer(0, _to, uid);
    }

    //-------------------------------------------------------------------------
    /// @notice Send and lock PLAY to mint `_amount` new empty TOY Tokens for
    ///  yourself.
    /// @dev Sender must have approved this contract address as an authorized
    ///  spender with at least "priceToMint" x `_amount` PLAY. Throws if the
    ///  sender has insufficient PLAY. Throws if sender has not granted this
    ///  contract's address sufficient allowance.
    //-------------------------------------------------------------------------
    function mintBulk(uint _amount) external {
        play.lockFrom (msg.sender, 2, priceToMint * _amount);

        for (uint i = 0; i < _amount; ++i) {
            uint uid = uidBuffer + toyArray.length;
            uint index = toyArray.push(ToyToken(msg.sender, uid, 0, 0, ""));
            uidToToyIndex[uid] = index - 1;
            emit Transfer(0, msg.sender, uid);
        }
    }

    //-------------------------------------------------------------------------
    /// @notice Change TOY Token #`_toyId` to TOY Token #`_newUid`. Writes any
    ///  data passed through '_data' into the TOY Token's public data.
    /// @dev Throws if TOY Token #`_toyId` does not exist. Throws if sender is
    ///  not approved to operate for TOY Token. Throws if '_toyId' is smaller
    ///  than 8 bytes. Throws if '_newUid' is bigger than 7 bytes. Throws if 
    ///  '_newUid' is zero. Throws if '_newUid' is already taken.
    /// @param _newUid The UID of the RFID chip to link to the TOY Token
    /// @param _toyId The UID of the empty TOY Token to link
    /// @param _data A byte string of data to attach to the TOY Token
    //-------------------------------------------------------------------------
    function link(
        bytes7 _newUid, 
        uint _toyId, 
        bytes _data
    ) external canOperate(_toyId) {
        ToyToken storage toy = toyArray[uidToToyIndex[_toyId]];
        // _toyId must be an empty TOY Token
        require (_toyId > uidBuffer, "TOY Token already linked");
        // _newUid field cannot be empty or greater than 7 bytes
        require (_newUid > 0 && uint(_newUid) < UID_MAX, "Invalid new UID");
        // a TOY Token with the new UID must not currently exist
        require (
            uidToToyIndex[uint(_newUid)] == 0, 
            "TOY Token with 'newUID' already exists"
        );

        // set new UID's mapping to index to old UID's mapping
        uidToToyIndex[uint(_newUid)] = uidToToyIndex[_toyId];
        // reset old UID's mapping to index
        uidToToyIndex[_toyId] = 0;
        // set TOY Token's UID to new UID
        toy.uid = uint(_newUid);
        // set any data
        toy.toyData = _data;
        // reset the timestamp
        toy.timestamp = now;

        emit Link(_toyId, uint(_newUid));
    }

    //-------------------------------------------------------------------------
    /// @notice Change TOY Token UIDs to new UIDs for multiple TOY Tokens.
    ///  Writes any data passed through '_data' into all the TOY Tokens' data.
    /// @dev Throws if any TOY Token's UID does not exist. Throws if sender is
    ///  not approved to operate for any TOY Token. Throws if any '_toyId' is
    ///  smaller than 8 bytes. Throws if any '_newUid' is bigger than 7 bytes. 
    ///  Throws if any '_newUid' is zero. Throws if '_newUid' is already taken.
    ///  Throws if array parameters are not the same length.
    /// @param _newUid The UID of the RFID chip to link to the TOY Token
    /// @param _toyId The UID of the empty TOY Token to link
    /// @param _data A byte string of data to attach to the TOY Token
    //-------------------------------------------------------------------------
    function linkBulk(
        bytes7[] _newUid, 
        uint[] _toyId, 
        bytes _data
    ) external {
        require (_newUid.length == _toyId.length, "Array lengths not equal");
        for (uint i = 0; i < _newUid.length; ++i) {
            ToyToken storage toy = toyArray[uidToToyIndex[_toyId[i]]];
            // sender must be authorized operator
            require (
                msg.sender == toy.owner ||
                msg.sender == idToApprovedAddress[_toyId[i]] ||
                operatorApprovals[toy.owner][msg.sender],
                "Not authorized to operate for this TOY Token"
            );
            // _toyId must be an empty TOY Token
            require (_toyId[i] > uidBuffer, "TOY Token already linked");
            // _newUid field cannot be empty or greater than 7 bytes
            require (_newUid[i] > 0 && uint(_newUid[i]) < UID_MAX, "Invalid new UID");
            // a TOY Token with the new UID must not currently exist
            require (
                uidToToyIndex[uint(_newUid[i])] == 0, 
                "TOY Token with 'newUID' already exists"
            );

            // set new UID's mapping to index to old UID's mapping
            uidToToyIndex[uint(_newUid[i])] = uidToToyIndex[_toyId[i]];
            // reset old UID's mapping to index
            uidToToyIndex[_toyId[i]] = 0;
            // set TOY Token's UID to new UID
            toy.uid = uint(_newUid[i]);
            // set any data
            toy.toyData = _data;
            // reset the timestamp
            toy.timestamp = now;

            emit Link(_toyId[i], uint(_newUid[i]));
        }
    }

    //-------------------------------------------------------------------------
    /// @notice Set external NFT #`_externalId` as TOY Token #`_toyUid`'s
    ///  linked external NFT.
    /// @dev Throws if sender is not authorized to operate TOY Token #`_toyUid`
    ///  Throws if '_toyUid' is bigger than 7 bytes. Throws if external NFT is
    ///  already linked. Throws if sender is not authorized to operate external
    ///  NFT.
    /// @param _toyUid The UID of the TOY Token to link
    /// @param _externalAddress The contract address of the external NFT
    /// @param _externalId The UID of the external NFT to link
    //-------------------------------------------------------------------------
    function linkExternalNft(
        uint _toyUid, 
        address _externalAddress, 
        uint _externalId
    ) external canOperate(_toyUid) {
        require(_toyUid < UID_MAX, "TOY Token not linked to a physical toy");
        require(
            linkedExternalNfts[_externalAddress][_externalId] == false,
            "External NFT already linked"
        );
        require(
            msg.sender == ERC721(_externalAddress).ownerOf(_externalId),
            "Sender does not own external NFT"
        );
        uidToExternalNft[_toyUid] = ExternalNft(_externalAddress, _externalId);
        linkedExternalNfts[_externalAddress][_externalId] = true;
    }
}
//-----------------------------------------------------------------------------
function updatePlayTokenContract(address _newAddress) external onlyOwner {
    play = PlayInterface(_newAddress);
}

//-------------------------------------------------------------------------