contract BridgedERC1155 is ERC1155, BridgedERC1155I {
    using Strings for uint256;

    bool public override isPrototype;
    BridgeDataI public override bridgeData;
    string public override originalChainName;
    address public override originalChainAddress;

    mapping(address => uint256) public override signedTransferNonce;

    event BridgeDataChanged(address indexed previousBridgeData, address indexed newBridgeData);

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

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

    modifier onlyBridgeControl()
    {
        require(msg.sender == bridgeData.getAddress("bridgeControl"), "bridgeControl key required for this function.");
        _;
    }

    modifier onlyBridge()
    {
        require(msg.sender == bridgeData.getAddress("bridgeControl") || msg.sender == bridgeData.getAddress("bridgeHead"), "bridgeControl key or bridge head required for this function.");
        _;
    }

    modifier onlyTokenAssignmentControl() {
        require(msg.sender == bridgeData.getAddress("tokenAssignmentControl"), "tokenAssignmentControl key required for this function.");
        _;
    }

    modifier requireTransferEnabled() {
        require(transferEnabled() == true, "This call only works when transfers are enabled.");
        _;
    }

    /*** Initialize the basic variables ***/

    function initialRegister(address _bridgeDataAddress, string memory _originalChainName, address _originalChainAddress)
    external override
    requireActive
    {
        // Make sure that this function has not been called on this contract yet.
        require(address(bridgeData) == address(0), "Cannot be initialized twice.");
        bridgeData = BridgeDataI(_bridgeDataAddress);
        originalChainName = _originalChainName;
        originalChainAddress = _originalChainAddress;
    }

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

    function setBridgeData(BridgeDataI _newBridgeData)
    external
    onlyBridgeControl
    {
        require(address(_newBridgeData) != address(0x0), "You need to provide an actual bridge data contract.");
        emit BridgeDataChanged(address(bridgeData), address(_newBridgeData));
        bridgeData = _newBridgeData;
    }

    // Return true if transfers are possible.
    // This can have additional conditions to just the sunset variable.
    function transferEnabled()
    public view override
    requireActive
    returns (bool)
    {
        uint256 tokenSunsetTimestamp = bridgeData.tokenSunsetTimestamp();
        // solhint-disable-next-line not-rely-on-time
        return (tokenSunsetTimestamp == 0 || tokenSunsetTimestamp > block.timestamp);
    }

    function uri(uint256 _tokenId) public view override(ERC1155, IERC1155MetadataURI) returns (string memory) {
        return string(abi.encodePacked(bridgeData.tokenURIBase(), uint256(uint160(address(this))).toHexString(20), "/meta/", _tokenId.toString()));
    }

    /*** Implement mintable and exists interfaces ***/

    // Mint a token. Calls onERC1155Received() on the `_to` address if it's a contract.
    function mint(address _account, uint256 _id, uint256 _amount)
    public override
    requireTransferEnabled
    onlyBridge
    {
        _mint(_account, _id, _amount, "");
    }

    // Mint a batch of tokens. Calls onERC1155Received() on the `_to` address if it's a contract.
    function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _amounts)
    public override
    requireTransferEnabled
    onlyBridge
    {
        _mintBatch(_to, _ids, _amounts, "");
    }

    // Burn a batch of NFTs.
    function burnBatch(address _account, uint256[] memory _ids, uint256[] memory _amounts)
    public
    requireTransferEnabled
    onlyBridge
    {
        _burnBatch(_account, _ids, _amounts);
    }

    /*** Override internal functionality for special rules on approvals and transfers ***/

    // When the bridge is sunset, all token actions will be blocked.
    function _beforeTokenTransfer(address _operator, address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    internal override
    requireTransferEnabled
    {
        super._beforeTokenTransfer(_operator, _from, _to, _ids, _amounts, _data);
    }

    /*** Allows any user to initiate a transfer with the signature of the current stamp owner  ***/

    // Outward-facing function for signed transfer: assembles the expected data and then calls the internal function to do the rest.
    // Can called by anyone knowing about the right signature, but can only transfer to the given specific target.
    function signedBatchTransfer(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _signature)
    public override
    requireTransferEnabled
    {
        // The signed data contains the token IDs, amounts, the transfer source and target as well as a nonce.
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedBatchTransfer.selector, _from, _to, _ids, _amounts, signedTransferNonce[_from]));
        _signedBatchTransferInternal(_from, _to, _ids, _amounts, data, _signature);
    }

    // Outward-facing function for operator-driven signed transfer: assembles the expected data and then calls the internal function to do the rest.
    // Can transfer to any target, but only be called by the trusted operator contained in the signature.
    function signedBatchTransferWithOperator(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _signature)
    public override
    requireTransferEnabled
    {
        // The signed data contains the operator, the token IDs, amounts, and a nonce. Note that we use the selector of the external function here!
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedBatchTransferWithOperator.selector, msg.sender, _from, _ids, _amounts, signedTransferNonce[_from]));
        _signedBatchTransferInternal(_from, _to, _ids, _amounts, data, _signature);
    }

    // Actually check the signature and perform a signed transfer.
    function _signedBatchTransferInternal(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes32 _data, bytes memory _signature)
    internal
    {
        bytes32 hash = ECDSA.toEthSignedMessageHash(_data);
        address signer = ECDSA.recover(hash, _signature);
        require(signer == _from, "Signature needs to match parameters, nonce, and current owner.");
        // Now that we checked that the signature is correct, do the actual transfer and increase the nonce.
        emit SignedBatchTransfer(msg.sender, _from, _to, _ids, _amounts, signedTransferNonce[_from]);
        signedTransferNonce[_from]++;
        _safeBatchTransferFrom(_from, _to, _ids, _amounts, "");
    }

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

    // Call this with the address of the reverse registrar for the respective network and the ENS name to register.
    // The reverse registrar can be found as the owner of 'addr.reverse' in the ENS system.
    // For Mainnet, the address needed is 0x9062c0a6dbd6108336bcbe4593a3d1ce05512069
    function registerReverseENS(address _reverseRegistrarAddress, string calldata _name)
    external
    onlyTokenAssignmentControl
    {
        require(_reverseRegistrarAddress != address(0), "need a 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
    onlyTokenAssignmentControl
    {
        IERC20 erc20Token = IERC20(_foreignToken);
        erc20Token.transfer(_to, erc20Token.balanceOf(address(this)));
    }

    // If this contract gets a balance in some ERC721 contract after it's finished, then we can rescue it.
    function approveNFTrescue(IERC721 _foreignNFT, address _to)
    external
    onlyTokenAssignmentControl
    {
        _foreignNFT.setApprovalForAll(_to, true);
    }

}
/*
 * A generic bridged semi-fungible token, to be deployed by the bridge when a
 * token from the other side is exiting on this side of the bridge.
 * Implements ERC 1155 token standard: https://eips.ethereum.org/EIPS/eip-1155
 */
function transferEnabled()
public view override
requireActive
returns (bool)
{
    uint256 tokenSunsetTimestamp = bridgeData.tokenSunsetTimestamp();
    // solhint-disable-next-line not-rely-on-time
    return (tokenSunsetTimestamp == 0 || tokenSunsetTimestamp > block.timestamp);
}

// Return true if transfers are possible.
// This can have additional conditions to just the sunset variable.