GetEstimatedChangesCountAsync(ClientScopeInfo clientScopeInfo, SyncParameters parameters = default, DbConnection connection = default, DbTransaction transaction = default, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null)
        {
            var context = new SyncContext(Guid.NewGuid(), clientScopeInfo.Name);

            if (parameters != null)
            {
                context.Parameters = parameters;
            }

            SyncSet         schema;
            ServerScopeInfo serverScopeInfo;

            // Need the server scope
            (context, serverScopeInfo) = await this.InternalGetServerScopeInfoAsync(context, clientScopeInfo.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

            schema = serverScopeInfo.Schema;
            schema.EnsureSchema();

            clientScopeInfo.Schema  = schema;
            clientScopeInfo.Setup   = serverScopeInfo.Setup;
            clientScopeInfo.Version = serverScopeInfo.Version;


            // generate a message to send
            var changesToSend = new HttpMessageSendChangesRequest(context, clientScopeInfo)
            {
                Changes     = null,
                IsLastBatch = true,
                BatchIndex  = 0,
                BatchCount  = 0
            };

            var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>();
            var binaryData = await serializer.SerializeAsync(changesToSend);

            // Raise progress for sending request and waiting server response
            await this.InterceptAsync(new HttpGettingServerChangesRequestArgs(0, 0, context, this.GetServiceHost()), progress, cancellationToken).ConfigureAwait(false);

            // response
            var response = await this.httpRequestHandler.ProcessRequestAsync
                               (this.HttpClient, context, this.ServiceUri, binaryData, HttpStep.GetEstimatedChangesCount,
                               this.SerializerFactory, this.Converter, this.Options.BatchSize, this.SyncPolicy, cancellationToken, progress).ConfigureAwait(false);

            HttpMessageSendChangesResponse summaryResponseContent = null;

            using (var streamResponse = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
            {
                var responseSerializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesResponse>();

                if (streamResponse.CanRead)
                {
                    summaryResponseContent = await responseSerializer.DeserializeAsync(streamResponse);
                }
            }

            if (summaryResponseContent == null)
            {
                throw new Exception("Summary can't be null");
            }

            // generate the new scope item
            this.CompleteTime = DateTime.UtcNow;

            return(new (summaryResponseContent.RemoteClientTimestamp, null, summaryResponseContent.ServerChangesSelected));
        }
        ApplyThenGetChangesAsync(ScopeInfo scope, BatchInfo clientBatchInfo, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null)
        {
            SyncSet schema;
            // Get context or create a new one
            var ctx = this.GetContext();

            if (!this.StartTime.HasValue)
            {
                this.StartTime = DateTime.UtcNow;
            }

            //// create the in memory changes set
            //var changesSet = new SyncSet();

            // is it something that could happens ?
            if (scope.Schema == null)
            {
                // Make a remote call to get Schema from remote provider
                var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false);

                schema = serverScopeInfo.Schema;
            }
            else
            {
                schema = scope.Schema;
            }

            schema.EnsureSchema();

            // if we don't have any BatchPartsInfo, just generate a new one to get, at least, something to send to the server
            // and get a response with new data from server
            if (clientBatchInfo == null)
            {
                clientBatchInfo = new BatchInfo(true, schema);
            }

            // Get sanitized schema, without readonly columns
            var sanitizedSchema = clientBatchInfo.SanitizedSchema;

            // --------------------------------------------------------------
            // STEP 1 : Send everything to the server side
            // --------------------------------------------------------------

            // response
            HttpMessageSendChangesResponse httpMessageContent = null;


            // If not in memory and BatchPartsInfo.Count == 0, nothing to send.
            // But we need to send something, so generate a little batch part
            if (clientBatchInfo.InMemory || (!clientBatchInfo.InMemory && clientBatchInfo.BatchPartsInfo.Count == 0))
            {
                var changesToSend = new HttpMessageSendChangesRequest(ctx, scope);

                if (this.Converter != null && clientBatchInfo.InMemoryData != null && clientBatchInfo.InMemoryData.HasRows)
                {
                    this.BeforeSerializeRows(clientBatchInfo.InMemoryData);
                }

                var containerSet = clientBatchInfo.InMemoryData == null ? new ContainerSet() : clientBatchInfo.InMemoryData.GetContainerSet();
                changesToSend.Changes     = containerSet;
                changesToSend.IsLastBatch = true;
                changesToSend.BatchIndex  = 0;

                // serialize message
                var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>();
                var binaryData = await serializer.SerializeAsync(changesToSend);

                await this.InterceptAsync(new HttpMessageSendChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false);

                httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>
                                         (this.HttpClient, this.ServiceUri, binaryData, HttpStep.SendChanges, ctx.SessionId, scope.Name,
                                         this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                // Foreach part, will have to send them to the remote
                // once finished, return context
                foreach (var bpi in clientBatchInfo.BatchPartsInfo.OrderBy(bpi => bpi.Index))
                {
                    // If BPI is InMempory, no need to deserialize from disk
                    // othewise load it
                    await bpi.LoadBatchAsync(sanitizedSchema, clientBatchInfo.GetDirectoryFullPath(), this);

                    var changesToSend = new HttpMessageSendChangesRequest(ctx, scope);

                    if (this.Converter != null && bpi.Data.HasRows)
                    {
                        BeforeSerializeRows(bpi.Data);
                    }

                    // Set the change request properties
                    changesToSend.Changes     = bpi.Data.GetContainerSet();
                    changesToSend.IsLastBatch = bpi.IsLastBatch;
                    changesToSend.BatchIndex  = bpi.Index;

                    // serialize message
                    var serializer = this.SerializerFactory.GetSerializer <HttpMessageSendChangesRequest>();
                    var binaryData = await serializer.SerializeAsync(changesToSend);

                    await this.InterceptAsync(new HttpMessageSendChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false);

                    httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>
                                             (this.HttpClient, this.ServiceUri, binaryData, HttpStep.SendChanges, ctx.SessionId, scope.Name,
                                             this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false);


                    // for some reasons, if server don't want to wait for more, just break
                    // That should never happened, actually
                    if (httpMessageContent.ServerStep != HttpStep.SendChangesInProgress)
                    {
                        break;
                    }
                }
            }

            // --------------------------------------------------------------
            // STEP 2 : Receive everything from the server side
            // --------------------------------------------------------------

            // Now we have sent all the datas to the server and now :
            // We have a FIRST response from the server with new datas
            // 1) Could be the only one response (enough or InMemory is set on the server side)
            // 2) Could bt the first response and we need to download all batchs

            // While we have an other batch to process
            var isLastBatch = false;

            // Get if we need to work in memory or serialize things
            var workInMemoryLocally = this.Options.BatchSize == 0;

            // Create the BatchInfo and SyncContext to return at the end
            // Set InMemory by default to "true", but the real value is coming from server side
            var serverBatchInfo = new BatchInfo(workInMemoryLocally, schema, this.Options.BatchDirectory);

            // stats
            DatabaseChangesSelected serverChangesSelected = null;
            DatabaseChangesApplied  clientChangesApplied  = null;

            //timestamp generated by the server, hold in the client db
            long remoteClientTimestamp = 0;

            // While we are not reaching the last batch from server
            do
            {
                // Check if we are at the last batch.
                // If so, we won't make another loop
                isLastBatch           = httpMessageContent.IsLastBatch;
                serverChangesSelected = httpMessageContent.ServerChangesSelected;
                clientChangesApplied  = httpMessageContent.ClientChangesApplied;
                ctx = httpMessageContent.SyncContext;
                remoteClientTimestamp = httpMessageContent.RemoteClientTimestamp;

                var changesSet = sanitizedSchema.Clone();

                changesSet.ImportContainerSet(httpMessageContent.Changes, false);

                if (this.Converter != null && changesSet.HasRows)
                {
                    AfterDeserializedRows(changesSet);
                }

                // Create a BatchPartInfo instance
                await serverBatchInfo.AddChangesAsync(changesSet, httpMessageContent.BatchIndex, isLastBatch, this);

                // free some memory
                if (!workInMemoryLocally && httpMessageContent.Changes != null)
                {
                    httpMessageContent.Changes.Clear();
                }

                if (!isLastBatch)
                {
                    // Ask for the next batch index
                    var requestBatchIndex = httpMessageContent.BatchIndex + 1;

                    // Create the message enveloppe
                    var httpMessage = new HttpMessageGetMoreChangesRequest(ctx, requestBatchIndex);

                    // serialize message
                    var serializer = this.SerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>();
                    var binaryData = await serializer.SerializeAsync(httpMessage);

                    await this.InterceptAsync(new HttpMessageGetMoreChangesRequestArgs(binaryData), cancellationToken).ConfigureAwait(false);

                    httpMessageContent = await this.httpRequestHandler.ProcessRequestAsync <HttpMessageSendChangesResponse>(
                        this.HttpClient, this.ServiceUri, binaryData, HttpStep.GetMoreChanges, ctx.SessionId, scope.Name,
                        this.SerializerFactory, this.Converter, this.Options.BatchSize, cancellationToken).ConfigureAwait(false);
                }
            } while (!isLastBatch);

            // generate the new scope item
            this.CompleteTime = DateTime.UtcNow;

            // Reaffect context
            this.SetContext(httpMessageContent.SyncContext);

            return(remoteClientTimestamp, serverBatchInfo,
                   httpMessageContent.ConflictResolutionPolicy, clientChangesApplied, serverChangesSelected);
        }