using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using bbiwarg.Graphics;
using bbiwarg.Utility;

namespace bbiwarg.Recognition.Tracking
{
    public enum TrackingState
    {
        None = 0,
        Detected = 1,
        Tracked = 2,
        Lost = 3,
    }

    class Tracker<T> where T : TrackableObject
    {
        public List<TrackableObjectHistory<T>> Histories { get; private set; }
        public List<T> TrackedObjects { get; private set; }
        private List<SimilarityContainer<T>> similarities;
        private String className;
        private LogSubject logSubject;
        private int numFramesUntilTracked;
        private int numFramesUntilLost;
        private float minSimilarityForTracking;

        public Tracker(LogSubject logSubject, int numFramesUntilTracked, int numFramesUntilLost, float minSimilarityForTracking)
        {
            this.className = typeof(T).Name;
            this.logSubject = logSubject;
            this.numFramesUntilTracked = numFramesUntilTracked;
            this.numFramesUntilLost = numFramesUntilLost;
            this.minSimilarityForTracking = minSimilarityForTracking;

            reset();
        }

        public void reset()
        {
            Histories = new List<TrackableObjectHistory<T>>();
            TrackedObjects = new List<T>();
        }

        public void updateFrame(List<T> detectedObjects)
        {
            if (Histories.Count == 0)
            {
                foreach (T detectedObject in detectedObjects)
                {
                    TrackableObjectHistory<T> history = new TrackableObjectHistory<T>(logSubject, numFramesUntilTracked);
                    history.addObjectToHistory(detectedObject);
                    Histories.Add(history);
                }
            }
            else
            {
                updateHistories(detectedObjects);
                removeLongLostObjects();
            }
        }

        private void updateHistories(List<T> detectedObjects)
        {
            TrackedObjects = new List<T>();

            List<TrackableObjectHistory<T>> unasignedHistories = new List<TrackableObjectHistory<T>>();
            foreach (TrackableObjectHistory<T> history in Histories)
                unasignedHistories.Add(history);

            List<TrackableObject> unasignedDetectedObjects = new List<TrackableObject>();
            foreach (T detectedObject in detectedObjects)
                unasignedDetectedObjects.Add(detectedObject);


            //asign previously tracked objects to their best fits of detectedObjects
            createSimilarities(detectedObjects);
            while (similarities.Count > 0)
            {
                SimilarityContainer<T> maxSimilarity = similarities[0];
                TrackableObjectHistory<T> history = maxSimilarity.History;
                T detectedObject = maxSimilarity.DetectedObject;
                history.addObjectToHistory(detectedObject);

                TrackedObjects.Add(detectedObject);
                unasignedHistories.Remove(history);
                unasignedDetectedObjects.Remove(detectedObject);
                removeConcurringSimilarities(maxSimilarity);
            }
            //add new history for each new unasigned detectedObject
            foreach (T unasignedDetectedObject in unasignedDetectedObjects)
            {
                TrackableObjectHistory<T> newHistory = new TrackableObjectHistory<T>(logSubject, numFramesUntilTracked);
                newHistory.addObjectToHistory(unasignedDetectedObject);
                Histories.Add(newHistory);
            }

            //add null-object to each unasigned history (didn't find a best fit during this frame -> lost track)
            foreach (TrackableObjectHistory<T> unasignedHistory in unasignedHistories)
            {
                if (unasignedHistory.CurrentState != TrackingState.Lost)
                    Logger.log(className + " #" + unasignedHistory.ID + " lost", logSubject);
                unasignedHistory.addObjectToHistory(null);
            }

        }

        private void createSimilarities(List<T> detectedObjects)
        {
            similarities = new List<SimilarityContainer<T>>();

            foreach (TrackableObjectHistory<T> history in Histories)
            {
                foreach (T detectedObject in detectedObjects)
                {
                    SimilarityContainer<T> similarityContainer = new SimilarityContainer<T>(history, detectedObject);
                    if (similarityContainer.Similarity > minSimilarityForTracking)
                    {
                        similarities.Add(similarityContainer);
                    }
                }
            }

            // sort depending on similarity-value
            similarities.Sort((fs1, fs2) => fs2.Similarity.CompareTo(fs1.Similarity));
        }

        private void removeConcurringSimilarities(SimilarityContainer<T> removeSimilarity)
        {
            for (int i = similarities.Count - 1; i >= 0; i--)
            {
                SimilarityContainer<T> similarity = similarities[i];
                if (similarity.History == removeSimilarity.History || similarity.DetectedObject == removeSimilarity.DetectedObject)
                    similarities.RemoveAt(i);
            }
        }

        private void removeLongLostObjects()
        {
            for (int i = Histories.Count - 1; i >= 0; i--)
            {
                TrackableObjectHistory<T> history = Histories[i];
                if (history.CurrentState == TrackingState.Lost && history.NumFramesInCurrentState >= numFramesUntilLost)
                {
                    Histories.RemoveAt(i);
                    Logger.log(className + " #" + history.ID + " deleted", logSubject);
                }
            }
        }
    }
}
