/// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="db">The local database to replicate to/from</param>
        /// <param name="remote">The remote Uri to sync with</param>
        /// <param name="continuous">If set to <c>true</c> continuous.</param>
        /// <param name="clientFactory">The client factory for instantiating the HttpClient used to create web requests</param>
        /// <param name="workExecutor">The TaskFactory to execute work on</param>
        protected Replication(Database db, Uri remote, bool continuous, IHttpClientFactory clientFactory, TaskFactory workExecutor)
        {
            LocalDatabase = db;
            Continuous = continuous;
            // NOTE: Consider running a separate scheduler for all http requests.
            WorkExecutor = workExecutor;
            CancellationTokenSource = new CancellationTokenSource();
            RemoteUrl = remote;
            Options = new ReplicationOptionsDictionary();
            RequestHeaders = new Dictionary<String, Object>();
            _requests = new ConcurrentDictionary<HttpRequestMessage, Task>();

            // FIXME: Refactor to visitor pattern.
            if (RemoteUrl.GetQuery() != null && !StringEx.IsNullOrWhiteSpace(RemoteUrl.GetQuery()))
            {
                var uri = new Uri(remote.ToString());
                var personaAssertion = URIUtils.GetQueryParameter(uri, PersonaAuthorizer.QueryParameter);

                if (personaAssertion != null && !StringEx.IsNullOrWhiteSpace(personaAssertion))
                {
                    var email = PersonaAuthorizer.RegisterAssertion(personaAssertion);
                    var authorizer = new PersonaAuthorizer(email);
                    Authenticator = authorizer;
                }

                var facebookAccessToken = URIUtils.GetQueryParameter(uri, FacebookAuthorizer.QUERY_PARAMETER);

                if (facebookAccessToken != null && !StringEx.IsNullOrWhiteSpace(facebookAccessToken))
                {
                    var email = URIUtils.GetQueryParameter(uri, FacebookAuthorizer.QUERY_PARAMETER_EMAIL);
                    var authorizer = new FacebookAuthorizer(email);
                    Uri remoteWithQueryRemoved = null;

                    try
                    {
                        remoteWithQueryRemoved = new UriBuilder(remote.Scheme, remote.GetHost(), remote.Port, remote.AbsolutePath).Uri;
                    }
                    catch (UriFormatException e)
                    {
                        throw new ArgumentException("Invalid URI format.", "remote", e);
                    }

                    FacebookAuthorizer.RegisterAccessToken(facebookAccessToken, email, remoteWithQueryRemoved);

                    Authenticator = authorizer;
                }
                // we need to remove the query from the URL, since it will cause problems when
                // communicating with sync gw / couchdb
                try
                {
                    RemoteUrl = new UriBuilder(remote.Scheme, remote.GetHost(), remote.Port, remote.AbsolutePath).Uri;
                }
                catch (UriFormatException e)
                {
                    throw new ArgumentException("Invalid URI format.", "remote", e);
                }
            }

            Batcher = new Batcher<RevisionInternal>(workExecutor, INBOX_CAPACITY, PROCESSOR_DELAY, inbox =>
            {
                try {
                    Log.V(TAG, "*** BEGIN ProcessInbox ({0} sequences)", inbox.Count);
                    if(Continuous) {
                        FireTrigger(ReplicationTrigger.Resume);
                    }

                    ProcessInbox (new RevisionList(inbox));

                    Log.V(TAG, "*** END ProcessInbox (lastSequence={0})", LastSequence);
                } catch (Exception e) {
                    Log.E(TAG, "ProcessInbox failed: ", e);
                    throw new RuntimeException(e);
                }
            });

            ClientFactory = clientFactory;

            _stateMachine = new StateMachine<ReplicationState, ReplicationTrigger>(ReplicationState.Initial);
            InitializeStateMachine();
        }
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="db">The local database to replicate to/from</param>
        /// <param name="remote">The remote Uri to sync with</param>
        /// <param name="continuous">If set to <c>true</c> continuous.</param>
        /// <param name="clientFactory">The client factory for instantiating the HttpClient used to create web requests</param>
        /// <param name="workExecutor">The TaskFactory to execute work on</param>
        internal Replication(Database db, Uri remote, bool continuous, IHttpClientFactory clientFactory, TaskFactory workExecutor)
        {
            sessionID = $"repl{ Interlocked.Increment(ref _lastSessionID):000}";
            var opts = new RemoteSessionContructorOptions {
                BaseUrl = remote,
                WorkExecutor = workExecutor,
                Id = _replicatorID,
                CancellationTokenSource = CancellationTokenSource
            };
            _remoteSession = new RemoteSession(opts);
            Username = remote.UserInfo;

            LocalDatabase = db;
            _eventContext = LocalDatabase.Manager.CapturedContext;
            Continuous = continuous;
            // NOTE: Consider running a separate scheduler for all http requests.
            WorkExecutor = workExecutor;
            RemoteUrl = remote;
#pragma warning disable 618
            Options = new ReplicationOptionsDictionary();
#pragma warning restore 618
            ReplicationOptions = new ReplicationOptions();

            if (RemoteUrl.Query != null && !StringEx.IsNullOrWhiteSpace(RemoteUrl.Query)) {
                Authenticator = AuthenticatorFactory.CreateFromUri(remote);

                // we need to remove the query from the URL, since it will cause problems when
                // communicating with sync gw / couchdb
                try {
                    RemoteUrl = new UriBuilder(remote.Scheme, remote.Host, remote.Port, remote.AbsolutePath).Uri;
                } catch (UriFormatException e) {
                    throw Misc.CreateExceptionAndLog(Log.To.Sync, e, Tag,
                        "Invalid URI format for remote endpoint");
                }
            }

            Batcher = new Batcher<RevisionInternal>(workExecutor, INBOX_CAPACITY, ProcessorDelay, inbox =>
            {
                try {
                    Log.To.Sync.V(Tag, "*** {0} BEGIN ProcessInbox ({1} sequences)", this, inbox.Count);
                    if(Continuous) {
                        FireTrigger(ReplicationTrigger.Resume);
                    }

                    ProcessInbox (new RevisionList(inbox));

                    Log.To.Sync.V(Tag, "*** {0} END ProcessInbox (lastSequence={1})", this, LastSequence);
                } catch(Exception e) {
                    throw Misc.CreateExceptionAndLog(Log.To.Sync, e, Tag, 
                        "{0} ProcessInbox failed", this);
                }
            });

            ClientFactory = clientFactory;

            _stateMachine = new StateMachine<ReplicationState, ReplicationTrigger>(ReplicationState.Initial);
            InitializeStateMachine();
        }