public async Task QueryingWithAggregatedHashesShouldResultInTheSameMatches()
        {
            var audioService = new SoundFingerprintingAudioService();
            var modelService = new InMemoryModelService();

            int count = 20, testWaitTime = 5000;
            var data         = GenerateRandomAudioChunks(count, 1, DateTime.UtcNow);
            var concatenated = Concatenate(data);
            var hashes       = await FingerprintCommandBuilder.Instance
                               .BuildFingerprintCommand()
                               .From(concatenated)
                               .WithFingerprintConfig(config => config)
                               .UsingServices(audioService)
                               .Hash();

            modelService.Insert(new TrackInfo("312", "Bohemian Rhapsody", "Queen"), hashes);

            var collection = SimulateRealtimeQueryData(data, jitterLength: 0);
            var cancellationTokenSource = new CancellationTokenSource(testWaitTime);
            var fingerprints            = new List <Hashes>();
            var entries = new List <ResultEntry>();

            await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
            .From(collection)
            .WithRealtimeQueryConfig(config =>
            {
                config.SuccessCallback   = entry => entries.Add(entry);
                config.ResultEntryFilter = new TrackRelativeCoverageLengthEntryFilter(0.8d);
                config.Stride            = new IncrementalStaticStride(2048);
                return(config);
            })
            .Intercept(queryHashes =>
            {
                fingerprints.Add(queryHashes);
                return(queryHashes);
            })
            .UsingServices(modelService)
            .Query(cancellationTokenSource.Token);

            Assert.IsTrue(entries.Any());
            Assert.AreEqual(1, entries.Count);
            var realtimeResult    = entries.First();
            var aggregatedHashes  = Hashes.Aggregate(fingerprints, 60d).First();
            var nonRealtimeResult = await QueryCommandBuilder.Instance
                                    .BuildQueryCommand()
                                    .From(aggregatedHashes)
                                    .UsingServices(modelService, audioService)
                                    .Query();

            Assert.IsTrue(nonRealtimeResult.ContainsMatches);
            Assert.AreEqual(1, nonRealtimeResult.ResultEntries.Count());
            Assert.AreEqual(realtimeResult.MatchedAt, aggregatedHashes.RelativeTo);
            Assert.AreEqual(realtimeResult.MatchedAt, nonRealtimeResult.BestMatch.MatchedAt, $"Realtime vs NonRealtime {nonRealtimeResult.BestMatch.Coverage.BestPath.Count()} match time does not match");
        }
        public async Task HashesShouldMatchExactlyWhenAggregated()
        {
            var audioService = new SoundFingerprintingAudioService();
            var modelService = new InMemoryModelService();

            int count        = 20;
            var data         = GenerateRandomAudioChunks(count, seed: 1, DateTime.UtcNow);
            var concatenated = Concatenate(data);
            var hashes       = await FingerprintCommandBuilder.Instance
                               .BuildFingerprintCommand()
                               .From(concatenated)
                               .WithFingerprintConfig(config =>
            {
                config.Stride = new IncrementalStaticStride(512);
                return(config);
            })
                               .UsingServices(audioService)
                               .Hash();

            var collection = SimulateRealtimeQueryData(data, jitterLength: 0);
            var list       = new List <Hashes>();

            await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
            .From(collection)
            .WithRealtimeQueryConfig(config =>
            {
                config.Stride = new IncrementalStaticStride(512);
                return(config);
            })
            .Intercept(timedHashes =>
            {
                list.Add(timedHashes);
                return(timedHashes);
            })
            .UsingServices(modelService)
            .Query(CancellationToken.None);

            Assert.AreEqual(hashes.Count, list.Select(entry => entry.Count).Sum());
            var merged = Hashes.Aggregate(list, concatenated.Duration).ToList();

            Assert.AreEqual(1, merged.Count, $"Hashes:{string.Join(",", merged.Select(_ => $"{_.RelativeTo},{_.DurationInSeconds:0.00}"))}");
            Assert.AreEqual(hashes.Count, merged.Select(entry => entry.Count).Sum());

            var aggregated = Hashes.Aggregate(list, double.MaxValue).ToList();

            Assert.AreEqual(1, aggregated.Count);
            Assert.AreEqual(hashes.Count, aggregated[0].Count);
            foreach (var zipped in hashes.OrderBy(h => h.SequenceNumber).Zip(aggregated[0], (a, b) => new { a, b }))
            {
                Assert.AreEqual(zipped.a.StartsAt, zipped.b.StartsAt, 1d);
                Assert.AreEqual(zipped.a.SequenceNumber, zipped.b.SequenceNumber);
                CollectionAssert.AreEqual(zipped.a.HashBins, zipped.b.HashBins);
            }
        }
        public async Task HashesShouldMatchExactlyWhenAggregated()
        {
            var audioService = new SoundFingerprintingAudioService();
            var modelService = new InMemoryModelService();

            int count = 20, testWaitTime = 40000;
            var data = GenerateRandomAudioChunks(count, 1);
            var concatenated = Concatenate(data);
            var hashes = await FingerprintCommandBuilder.Instance
                .BuildFingerprintCommand()
                .From(concatenated)
                .WithFingerprintConfig(config =>
                {
                    config.Stride = new IncrementalStaticStride(512);
                    return config;
                })
                .UsingServices(audioService)
                .Hash();
            
            var collection = SimulateRealtimeQueryData(data, false, TimeSpan.FromSeconds);
            var cancellationTokenSource = new CancellationTokenSource(testWaitTime);
            var list = new List<Hashes>();
            
            await QueryCommandBuilder.Instance.BuildRealtimeQueryCommand()
                .From(collection)
                .WithRealtimeQueryConfig(config =>
                {
                    config.QueryFingerprintsCallback += timedHashes => list.Add(timedHashes);
                    config.Stride = new IncrementalStaticStride(512);
                    return config;
                })
                .UsingServices(modelService)
                .Query(cancellationTokenSource.Token);
            
            Assert.AreEqual(hashes.Count, list.Select(entry => entry.Count).Sum());
            var merged = Hashes.Aggregate(list, 20d).ToList();
            Assert.AreEqual(2, merged.Count);
            Assert.AreEqual(hashes.Count, merged.Select(entry => entry.Count).Sum());

            var aggregated = Hashes.Aggregate(list, double.MaxValue).ToList();
            Assert.AreEqual(1, aggregated.Count);
            Assert.AreEqual(hashes.Count, aggregated[0].Count);
            foreach (var zipped in hashes.OrderBy(h => h.SequenceNumber).Zip(aggregated[0], (a, b) => new { a, b }))
            {
                Assert.AreEqual(zipped.a.StartsAt, zipped.b.StartsAt, 0.5d);
                Assert.AreEqual(zipped.a.SequenceNumber, zipped.b.SequenceNumber);
                CollectionAssert.AreEqual(zipped.a.HashBins, zipped.b.HashBins);
            }
        }