/// <summary>
        /// Calculate distance betwee strokes.
        /// DP matching
        /// </summary>
        /// <param name="stroke1"></param>
        /// <param name="stroke2"></param>
        /// <returns></returns>
        private DPMatchingResult CalcStrokeDistance(AnalysisPenStroke stroke1, AnalysisPenStroke stroke2)
        {
            //double distance = 0.0;

            // parameters ----------------------------
            // gap cost for DP matching
            double gapCost = 40.0;
            // ---------------------------------------

            Point[] points1 = stroke1.GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres);
            Point[] points2 = stroke2.GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres);

            if (points1.Length == 0 && points2.Length == 0)
            {
                return new DPMatchingResult(0.0, new List<int[]>());
            }
            if (points1.Length == 0)
            {
                return new DPMatchingResult(1.0 / ((double)points2.Length * gapCost), new List<int[]>());
                //return points2.Length * gapCost;
            }
            if (points2.Length == 0)
            {
                return new DPMatchingResult(1.0 / ((double)points1.Length * gapCost), new List<int[]>());
                //return points1.Length * gapCost;
            }

            double[,] m = new double[points1.Length, points2.Length];
            // from which direction?
            // 0->match, -1->fromPoints1, -2->fromPoints2
            int[,] from = new int[points1.Length, points2.Length];
            for (int i = 0, ilen = points1.Length; i < ilen; i++)
            {
                m[i, 0] = (double)i * gapCost;
                from[i, 0] = -1;
            }
            for (int i = 0, ilen = points2.Length; i < ilen; i++)
            {
                m[0, i] = (double)i * gapCost;
                from[0, i] = -2;
            }
            from[0, 0] = 0;
            for (int i = 1, ilen = points1.Length; i < ilen; i++)
            {
                for (int k = 1, klen = points2.Length; k < klen; k++)
                {
                    double d = 0.0;
                    d = CommonFunction.Distance(points1[i], points2[k]);
                    //Console.WriteLine("Stroke distance: " + d);
                    //double[] scores = new double[] { m[i - 1, k] + gapCost, m[i - 1, k - 1] + d, m[i, k - 1] + gapCost };
                    //m[i, k] = scores.Min();

                    double from_grp1 = m[i - 1, k] + gapCost;
                    double from_grp2 = m[i, k - 1] + gapCost;
                    double from_match = m[i - 1, k - 1] + d;
                    if (from_match <= from_grp1 && from_match <= from_grp2)
                    {
                        m[i, k] = from_match;
                    }
                    else if (from_grp1 <= from_grp2 && from_grp1 <= from_match)
                    {
                        from[i, k] = -1;
                        m[i, k] = from_grp1;
                    }
                    else if (from_grp2 <= from_grp1 && from_grp2 <= from_match)
                    {
                        from[i, k] = -2;
                        m[i, k] = from_grp2;
                    }
                }
            }

            // generate matching list
            // {group1Pos, group2Pos, distance, joinCount}
            List<int[]> matchingList = new List<int[]>();
            int g1pos = points1.Length - 1;
            int g2pos = points2.Length - 1;
            //int matchCnt = 0;
            for (int i = points1.Length - 1, k = points2.Length - 1; i != 0 || k != 0; )
            {
                int[] match = null;
                switch (from[i, k])
                {
                    case -1:
                        match = new int[4] { g1pos, -1, -1, 0 };
                        i--;
                        g1pos--;
                        break;
                    case -2:
                        match = new int[4] { -1, g2pos, -1, 0 };
                        k--;
                        g2pos--;
                        break;
                    default:
                        match = new int[4] { g1pos, g2pos, -1, from[i, k] };
                        i--;
                        k--;
                        g1pos--;
                        g2pos--;
                        //matchCnt++;
                        break;
                }
                matchingList.Add(match);
            }
            matchingList.Reverse();

            DPMatchingResult res = new DPMatchingResult(m[points1.Length - 1, points2.Length - 1] / (Math.Min(points1.Length, points2.Length)), matchingList);

            //distance = m[points1.Length - 1, points2.Length - 1] / (Math.Min(points1.Length, points2.Length));

            return res;
        }
        /// <summary>
        /// Get samples of stroke comparison
        /// </summary>
        /// <param name="group">Answer sheet group</param>
        /// <param name="max">Maximum number</param>
        /// <returns>List of comparison result</returns>
        private List<StrokeComparisonResult> GetStrokeComparisons(AnswerSheetGroup group, int max)
        {
            List<StrokeComparisonResult> results = new List<StrokeComparisonResult>();

            List<AnswerStep> steps1 = GroupAnswerStep(group.AnswerSheetList[0].Strokes);
            List<AnswerStep> steps2 = GroupAnswerStep(group.AnswerSheetList[1].Strokes);

            int cnt = 0;
            bool limit = false;
            foreach (AnswerStep step1 in steps1)
            {
                List<AnalysisPenStroke> strokes1 = step1.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, false);
                foreach (AnswerStep step2 in steps2)
                {
                    List<AnalysisPenStroke> strokes2 = step2.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, false);
                    for (int i = 0, ilen = strokes1.Count; i < ilen; i++)
                    {
                        for (int k = 0, klen = strokes2.Count; k < klen; k++)
                        {
                            DPMatchingResult matchRes = CalcStrokeDistance(strokes1[i], strokes2[k]);
                            Point[] p1 = strokes1[i].GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres);
                            Point[] p2 = strokes2[k].GetRamerSampledPoints_NonRecursive(AnswerSheetAnalyzer.RamerSamplingDistanceThres);
                            AnalysisPenStroke sampledStroke1 = new AnalysisPenStroke();
                            AnalysisPenStroke sampledStroke2 = new AnalysisPenStroke();
                            for (int m = 0, mlen = p1.Length; m < mlen; m++)
                            {
                                sampledStroke1.Points.Add(new AnalysisPenPoint(0, p1[m].X, p1[m].Y));
                            }
                            for (int m = 0, mlen = p2.Length; m < mlen; m++)
                            {
                                sampledStroke2.Points.Add(new AnalysisPenPoint(0, p2[m].X, p2[m].Y));
                            }
                            StrokeComparisonResult compResult = new StrokeComparisonResult(strokes1[i], strokes2[k], sampledStroke1, sampledStroke2, matchRes);
                            results.Add(compResult);

                            cnt++;
                            if (max <= cnt)
                            {
                                limit = true;
                                break;
                            }
                        }
                        if (limit) break;
                    }
                    if (limit) break;
                }
                if (limit) break;
            }

            return results;
        }
        /// <summary>
        /// Get normalized strokes. Call "ClearCache" function for recalculating
        /// </summary>
        /// <param name="height">Height of bounding box after the normalization</param>
        /// <param name="sortPos">Sort strokes in order of x coordinate of gravity point</param>
        /// <returns></returns>
        public List<AnalysisPenStroke> GetNormalizedStrokes(double height, bool sortPos)
        {
            if (this.normalizedStrokes == null)
            {
                List<AnalysisPenStroke> strokes = new List<AnalysisPenStroke>(this.Strokes);
                Rect gr = GetBounds();
                double scale = height / gr.Height;

                // position sort
                if (sortPos)
                {
                    strokes = AnalysisPenStroke.SortByCenterX(strokes);
                }

                // normalization
                List<AnalysisPenStroke> nstrokes = new List<AnalysisPenStroke>();
                foreach (AnalysisPenStroke s in strokes)
                {
                    AnalysisPenStroke ns = new AnalysisPenStroke();
                    Rect sr = s.BoundingBox;
                    foreach (AnalysisPenPoint p in s.Points)
                    {
                        AnalysisPenPoint np = new AnalysisPenPoint(p.Time, (p.X - sr.Left) * scale, (p.Y - sr.Top) * scale);
                        ns.Points.Add(np);
                        //Console.Write("(" + np.X + "," + np.Y + "),");
                    }
                    nstrokes.Add(ns);
                    //Console.WriteLine();
                }
                this.normalizedStrokes = nstrokes;
            }

            return this.normalizedStrokes;
        }
        /// <summary>
        /// Load writing data file
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public List<AnalysisPenStroke> LoadStrokesFromFile(string path)
        {
            List<AnalysisPenStroke> strokes = new List<AnalysisPenStroke>();
            using (StreamReader sr = new StreamReader(path))
            {
                while (sr.Peek() >= 0)
                {
                    string jsonStr = sr.ReadLine();
                    AnalysisPenStroke dnpStroke = new AnalysisPenStroke(jsonStr);
                    Stroke stroke = dnpStroke.GetStrokeObject(Config.OutputCanvasWidth, Config.OutputCanvasHeight);
                    strokes.Add(dnpStroke);
                }
            }

            return strokes;
        }
        /// <summary>
        /// Join an answer step after the answer step
        /// </summary>
        /// <param name="step"></param>
        public void JoinStrokes(AnswerStep step)
        {
            // Calculate offset value for coordinate transformation
            Rect orgBounds = this.GetBounds();
            Rect addBounds = step.GetBounds();
            Point endPoint = new Point(orgBounds.Left, (orgBounds.Top + orgBounds.Bottom) / 2.0);
            Point startPoint = new Point(addBounds.Left, (addBounds.Top + addBounds.Bottom) / 2.0);
            Point offsetPoint = new Point(endPoint.X - startPoint.X, endPoint.Y - startPoint.Y);

            // transform coordinates to join anter the origin strokes
            foreach (AnalysisPenStroke addStroke in step.Strokes)
            {
                AnalysisPenStroke s = new AnalysisPenStroke();
                foreach (AnalysisPenPoint addPoint in addStroke.Points)
                {
                    AnalysisPenPoint p = new AnalysisPenPoint(addPoint.Time, addPoint.X + offsetPoint.X, addPoint.Y + offsetPoint.Y);
                    s.Points.Add(p);
                }
                this.Strokes.Add(s);
            }
        }