using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace bbiwarg.Recognition.Tracking
{
    abstract class Tracker<T, TrackedT>
        where T : TrackableObject
        where TrackedT : TrackedObject<T>
    {
        private float minSimilarity;
        private List<Similarity<T, TrackedT>> similarities;
        protected TrackIDPool idPool;

        public List<TrackedT> TrackedObjects;


        public Tracker()
        {
            reset();
        }

        public void reset()
        {
            idPool = new TrackIDPool();
            TrackedObjects = new List<TrackedT>();
        }

        public void updateFrame(List<T> detectedObjects)
        {
            if (TrackedObjects.Count == 0)
            {
                addNewTrackedObjects(detectedObjects);
            }
            else
            {
                updateTrackedObjects(detectedObjects);
                removeDeletableTrackedObjects();
            }
        }

        public void updateTrackedObjects(List<T> detectedObjects)
        {
            List<TrackedT> unasignedTrackedObjects = new List<TrackedT>(TrackedObjects);
            List<T> unasignedDetectedObjects = new List<T>(detectedObjects);

            createSimilarities(detectedObjects);
            while (similarities.Count > 0) {
                Similarity<T, TrackedT> maxSimilarity = similarities[0];
                maxSimilarity.TrackedObject.updateFrame(maxSimilarity.DetectedObject);

                unasignedDetectedObjects.Remove(maxSimilarity.DetectedObject);
                unasignedTrackedObjects.Remove(maxSimilarity.TrackedObject);
                removeConcurringSimilarities(maxSimilarity);
            }

            addNewTrackedObjects(unasignedDetectedObjects);

            foreach (TrackedT trackedObject in unasignedTrackedObjects) {
                trackedObject.updateFrame(null);
            }
        }

        private void addNewTrackedObjects(List<T> detectedObjects)
        {
            foreach (T detectedObject in detectedObjects)
            {
                TrackedT trackedObject = createTrackedObject(detectedObject);
                TrackedObjects.Add(trackedObject);
            }
        }

        private void createSimilarities(List<T> detectedObjects) {
            similarities = new List<Similarity<T, TrackedT>>();

            foreach (TrackedT trackedObject in TrackedObjects) {
                foreach (T detectedObject in detectedObjects) {
                    float similarityValue = trackedObject.calculateSimilarity(detectedObject);
                    Similarity<T, TrackedT> similarity = new Similarity<T,TrackedT>(trackedObject, detectedObject, similarityValue);
                    if (similarity.Value > 0)
                        similarities.Add(similarity);
                }
            }

            // sort depending on similarity-value
            similarities.Sort((s1, s2) => s2.Value.CompareTo(s1.Value));
        }

        private void removeConcurringSimilarities(Similarity<T, TrackedT> similarity) {
            for (int i = similarities.Count - 1; i >= 0; i--) {
                Similarity<T, TrackedT> s = similarities[i];
                if (s.TrackedObject == similarity.TrackedObject || s.DetectedObject == similarity.DetectedObject)
                    similarities.RemoveAt(i);
            }
        }

        private void removeDeletableTrackedObjects() {
            for (int i = TrackedObjects.Count - 1; i >= 0; i--) {
                TrackedT trackedObject = TrackedObjects[i];
                if (trackedObject.CurrentState == TrackingState.Deleted) {
                    idPool.setIDUnused(trackedObject.ID);
                    TrackedObjects.RemoveAt(i);
                }
            }
        }

        protected List<T> getCurrentObjectsWithState(TrackingState state) {
            List<T> objects = new List<T>();
            foreach (TrackedT trackedObject in TrackedObjects) {
                if (trackedObject.CurrentState == state)
                    objects.Add(trackedObject.CurrentObject);
            }
            return objects;
        } 

        protected abstract TrackedT createTrackedObject(T detectedObject);
    }
}