using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Grpc.Core.Utils;
namespace Routeguide
{
    /// 
    /// Example implementation of RouteGuide server.
    /// 
    public class RouteGuideImpl : RouteGuide.IRouteGuide
    {
        readonly List features;
        readonly object myLock = new object();
        readonly Dictionary> routeNotes = new Dictionary>();   
        public RouteGuideImpl(List features)
        {
            this.features = features;
        }
        /// 
        /// Gets the feature at the requested point. If no feature at that location
        /// exists, an unnammed feature is returned at the provided location.
        /// 
        public Task GetFeature(Point request, Grpc.Core.ServerCallContext context)
        {
            return Task.FromResult(CheckFeature(request));
        }
        /// 
        /// Gets all features contained within the given bounding rectangle.
        /// 
        public async Task ListFeatures(Rectangle request, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context)
        {
            var responses = features.FindAll( (feature) => feature.Exists() && request.Contains(feature.Location) );
            foreach (var response in responses)
            {
                await responseStream.WriteAsync(response);
            }
        }
        /// 
        /// Gets a stream of points, and responds with statistics about the "trip": number of points,
        /// number of known features visited, total distance traveled, and total time spent.
        /// 
        public async Task RecordRoute(Grpc.Core.IAsyncStreamReader requestStream, Grpc.Core.ServerCallContext context)
        {
            int pointCount = 0;
            int featureCount = 0;
            int distance = 0;
            Point previous = null;
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            while (await requestStream.MoveNext())
            {
                var point = requestStream.Current;
                pointCount++;
                if (CheckFeature(point).Exists())
                {
                    featureCount++;
                }
                if (previous != null)
                {
                    distance += (int) previous.GetDistance(point);
                }
                previous = point;
            }
            stopwatch.Stop();
            
            return new RouteSummary
            {
                PointCount = pointCount,
                FeatureCount = featureCount,
                Distance = distance,
                ElapsedTime = (int)(stopwatch.ElapsedMilliseconds / 1000)
            };
        }
        /// 
        /// Receives a stream of message/location pairs, and responds with a stream of all previous
        /// messages at each of those locations.
        /// 
        public async Task RouteChat(Grpc.Core.IAsyncStreamReader requestStream, Grpc.Core.IServerStreamWriter responseStream, Grpc.Core.ServerCallContext context)
        {
            while (await requestStream.MoveNext())
            {
                var note = requestStream.Current;
                List prevNotes = AddNoteForLocation(note.Location, note);
                foreach (var prevNote in prevNotes)
                {
                    await responseStream.WriteAsync(prevNote);
                }
            }
        }
        /// 
        /// Adds a note for location and returns a list of pre-existing notes for that location (not containing the newly added note).
        /// 
        private List AddNoteForLocation(Point location, RouteNote note)
        {
            lock (myLock)
            {
                List notes;
                if (!routeNotes.TryGetValue(location, out notes)) {
                    notes = new List();
                    routeNotes.Add(location, notes);
                }
                var preexistingNotes = new List(notes);
                notes.Add(note);
                return preexistingNotes;
            }
        }
        /// 
        /// Gets the feature at the given point.
        /// 
        /// the location to check
        /// The feature object at the point Note that an empty name indicates no feature.
        private Feature CheckFeature(Point location)
        {
            var result = features.FirstOrDefault((feature) => feature.Location.Equals(location));
            if (result == null)
            {
                // No feature was found, return an unnamed feature.
                return new Feature { Name = "", Location = location };
            }
            return result;
        }
    }
}