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,
        Delete = 4
    }

    abstract 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 int numFramesDetectedUntilTracked;
        private int numFramesLostUntilDeleted;
        private float minSimilarityForTracking;

        public Tracker(int numFramesDetectedUntilTracked, int numFramesLostUntilDeleted, float minSimilarityForTracking)
        {
            this.numFramesDetectedUntilTracked = numFramesDetectedUntilTracked;
            this.numFramesLostUntilDeleted = numFramesLostUntilDeleted;
            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 = createNewHistory();
                    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 = createNewHistory();
                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)
            {
                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.Delete)
                {
                    Histories.RemoveAt(i);
                }
            }
        }

        private TrackableObjectHistory<T> createNewHistory()
        {
            TrackableObjectHistory<T> history = new TrackableObjectHistory<T>(numFramesDetectedUntilTracked, numFramesLostUntilDeleted);
            history.Detected += onDetect;
            history.Tracked += onTrack;
            history.Retracked += onRetrack;
            history.Lost += onLoose;
            history.Deleted += onDelete;
            return history;
        }

        protected abstract void onDetect(object sender, EventArgs e);
        protected abstract void onTrack(object sender, EventArgs e);
        protected abstract void onRetrack(object sender, EventArgs e);
        protected abstract void onLoose(object sender, EventArgs e);
        protected abstract void onDelete(object sender, EventArgs e);
    }
}
