contract Collection is CollectionI  {
    using SafeMath for uint256;

    // See ERC165.sol, ERC721.sol, ERC1155.sol, and CollectionNotificationI.sol
    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
    bytes4 private constant _INTERFACE_ID_ERC1155 = 0xd9b67a26;
    bytes4 private constant _INTERFACE_ID_ERC1155RECEIVER = 0x4e2312e0;
    bytes4 private constant _INTERFACE_ID_COLLECTION_NOTIFICATION = 0x49ae07c9;

    struct AssetInfo {
        address tokenAddress;
        uint256 tokenId;
        CollectionNotificationI.TokenType tokenType;
    }

    AssetInfo[] public ownedAssets;
    mapping(bytes32 => uint256) public ownedAssetIndex; // for looking up an asset

    event NotificationContractTransferred(address indexed previousNotificationContract, address indexed newNotificationContract);
    event AssetAdded(address tokenAddress, uint256 tokenId);
    event AssetRemoved(address tokenAddress, uint256 tokenId);
    event CollectionDestroyed(address operator);
    // TestTracker events - never emitted in this contract but helpful for running our tests.
    event SeenContractAdded(bool initial);
    event SeenContractRemoved();
    event SeenAssetAdded(address tokenAddress, uint256 tokenId, CollectionNotificationI.TokenType tokenType);
    event SeenAssetRemoved(address tokenAddress, uint256 tokenId, CollectionNotificationI.TokenType tokenType);

    modifier onlyOwner {
        require(!isPrototype && msg.sender == ownerAddress(), "Only Collection owner or zero allowed.");
        _;
    }

    modifier requireActive {
        require(!isPrototype, "Needs an active contract, not the prototype.");
        _;
    }

    constructor()
    public {
       // The initially deployed contract is just a prototype and code holder.
        // Clones will proxy their commends to this one and actually work.
        isPrototype = true;
    }

    function initialRegister(address _notificationContract,
                             string calldata _ensName,
                             string calldata _ensSubdomainName,
                             address _ensSubdomainRegistrarAddress,
                             address _ensReverseRegistrarAddress)
    external override
    requireActive
    {
        // Make sure that this function has not been called on this contract yet.
        require(address(collections) == address(0), "Cannot be initialized twice.");
        // Make sure that caller is an ERC721 contract itself.
        collections = CollectionsI(msg.sender);
        require(collections.supportsInterface(_INTERFACE_ID_ERC721), "Creator needs to be ERC721!");
        if (_notificationContract != address(0)) {
            _transferNotificationContract(_notificationContract, true);
        }
        // Register ENS name if we did get a registrar.
        if (_ensSubdomainRegistrarAddress != address(0)) {
            _registerENS(_ensName, _ensSubdomainRegistrarAddress);
        }
        // We also set a reverse record via https://docs.ens.domains/contract-api-reference/reverseregistrar#set-name which needs a full name.
        if (_ensReverseRegistrarAddress != address(0)) {
            _registerReverseENS(_ensReverseRegistrarAddress, string(abi.encodePacked(_ensName, ".", _ensSubdomainName)));
        }
    }

    /*** ERC165 ***/

    function supportsInterface(bytes4 interfaceId) external view override returns (bool) {
        // We know what interfaces the collection supports, so hardcode the handling.
        if (interfaceId == _INTERFACE_ID_ERC165) { return true; }
        else if (interfaceId == _ERC721_RECEIVED) { return true; }
        else if (interfaceId == _INTERFACE_ID_ERC1155RECEIVER) { return true; }
        return false;
    }

    /*** Enable adjusting variables after deployment ***/

    function transferNotificationContract(address _newNotificationContract)
    public
    onlyOwner
    {
        _transferNotificationContract(_newNotificationContract, false);
    }

    function _transferNotificationContract(address _newNotificationContract, bool _initial)
    private
    {
        if (notificationContract != _newNotificationContract) {
            emit NotificationContractTransferred(notificationContract, _newNotificationContract);
            if (notificationContract != address(0)) {
                require(
                    CollectionNotificationI(notificationContract).onContractRemoved() ==
                        CollectionNotificationI(notificationContract).onContractRemoved.selector,
                    "onContractRemoved failure"
                );
            }
            notificationContract = _newNotificationContract;
            if (notificationContract != address(0)) {
                require(IERC165(notificationContract).supportsInterface(_INTERFACE_ID_COLLECTION_NOTIFICATION),
                        "Need to implement the actual collection notification interface!");
                require(
                    CollectionNotificationI(notificationContract).onContractAdded(_initial) ==
                        CollectionNotificationI(notificationContract).onContractAdded.selector,
                    "onContractAdded failure"
                );
            }
        }
    }

    /*** Deal with ERC721 and ERC1155 tokens we receive ***/

    // Override ERC721Receiver to record receiving of ERC721 tokens.
    // Also, comment out all params that are in the interface but not actually used, to quiet compiler warnings.
    function onERC721Received(address /*_operator*/, address /*_from*/, uint256 _tokenId, bytes memory /*_data*/)
    public override
    requireActive
    returns (bytes4)
    {
        address _tokenAddress = msg.sender;
        // Make sure whoever called this plays nice, check for token being an ERC721 contract.
        require(IERC165(_tokenAddress).supportsInterface(_INTERFACE_ID_ERC721), "onERC721Received caller needs to implement ERC721!");
        // If we think we own this asset already, we don't need to add it, but this is still weird and should not happen.
        if (!ownsAsset(_tokenAddress, _tokenId)) {
            _addtoAssets(_tokenAddress, _tokenId, CollectionNotificationI.TokenType.ERC721);
        }
        return this.onERC721Received.selector;
    }

    function onERC1155Received(address /*_operator*/, address /*_from*/, uint256 _id, uint256 /*_value*/, bytes calldata /*_data*/)
    external override
    requireActive
    returns(bytes4)
    {
        address _tokenAddress = msg.sender;
        // Make sure whoever called this plays nice, check for token being an ERC1155 contract.
        require(IERC165(_tokenAddress).supportsInterface(_INTERFACE_ID_ERC1155), "onERC1155Received caller needs to implement ERC1155!");
        // If we think we own this asset already, we don't need to add it. On ERC115 this can happen easily.
        if (!ownsAsset(_tokenAddress, _id)) {
            _addtoAssets(_tokenAddress, _id, CollectionNotificationI.TokenType.ERC1155);
        }
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(address /*_operator*/, address /*_from*/, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata /*_data*/)
    external override
    requireActive
    returns(bytes4)
    {
        address _tokenAddress = msg.sender;
        // Make sure whoever called this plays nice, check for token being an ERC1155 contract.
        require(IERC165(_tokenAddress).supportsInterface(_INTERFACE_ID_ERC1155), "onERC1155BatchReceived caller needs to implement ERC1155!");
        uint256 batchcount = _ids.length;
        require(batchcount == _values.length, "Both ids and values need to be the same length.");
        for (uint256 i = 0; i < batchcount; i++) {
          if (!ownsAsset(_tokenAddress, _ids[i])) {
              _addtoAssets(_tokenAddress, _ids[i], CollectionNotificationI.TokenType.ERC1155);
          }
        }
        return this.onERC1155BatchReceived.selector;
    }

    /*** Special Collection functionality ***/

    // Get collection owner from ERC 721 parent (Collections factory)
    function ownerAddress()
    public view override
    requireActive
    returns (address)
    {
        return collections.ownerOf(uint256(address(this)));
    }

    function ownsAsset(address _tokenAddress, uint256 _tokenId)
    public view override
    requireActive
    returns(bool)
    {
        // Check if we do have this in our owned asset data.
        uint256 probableIndex = lookupIndex(_tokenAddress, _tokenId);
        if (probableIndex >= ownedAssets.length ||
            ownedAssets[probableIndex].tokenAddress != _tokenAddress ||
            ownedAssets[probableIndex].tokenId != _tokenId) {
            return false;
        }
        return true;
    }

    function ownedAssetsCount()
    public view override
    requireActive
    returns (uint256) {
        return ownedAssets.length;
    }

    function syncAssetOwnership(address _tokenAddress, uint256 _tokenId)
    public override
    requireActive
    {
        // Check if we do have this in our owned asset data.
        CollectionNotificationI.TokenType tokenType;
        bool hasOwnership;
        if (IERC165(_tokenAddress).supportsInterface(_INTERFACE_ID_ERC1155)) {
            hasOwnership = (IERC1155(_tokenAddress).balanceOf(address(this), _tokenId) > 0);
            tokenType = CollectionNotificationI.TokenType.ERC1155;
        }
        else if (IERC165(_tokenAddress).supportsInterface(_INTERFACE_ID_ERC721)) {
            hasOwnership = (IERC721(_tokenAddress).ownerOf(_tokenId) == address(this));
            tokenType = CollectionNotificationI.TokenType.ERC721;
        }
        else {
            revert("Token address has to be either ERC721 or ERC1155!");
        }
        bool isOwned = ownsAsset(_tokenAddress, _tokenId);
        if (isOwned && !hasOwnership) {
            // We think we own the asset but it moved on the contract, remove it.
            _removeFromAssets(_tokenAddress, _tokenId, tokenType);
        }
        else if (!isOwned && hasOwnership) {
            // The contract says we own it but we think we don't, add it.
            _addtoAssets(_tokenAddress, _tokenId, tokenType);
        }
    }

    // Internal helper to add item to assets - make sure we have tested for !ownsAsset before.
    function _addtoAssets(address _tokenAddress, uint256 _tokenId, CollectionNotificationI.TokenType _tokenType)
    internal
    {
        ownedAssets.push(AssetInfo(_tokenAddress, _tokenId, _tokenType));
        uint256 newIndex = ownedAssets.length.sub(1);
        ownedAssetIndex[getLookupHash(ownedAssets[newIndex])] = newIndex;
        emit AssetAdded(_tokenAddress, _tokenId);

        if (notificationContract != address(0)) {
            require(
                CollectionNotificationI(notificationContract).onAssetAdded(_tokenAddress, _tokenId, _tokenType) ==
                    CollectionNotificationI(notificationContract).onAssetAdded.selector,
                "onAssetAdded failure"
            );
        }
    }

    // Internal helper to remove item from assets - make sure we have tested for ownsAsset before.
    function _removeFromAssets(address _tokenAddress, uint256 _tokenId, CollectionNotificationI.TokenType _tokenType)
    internal
    {
        bytes32 lookupHash = getLookupHash(_tokenAddress, _tokenId);
        uint256 currentIndex = ownedAssetIndex[lookupHash];
        require(ownedAssets[currentIndex].tokenType == _tokenType, "Mismatching token types!");
        uint256 lastIndex = ownedAssets.length.sub(1);
        // When the asset to delete is the last one, the swap operation is unnecessary
        if (currentIndex != lastIndex) {
            AssetInfo storage lastAsset = ownedAssets[lastIndex];
            ownedAssets[currentIndex] = lastAsset; // Move the last asset to the slot of the to-delete asset
            ownedAssetIndex[getLookupHash(lastAsset)] = currentIndex; // Update the moved asset's index
        }
        // Deletes the contents at the last position of the array and re-sets the index.
        ownedAssets.pop();
        ownedAssetIndex[lookupHash] = 0;
        emit AssetRemoved(_tokenAddress, _tokenId);

        if (notificationContract != address(0)) {
            require(
                CollectionNotificationI(notificationContract).onAssetRemoved(_tokenAddress, _tokenId, _tokenType) ==
                    CollectionNotificationI(notificationContract).onAssetRemoved.selector,
                "onAssetRemoved failure"
            );
        }
    }

    /*** Provide functions to transfer owned assets away ***/

    function safeTransferTo(address _tokenAddress, uint256 _tokenId, address _to)
    external override
    {
        // Called function checks if it's the owner or an allowed account calling this.
        safeTransferTo(_tokenAddress, _tokenId, _to, 1);
    }

    function safeTransferTo(address _tokenAddress, uint256 _tokenId, address _to, uint256 _value)
    public override
    {
        require(collections.isApprovedOrOwner(msg.sender, uint256(address(this))), "Only an approved address or Collection owner allowed.");
        // In theory, we could enforce a syncAssetOwnership() here but we'd still need the require.
        require(ownsAsset(_tokenAddress, _tokenId), "We do not own this asset.");
        uint256 assetIndex = lookupIndex(_tokenAddress, _tokenId);
        if (ownedAssets[assetIndex].tokenType == CollectionNotificationI.TokenType.ERC721) {
            IERC721(_tokenAddress).safeTransferFrom(address(this), _to, _tokenId);
            // _removeFromAssets calls onAssetRemoved() which will check we already do not own it any more.
            _removeFromAssets(_tokenAddress, _tokenId, CollectionNotificationI.TokenType.ERC721);
        }
        else { // ERC1155
            IERC1155 tokenContract = IERC1155(_tokenAddress);
            tokenContract.safeTransferFrom(address(this), _to, _tokenId, _value, "");
            // Only remove from assets if we now do not own any of this token after the transaction.
            if (tokenContract.balanceOf(address(this), _tokenId) == 0) {
                // _removeFromAssets calls onAssetRemoved() which will check we already do not own it any more.
                _removeFromAssets(_tokenAddress, _tokenId, CollectionNotificationI.TokenType.ERC1155);
            }
        }
    }

    /*** Internal helpers to calculate the hash to use for the lookup mapping. ***/

    function getLookupHash(address _tokenAddress, uint256 _tokenId)
    private pure
    returns(bytes32)
    {
        return keccak256(abi.encodePacked(_tokenAddress, _tokenId));
    }

    function getLookupHash(AssetInfo memory _assetInfo)
    private pure
    returns(bytes32)
    {
        return getLookupHash(_assetInfo.tokenAddress, _assetInfo.tokenId);
    }

    function lookupIndex(address _tokenAddress, uint256 _tokenId)
    public view
    returns(uint256)
    {
        return ownedAssetIndex[getLookupHash(_tokenAddress, _tokenId)];
    }

    /*** Destroy Collection ***/

    // Destroys and burns an empty Collection.
    function destroy()
    external override
    onlyOwner
    {
        require(ownedAssets.length == 0, "Only empty collections can be destroyed.");
        address payable collectionOwner = payable(ownerAddress());
        collections.burn(uint256(address(this)));
        emit CollectionDestroyed(msg.sender);
        selfdestruct(collectionOwner);
    }

    /*** Forward calls to external contracts ***/

    // Given a contract address and an already-encoded payload (with a function call etc.),
    // we call that contract with this payload, e.g. to trigger actions in the name of the collection.
    function externalCall(address payable _remoteAddress, bytes calldata _callPayload)
    external override payable
    onlyOwner
    {
        // .call() forwards all available gas, we forward the sent ether explicitly.
        (bool success, /*bytes memory data*/) = _remoteAddress.call{value: msg.value}(_callPayload);
        if (!success) { revert("Error in remote call!"); }
    }

    /*** ENS registration access ***/

    // There is no standard for how to register a name with an ENS registrar.
    // Examples are:
    // .eth permanent registrar controller: https://docs.ens.domains/contract-api-reference/.eth-permanent-registrar/controller#register-name
    // .test registrar: https://docs.ens.domains/contract-api-reference/testregistrar#register-a-domain
    // Sample custom registrar: https://docs.ens.domains/contract-developer-guide/writing-a-registrar
    // Either the plain name or the label has can be required to call the function for registration, see
    // https://docs.ens.domains/contract-api-reference/name-processing for the description on name processing.
    // The registrar usually ends up calling setSubnodeOwner(bytes32 node, bytes32 label, address owner),
    // see https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol for the ENS interface.
    // Because of all this, the function *only* works with a registrar with the same register() signature as
    // the sample custom FIFS registrar. Any more complicated registrations need to be done via externalCall().
    function registerENS(string memory _name, address _registrarAddress)
    public override
    onlyOwner
    {
        _registerENS(_name, _registrarAddress);
    }

    function _registerENS(string memory _name, address _registrarAddress)
    private
    {
        require(_registrarAddress != address(0), "Need valid registrar.");
        bytes32 label = keccak256(bytes(_name));
        ENSSimpleRegistrarI(_registrarAddress).registerAddr(label, address(this));
    }

    /*** Enable reverse ENS registration ***/

    // Call this with the address of the reverse registrar for the respecitve network and the ENS name to register.
    // The reverse registrar can be found as the owner of 'addr.reverse' in the ENS system.
    // See https://docs.ens.domains/ens-deployments for address of ENS deployments, e.g. Etherscan can be used to look up that owner on those.
    // namehash.hash("addr.reverse") == "0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2"
    // Ropsten: ens.owner(namehash.hash("addr.reverse")) == "0x6F628b68b30Dc3c17f345c9dbBb1E483c2b7aE5c"
    // Mainnet: ens.owner(namehash.hash("addr.reverse")) == "0x084b1c3C81545d370f3634392De611CaaBFf8148"
    function registerReverseENS(address _reverseRegistrarAddress, string memory _name)
    public override
    onlyOwner
    {
        _registerReverseENS(_reverseRegistrarAddress, _name);
    }

    function _registerReverseENS(address _reverseRegistrarAddress, string memory _name)
    private
    {
        require(_reverseRegistrarAddress != address(0), "Need valid reverse registrar.");
        ENSReverseRegistrarI(_reverseRegistrarAddress).setName(_name);
    }

    /*** Make sure currency or NFT doesn't get stranded in this contract ***/

    // If this contract gets a balance in some ERC20 contract after it's finished, then we can rescue it.
    function rescueToken(address _foreignToken, address _to)
    external
    onlyOwner
    {
        IERC20 erc20Token = IERC20(_foreignToken);
        erc20Token.transfer(_to, erc20Token.balanceOf(address(this)));
    }

}
function registerENS(string memory _name, address _registrarAddress)
public override
onlyOwner
{
    _registerENS(_name, _registrarAddress);
}

// There is no standard for how to register a name with an ENS registrar.
// Examples are:
// .eth permanent registrar controller: https://docs.ens.domains/contract-api-reference/.eth-permanent-registrar/controller#register-name
// .test registrar: https://docs.ens.domains/contract-api-reference/testregistrar#register-a-domain
// Sample custom registrar: https://docs.ens.domains/contract-developer-guide/writing-a-registrar
// Either the plain name or the label has can be required to call the function for registration, see
// https://docs.ens.domains/contract-api-reference/name-processing for the description on name processing.
// The registrar usually ends up calling setSubnodeOwner(bytes32 node, bytes32 label, address owner),
// see https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol for the ENS interface.
// Because of all this, the function *only* works with a registrar with the same register() signature as
// the sample custom FIFS registrar. Any more complicated registrations need to be done via externalCall().