public async Task UpdateScore(ScoreRecord score, string leaderboardName)
        {
            var client = await CreateClient(leaderboardName);

            score.CreatedOn = DateTime.UtcNow;
            await client.IndexAsync(score);
        }
        public async Task <long> GetRanking(ScoreRecord score, LeaderboardQuery filters, string leaderboardName)
        {
            var client = await CreateClient(leaderboardName);

            var rankResult = await client.CountAsync <ScoreRecord>(desc => desc
                                                                   .Query(query =>
                                                                          CreateQuery(query, filters,
                                                                                      q =>
            {
                var mustClauses = new List <Func <Nest.QueryContainerDescriptor <ScoreRecord>, Nest.QueryContainer> >();

                mustClauses.Add(q1 => q1.Range(r => r.Field(record => record.Score).GreaterThan(score.Score)));
                if (!EnableExequo)
                {
                    mustClauses.Add(q1 => q1.Bool(b2 => b2.Must(
                                                      q2 => q2.Term(t => t.Field(record => record.Score).Value(score.Score)),
                                                      q2 => q2.DateRange(r => r.Field(record => record.CreatedOn).LessThan(score.CreatedOn))
                                                      )));
                }
                return(q.Bool(b => b.Should(mustClauses)));
            }
                                                                                      )));

            if (!rankResult.IsValid)
            {
                throw new InvalidOperationException($"Failed to compute rank. {rankResult.ServerError.Error.Reason}");
            }
            return(rankResult.Count + 1);
        }
        public Nest.QueryContainer CreateNextPaginationFilter(Nest.QueryContainerDescriptor <ScoreRecord> q, ScoreRecord pivot)
        {
            // ( score < pivot.score) OR (score == pivot.score AND createdOn > pivot.createdOn)
            return(q.Bool(b1 => b1.Should(
                              q1 => q1.Range(r => r.Field(record => record.Score).LessThan(pivot.Score)),
                              q1 => q1.Bool(b2 => b2.Must(
                                                q2 => q2.Term(t => t.Field(record => record.Score).Value(pivot.Score)),
                                                q2 => q2.DateRange(r => r.Field(record => record.CreatedOn).GreaterThan(pivot.CreatedOn))
                                                ))

                              )));
        }
        public async Task <LeaderboardResult <ScoreRecord> > Query(LeaderboardQuery rq)
        {
            var isContinuation = rq is LeaderboardContinuationQuery;
            var client         = await CreateClient(rq);

            ScoreRecord start = null;

            if (!string.IsNullOrEmpty(rq.StartId))
            {
                start = await GetScore(rq.StartId, rq.Name);

                if (start == null)
                {
                    throw new ClientException("Admiral not found in leadeboard.");
                }
            }

            var result = await client.SearchAsync <ScoreRecord>(s =>
            {
                s = s.AllowNoIndices();
                s = s.Query(query => CreateQuery(query, rq, q =>
                {
                    if (start != null)//If we have a pivot we must add constraint to start the result around it.
                    {
                        //Create next/previous additional constraints
                        if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
                        {
                            return(CreatePreviousPaginationFilter(q, start));
                        }
                        else
                        {
                            return(CreateNextPaginationFilter(q, start));
                        }
                    }
                    else
                    {
                        return(q);
                    }
                })).AllowNoIndices();

                if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
                {
                    s = s.Sort(sort => sort.Ascending(record => record.Score).Descending(record => record.CreatedOn));
                }
                else
                {
                    s = s.Sort(sort => sort.Descending(record => record.Score).Ascending(record => record.CreatedOn));
                }
                if ((isContinuation && (rq as LeaderboardContinuationQuery)?.IsPrevious == false) || start == null)
                {
                    s = s.Size(rq.Count + 1).From(rq.Skip);// We get one more document  than necessary to be able to determine if we can build a "next" continuation
                }
                else// The pivot is not included in the result set, if we are not running a continuation query, we must prefix the results with the pivot.
                {
                    s = s.Size(rq.Count).From(rq.Skip);
                }


                return(s);
            });

            if (!result.IsValid)
            {
                if (result.ServerError.Status == 404)
                {
                    return(new LeaderboardResult <ScoreRecord> {
                        Results = new List <LeaderboardRanking <ScoreRecord> >()
                    });
                }
                _logger.Log(LogLevel.Error, "leaderboard", "failed to process query request.", result.ServerError);
                throw new InvalidOperationException($"Failed to query leaderboard : {result.ServerError.Error.Reason}");
            }
            var documents = result.Documents.ToList();

            if (!isContinuation && start != null)
            {
                documents.Insert(0, start);
            }
            else if ((rq as LeaderboardContinuationQuery)?.IsPrevious == true)
            {
                documents.Reverse();
            }

            //Compute rankings
            if (documents.Any())
            {
                int firstRank = 0;
                try
                {
                    firstRank = (int) await GetRanking(documents.First(), rq, rq.Name);
                }
                catch (InvalidOperationException ex)
                {
                    _logger.Log(LogLevel.Error, "leaderboard", ex.Message, ex);
                    throw new InvalidOperationException($"Failed to query leaderboard : {ex.Message}");
                }
                var rank      = firstRank;
                var lastScore = int.MaxValue;
                var lastRank  = firstRank;
                var results   = new List <LeaderboardRanking <ScoreRecord> >();


                foreach (var doc in documents.Take(rq.Count))
                {
                    if (EnableExequo)
                    {
                        int currentRank;
                        if (doc.Score == lastScore)
                        {
                            currentRank = lastRank;
                        }
                        else
                        {
                            currentRank = rank;
                        }

                        results.Add(new LeaderboardRanking <ScoreRecord> {
                            Document = doc, Ranking = currentRank
                        });
                        lastRank = currentRank;
                    }
                    else
                    {
                        results.Add(new LeaderboardRanking <ScoreRecord> {
                            Document = doc, Ranking = rank
                        });
                    }
                    rank++;
                }


                var leaderboardResult = new LeaderboardResult <ScoreRecord> {
                    Results = results
                };

                if (firstRank > 1)//There are scores before the first in the list
                {
                    var previousQuery = new LeaderboardContinuationQuery(rq);
                    previousQuery.Skip         = 0;
                    previousQuery.Count        = rq.Count;
                    previousQuery.IsPrevious   = true;
                    previousQuery.StartId      = results.First().Document.Id;
                    leaderboardResult.Previous = SerializeContinuationQuery(previousQuery);
                }

                if (documents.Count > rq.Count || (rq as LeaderboardContinuationQuery)?.IsPrevious == true)//there are scores after the last in the list.
                {
                    var nextQuery = new LeaderboardContinuationQuery(rq);
                    nextQuery.Skip         = 0;
                    nextQuery.Count        = rq.Count;
                    nextQuery.IsPrevious   = false;
                    nextQuery.StartId      = results.Last().Document.Id;
                    leaderboardResult.Next = SerializeContinuationQuery(nextQuery);
                }

                return(leaderboardResult);
            }
            else
            {
                return(new LeaderboardResult <ScoreRecord>());
            }
        }