contract CryptostampGoldenDogeL1 is ERC721NME_EnumerableSimple, ERC721ExistsI, ERC721SignedTransferI {

    BridgeDataI public bridgeData;

    uint256 immutable finalSupply;
    uint256 public nextToVirtuallyMint;

    mapping(address => uint256) public override signedTransferNonce;

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

    constructor(address _bridgeDataAddress, uint256 _finalSupply)
    ERC721NME("Crypto stamp Golden Doge", "CSGD")
    {
        bridgeData = BridgeDataI(_bridgeDataAddress);
        require(address(bridgeData) != address(0x0), "You need to provide an actual bridge data contract.");
        finalSupply = _finalSupply;
    }

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

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

    modifier onlyBridgeControl() {
        require(msg.sender == bridgeData.getAddress("bridgeControl"), "bridgeControl key 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.");
        _;
    }

    /*** 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
    returns (bool)
    {
        uint256 tokenSunsetTimestamp = bridgeData.tokenSunsetTimestamp();
        // solhint-disable-next-line not-rely-on-time
        return (tokenSunsetTimestamp == 0 || tokenSunsetTimestamp > block.timestamp);
    }

    /*** Base functionality: minting, URI, property getters, etc. ***/

    // This just emits the events for minting a series of NFTs.
    function virtualMintSeries(uint256 _startId, uint256 _count, bool checkForExisting)
    public
    onlyCreateControl
    {
        require(_startId >= nextToVirtuallyMint, "Starting ID is already minted.");
        address tokenHolder = bridgeData.getAddress("tokenHolder");
        uint256 endId = _startId + _count;
        require(endId <= finalSupply, "Cannot send mint events for non-existing token IDs.");
        for (uint256 i = _startId; i < endId; i++) {
            // If this has not been actually transferred yet, we emit the mint event.
            if (!checkForExisting || !ERC721NME._exists(i)) {
                emit Transfer(address(0), tokenHolder, i);
            }
        }
        nextToVirtuallyMint = endId;
    }

    function baseURI() public view returns (string memory) {
        return _baseURI();
    }
    function _baseURI() internal view override returns (string memory) {
        return string(abi.encodePacked(bridgeData.tokenURIBase(), symbol(), "/meta/"));
    }

    // Returns whether the specified token exists
    function exists(uint256 _tokenId)
    public view override
    returns (bool)
    {
        return _exists(_tokenId);
    }
    function _exists(uint256 _tokenId)
    internal view override
    returns (bool)
    {
        return _tokenId < finalSupply;
    }

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

    // When the bridge is sunset, all token actions will be blocked.
    function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId)
    internal override
    requireTransferEnabled
    {
        super._beforeTokenTransfer(_from, _to, _tokenId);
    }

    // If the token was only virtually minted, we need to "magically" mint instead of transfer.
    function _transfer(address _from, address _to, uint256 _tokenId)
    internal override
    {
        if (!ERC721NME._exists(_tokenId) && _from == bridgeData.getAddress("tokenHolder") && _isApprovedOrOwner(msg.sender, _tokenId)) {
            // Clear approvals from the previous owner
            _approve(address(0), _tokenId);
            // The _mint() function does NOT emit an event in the "MNE" implementation!
            // We then emit the event for an actual transfer, the mint event
            // either has been sent via virtualMintSeries() beforehand, or
            // otherwise, we manually emit it here.
            _mint(_to, _tokenId);
            if (_tokenId >= nextToVirtuallyMint) {
                emit Transfer(address(0), _from, _tokenId);
            }
            emit Transfer(_from, _to, _tokenId);
        }
        else {
            super._transfer(_from, _to, _tokenId);
        }
    }

    function balanceOf(address _owner)
    public view override(ERC721NME, IERC721)
    returns (uint256)
    {
        if (_owner == bridgeData.getAddress("tokenHolder")) {
            // All "virtually minted" tokens plus all actually owned tokens
            return finalSupply - super.totalSupply() + super.balanceOf(_owner);
        }
        return super.balanceOf(_owner);
    }

    function ownerOf(uint256 _tokenId)
    public view override(ERC721NME, IERC721)
    returns (address)
    {
        if (_exists(_tokenId) && !ERC721NME._exists(_tokenId)) {
            // valid token ID but not actually minted
            return bridgeData.getAddress("tokenHolder");
        }
        return super.ownerOf(_tokenId);
    }

    // Override totalSupply() so it reports the target final supply.
    /**
     * @dev Gets the total amount of tokens stored by the contract.
     * @return uint256 representing the total amount of tokens
     */
    function totalSupply()
    public view override
    returns (uint256) {
        return finalSupply;
    }

    function tokenOfOwnerByIndex(address _owner, uint256 _index)
    public view virtual override
    returns (uint256)
    {
        if (_index >= ERC721NME.balanceOf(_owner) && _owner == bridgeData.getAddress("tokenHolder")) {
            // We only get here if the requested token is not minted in storage.
            // As we have no records of such tokens, we need to loop through
            // all tokens from zero, until we have found enough tokens not
            // minted in storage to match the index, and take the non-minted
            // one at that position.
            require(_index < balanceOf(_owner), "ERC721Enumerable: owner index out of bounds");
            uint256 testIndex = ERC721NME.balanceOf(_owner);
            uint256 tokenId = 0;
            do {
                if (!ERC721NME._exists(tokenId)) {
                    if (testIndex >= _index) {
                        // We have found a non-minted token at the right index!
                        break;
                    }
                    testIndex++;
                }
                tokenId++;
            } while (tokenId < finalSupply); // Condition is never actually met.
            return tokenId;
        }
        return super.tokenOfOwnerByIndex(_owner, _index);
    }

    /*** 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 signedTransfer(uint256 _tokenId, address _to, bytes memory _signature)
    public override
    requireTransferEnabled
    {
        address currentOwner = ownerOf(_tokenId);
        // The signed data contains the token ID, the transfer target and a nonce.
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedTransfer.selector, currentOwner, _to, _tokenId, signedTransferNonce[currentOwner]));
        _signedTransferInternal(currentOwner, data, _tokenId, _to, _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 signedTransferWithOperator(uint256 _tokenId, address _to, bytes memory _signature)
    public override
    requireTransferEnabled
    {
        address currentOwner = ownerOf(_tokenId);
        // The signed data contains the operator, the token ID, and a nonce. Note that we use the selector of the external function here!
        bytes32 data = keccak256(abi.encodePacked(address(this), this.signedTransferWithOperator.selector, msg.sender, currentOwner, _tokenId, signedTransferNonce[currentOwner]));
        _signedTransferInternal(currentOwner, data, _tokenId, _to, _signature);
    }

    // Actually check the signature and perform a signed transfer.
    function _signedTransferInternal(address _currentOwner, bytes32 _data, uint256 _tokenId, address _to, bytes memory _signature)
    internal
    {
        bytes32 hash = ECDSA.toEthSignedMessageHash(_data);
        address signer = ECDSA.recover(hash, _signature);
        require(signer == _currentOwner, "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 SignedTransfer(msg.sender, _currentOwner, _to, _tokenId, signedTransferNonce[_currentOwner]);
        signedTransferNonce[_currentOwner]++;
        _safeTransfer(_currentOwner, _to, _tokenId, "");
    }

    /*** 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(IERC20 _foreignToken, address _to)
    external
    onlyTokenAssignmentControl
    {
        _foreignToken.transfer(_to, _foreignToken.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);
    }
}
/*
 * Implements ERC 721 NFT standard: https://github.com/ethereum/EIPs/issues/721.
 */
function totalSupply()
public view override
returns (uint256) {
    return finalSupply;
}

/**
 * @dev Gets the total amount of tokens stored by the contract.
 * @return uint256 representing the total amount of tokens
 */