package handler

import (
	"context"
	"io"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	pb "github.com/micro/services/test/routes/proto"
)

// RouteGuide implements the route guide handler interface
type RouteGuide struct {
	Features  []*pb.Feature
	Notes     map[string][]*pb.RouteNote
	NotesLock sync.Mutex
}

// GetFeature obtains the feature at a given position.
func (r *RouteGuide) GetFeature(ctx context.Context, point *pb.Point, feature *pb.Feature) error {
	for _, f := range r.Features {
		if proto.Equal(f.Location, point) {
			*feature = *f
			return nil
		}
	}

	// No feature was found, return an unnamed feature
	feature.Location = point
	return nil
}

// ListFeatures obtains the Features available within the given Rectangle.  Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
func (r *RouteGuide) ListFeatures(ctx context.Context, rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesStream) error {
	for _, f := range r.Features {
		if inRange(f.Location, rect) {
			if err := stream.Send(f); err != nil {
				return err
			}
		}
	}

	return nil
}

// RecordRoute accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
func (r *RouteGuide) RecordRoute(ctx context.Context, stream pb.RouteGuide_RecordRouteStream) error {
	var pointCount, featureCount, distance int32
	var lastPoint *pb.Point
	startTime := time.Now()

	for {
		point, err := stream.Recv()
		if err == io.EOF {
			endTime := time.Now()

			return stream.SendAndClose(&pb.RouteSummary{
				PointCount:   pointCount,
				FeatureCount: featureCount,
				Distance:     distance,
				ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
			})
		}
		if err != nil {
			return err
		}
		pointCount++
		for _, f := range r.Features {
			if proto.Equal(f.Location, point) {
				featureCount++
			}
		}
		if lastPoint != nil {
			distance += calcDistance(lastPoint, point)
		}
		lastPoint = point
	}
}

// RouteChat accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
func (r *RouteGuide) RouteChat(ctx context.Context, stream pb.RouteGuide_RouteChatStream) error {
	for {
		in, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		key := serialize(in.Location)

		r.NotesLock.Lock()
		r.Notes[key] = append(r.Notes[key], in)
		// Note: this copy prevents blocking other clients while serving this one.
		// We don't need to do a deep copy, because elements in the slice are
		// insert-only and never modified.
		rn := make([]*pb.RouteNote, len(r.Notes[key]))
		copy(rn, r.Notes[key])
		r.NotesLock.Unlock()

		for _, note := range rn {
			if err := stream.Send(note); err != nil {
				return err
			}
		}
	}
}
