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

namespace bbiwarg.Recognition.Tracking
{
    public delegate void TrackingStateChangedEventHandler(object sender, EventArgs e);

    class TrackableObjectHistory<T> where T : TrackableObject
    {
        private static List<int> usedIDs = new List<int>();
        public int ID { get; private set; }
        public List<T> TrackedObjects { get; private set; }
        public List<TrackingState> States { get; private set; }
        public T LastObject { get; private set; }
        public T CurrentObject { get { return TrackedObjects[TrackedObjects.Count - 1]; } }
        public TrackingState CurrentState { get { return States[States.Count - 1]; } }
        public int NumFramesInCurrentState { get; private set; }
        private int numFramesDetectedUntilTracked;
        private int numFramesLostUntilDeleted;
        private bool wasTrackedBefore;

        public event TrackingStateChangedEventHandler Detected;
        public event TrackingStateChangedEventHandler Tracked;
        public event TrackingStateChangedEventHandler Retracked;
        public event TrackingStateChangedEventHandler Lost;
        public event TrackingStateChangedEventHandler Deleted;

        protected virtual void OnDetect(EventArgs e) { if (Detected != null) Detected(this, e); }
        protected virtual void OnTrack(EventArgs e) { if (Tracked != null) Tracked(this, e); }
        protected virtual void OnRetrack(EventArgs e) { if (Retracked != null) Retracked(this, e); }
        protected virtual void OnLoose(EventArgs e) { if (Lost != null) Lost(this, e); }
        protected virtual void OnDelete(EventArgs e) { if (Deleted != null) Deleted(this, e); }

        public TrackableObjectHistory(int numFramesDetectedUntilTracked, int numFramesLostUntilDeleted)
        {
            ID = getNextUnusedID();
            TrackedObjects = new List<T>();
            TrackedObjects.Add(null);
            States = new List<TrackingState>();
            States.Add(TrackingState.None);
            NumFramesInCurrentState = 1;
            this.numFramesDetectedUntilTracked = numFramesDetectedUntilTracked;
            this.numFramesLostUntilDeleted = numFramesLostUntilDeleted;
            this.wasTrackedBefore = false;
        }

        ~TrackableObjectHistory()
        {
            setIDUnused(ID);
        }

        public void addObjectToHistory(T detectedObject)
        {
            TrackingState previousState = CurrentState;
            TrackingState newState = TrackingState.None;

            //get newState
            if (detectedObject != null)
            {
                if (previousState == TrackingState.None)
                    newState = TrackingState.Detected;
                else if (previousState == TrackingState.Lost)
                {
                    if (wasTrackedBefore)
                        newState = TrackingState.Tracked;
                    else
                        newState = TrackingState.Detected;
                }
                else if (previousState == TrackingState.Tracked)
                    newState = TrackingState.Tracked;
                else if (previousState == TrackingState.Detected)
                {
                    if (NumFramesInCurrentState == numFramesDetectedUntilTracked)
                        newState = TrackingState.Tracked;
                    else
                        newState = TrackingState.Detected;
                }
            }
            else if (previousState == TrackingState.Lost && NumFramesInCurrentState == numFramesLostUntilDeleted)
                newState = TrackingState.Delete;
            else
                newState = TrackingState.Lost;

            //set trackedBefore
            if (newState == TrackingState.Tracked)
                wasTrackedBefore = true;

            //update numFramesInCurrentState
            if (newState == previousState)
                NumFramesInCurrentState++;
            else
                NumFramesInCurrentState = 1;

            //update lastObject
            if (detectedObject != null)
            {
                LastObject = detectedObject;
                detectedObject.setTrackID(ID);
            }

            //add current state+object
            TrackedObjects.Add(detectedObject);
            States.Add(newState);

            //fire event
            switch (newState) {
                case TrackingState.Detected:
                    OnDetect(EventArgs.Empty);
                    break;
                case TrackingState.Tracked:
                    if (previousState == TrackingState.Lost)
                        OnRetrack(EventArgs.Empty);
                    else
                        OnTrack(EventArgs.Empty);
                    break;
                case TrackingState.Lost:
                    OnLoose(EventArgs.Empty);
                    break;
                case TrackingState.Delete:
                    OnDelete(EventArgs.Empty);
                    break;
            }
        }

        public static int getNextUnusedID()
        {
            int id = 1;
            while (usedIDs.Contains(id))
                id++;
            usedIDs.Add(id);
            return id;
        }

        public static void setIDUnused(int id)
        {
            usedIDs.Remove(id);
        }
    }
}
