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

namespace bbiwarg.Recognition.FingerRecognition
{
    public class FingerSliceTrail
    {
        private LineSegment2D lineSegment;
        private Vector2D fittedDirection;
        private bool lineSegmentUpToDate;
        private bool fittedDirectionUpToDate;

        public List<FingerSlice> Slices { get; private set; }
        public FingerSlice StartSlice { get { return Slices[0]; } }
        public FingerSlice MidSlice { get { return Slices[NumSlices / 2]; } }
        public FingerSlice EndSlice { get { return Slices[Slices.Count - 1]; } }
        public FingerSlice this[int index] { get { return Slices[index]; } }
        public int NumSlices { get { return Slices.Count; } }
        public LineSegment2D LineSegment { get { if (!lineSegmentUpToDate) updateLineSegment(); return lineSegment; } }
        public Vector2D FittedDirection { get { if (!fittedDirectionUpToDate) updateFittedDirection(); return fittedDirection; } }

        public FingerSliceTrail(FingerSlice slice)
        {
            Slices = new List<FingerSlice>();
            addSlice(slice);
            lineSegmentUpToDate = false;
            fittedDirectionUpToDate = false;
        }

        public void addSlice(FingerSlice slice)
        {
            Slices.Add(slice);
            lineSegmentUpToDate = false;
            fittedDirectionUpToDate = false;
        }

        public Vector2D getStartDirection()
        {
            int innerStartIndex = Math.Min(NumSlices-1, Parameters.FingerNumSlicesForRelativeDirection);
            return (StartSlice.Mid - Slices[innerStartIndex].Mid).normalize();
        }

        public Vector2D getEndDirection()
        {
            int innerEndIndex = Math.Max(0, NumSlices - Parameters.FingerNumSlicesForRelativeDirection);
            return (EndSlice.Mid - Slices[innerEndIndex].Mid).normalize();
        }

        public void removeFirstSlices(int numSlices)
        {
            Slices.RemoveRange(0, numSlices);
            lineSegmentUpToDate = false;
            fittedDirectionUpToDate = false;
        }

        public void reverse()
        {
            Slices.Reverse();
            lineSegmentUpToDate = false;
            fittedDirectionUpToDate = false;
        }

        public Contour<Point> getContour(float margin) {
            List<Point> pointsA = new List<Point>();
            List<Point> pointsB = new List<Point>();

            foreach (FingerSlice slice in Slices)
            {
                Vector2D direction = slice.Direction;
                pointsA.Add(slice.Start.moveWithinBound(direction.getInverse(), margin));
                pointsB.Add(slice.End.moveWithinBound(direction, margin));
            }

            pointsA.Reverse();
            pointsA.AddRange(pointsB);

            Contour<Point> contour = new Contour<Point>(new MemStorage());
            contour.PushMulti(pointsA.ToArray(), Emgu.CV.CvEnum.BACK_OR_FRONT.FRONT);
            return contour;
        }

        private void updateLineSegment()
        {
            lineSegment = new LineSegment2D(EndSlice.Mid, StartSlice.Mid);
            lineSegmentUpToDate = true;
        }

        private void updateFittedDirection()
        {
            List<PointF> midPoints = new List<PointF>();
            foreach (FingerSlice slice in Slices)
                midPoints.Add(slice.Mid);

            PointF pointOnLine, direction;
            PointCollection.Line2DFitting(midPoints.ToArray(), Emgu.CV.CvEnum.DIST_TYPE.CV_DIST_FAIR, out direction, out pointOnLine);
            fittedDirection = new Vector2D(direction).normalize();

            if (fittedDirection.isInOppositeDirection(LineSegment.Direction))
                fittedDirection = fittedDirection.getInverse();

            fittedDirectionUpToDate = true;
        }

    }
}
