/// <summary>
        /// Visualize answer process matching result
        /// </summary>
        /// <param name="ansGroupList1"></param>
        /// <param name="ansGroupList2"></param>
        /// <param name="matchingResult"></param>
        /// <param name="graphCanvas"></param>
        /// <param name="g2Origin">Offset point of origin</param>
        private void VisualizeMatchingResult(List<AnswerStep> ansGroupList1, List<AnswerStep> ansGroupList2, DPMatchingResult matchingResult, Canvas graphCanvas, Point g2Origin)
        {
            for (int i = 0, ilen = matchingResult.MatchingList.Count; i < ilen; i++)
            {
                List<int[]> matchings = matchingResult.MatchingList;
                if (matchings[i][0] != -1 && matchings[i][1] != -1)
                {
                    // draw joined group
                    Rect rect1 = ansGroupList1[matchings[i][0]].GetBounds(Config.OutputCanvasWidth, Config.OutputCanvasHeight);
                    Rect rect2 = ansGroupList2[matchings[i][1]].GetBounds(Config.OutputCanvasWidth, Config.OutputCanvasHeight);
                    if (matchings[i][3] > 0)
                    {
                        // join
                        List<AnswerStep> joinSteps = new List<AnswerStep>();
                        Rect jr;
                        List<int> joinedId = new List<int>();
                        double canvasLeftOffset = 0.0;
                        double canvasTopOffset = 0.0;
                        if (matchings[i - 1][1] == -1)
                        {
                            // group1 joined
                            for (int k = 0; k <= matchings[i][3]; k++)
                            {
                                joinSteps.Add(ansGroupList1[matchings[i][0] - k]);
                                joinedId.Add(matchings[i][0] - k);
                            }
                            jr = AnswerStep.GetJoinedBounds(joinSteps, Config.OutputCanvasWidth, Config.OutputCanvasHeight);
                            rect1 = jr;
                        }
                        else
                        {
                            // group2 joined
                            for (int k = 0; k <= matchings[i][3]; k++)
                            {
                                joinSteps.Add(ansGroupList2[matchings[i][1] - k]);
                                joinedId.Add(matchings[i][1] - k);
                            }
                            canvasLeftOffset = g2Origin.X;
                            canvasTopOffset = g2Origin.Y;
                            jr = AnswerStep.GetJoinedBounds(joinSteps, Config.OutputCanvasWidth, Config.OutputCanvasHeight);
                            rect2 = jr;
                        }
                        joinedId.Sort();

                        Rectangle drawRect = new Rectangle();
                        drawRect.Stroke = Brushes.Red;
                        drawRect.StrokeThickness = 3;
                        drawRect.SetValue(Canvas.LeftProperty, jr.Left + canvasLeftOffset);
                        drawRect.SetValue(Canvas.TopProperty, jr.Top + canvasTopOffset);
                        drawRect.Width = jr.Width;
                        drawRect.Height = jr.Height;
                        graphCanvas.Children.Add(drawRect);

                        // draw joined IDs
                        StringBuilder idtxt = new StringBuilder("Joined: ");
                        foreach (int id in joinedId)
                        {
                            idtxt.Append(id.ToString("D2") + ", ");
                        }
                        idtxt.Length -= 2;
                        ContentControl idtxtContent = new ContentControl();
                        Canvas.SetLeft(idtxtContent, jr.Left + canvasLeftOffset);
                        Canvas.SetTop(idtxtContent, jr.Bottom + canvasTopOffset);
                        TextBlock idtxtBlock = new TextBlock();
                        idtxtBlock.Text = idtxt.ToString();
                        idtxtBlock.Foreground = Brushes.Red;
                        idtxtBlock.FontSize = 16;
                        idtxtContent.Content = idtxtBlock;
                        graphCanvas.Children.Add(idtxtContent);
                    }

                    // draw matching connector
                    Line l = new Line();
                    l.Stroke = Brushes.Red;
                    l.StrokeThickness = 2;
                    l.X1 = (rect1.TopRight.X + rect1.BottomRight.X) / 2.0;
                    l.Y1 = (rect1.TopRight.Y + rect1.BottomRight.Y) / 2.0;
                    l.X2 = (rect2.TopLeft.X + rect2.BottomLeft.X) / 2.0 + g2Origin.X;
                    l.Y2 = (rect2.TopLeft.Y + rect2.BottomLeft.Y) / 2.0 + g2Origin.Y;
                    l.HorizontalAlignment = HorizontalAlignment.Left;
                    l.VerticalAlignment = VerticalAlignment.Center;
                    graphCanvas.Children.Add(l);

                    // draw distance score
                    ContentControl txtContent = new ContentControl();
                    Canvas.SetLeft(txtContent, (l.X1 + l.X2) / 2.0);
                    Canvas.SetTop(txtContent, (l.Y1 + l.Y2) / 2.0);
                    TextBlock tb = new TextBlock();
                    tb.Text = matchings[i][2].ToString();
                    tb.Foreground = Brushes.Red;
                    tb.FontSize = 16;
                    txtContent.Content = tb;
                    graphCanvas.Children.Add(txtContent);
                }
            }

            // similarity score
            ContentControl totalDistanceContent = new ContentControl();
            Canvas.SetLeft(totalDistanceContent, 0.0);
            Canvas.SetTop(totalDistanceContent, 0.0);
            TextBlock txtBlock = new TextBlock();
            txtBlock.Text = ((int)(matchingResult.Distance * 1000.0)).ToString();
            txtBlock.Foreground = Brushes.Red;
            txtBlock.FontSize = 18;
            totalDistanceContent.Content = txtBlock;
            graphCanvas.Children.Add(totalDistanceContent);
        }
        /// <summary>
        /// Calculate answer step distance.
        /// DP matching
        /// </summary>
        /// <param name="group1"></param>
        /// <param name="group2"></param>
        /// <returns></returns>
        private DPMatchingResult CalcAnswerStepDistance(AnswerStep group1, AnswerStep group2)
        {
            // parameters ---------------------
            // Gap cost for DP matching
            double gapCost = 100.0;
            // --------------------------------

            List<AnalysisPenStroke> strokes1 = group1.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, true);
            List<AnalysisPenStroke> strokes2 = group2.GetNormalizedStrokes(AnswerSheetAnalyzer.NormarizingHeight, true);

            if (group1.Strokes.Count == 0 && group2.Strokes.Count == 0)
            {
                return new DPMatchingResult(0.0, new List<int[]>());
            }
            if (group1.Strokes.Count == 0)
            {
                return new DPMatchingResult(1.0 / ((double)strokes2.Count * gapCost), new List<int[]>());
            }
            if (group2.Strokes.Count == 0)
            {
                return new DPMatchingResult(1.0 / ((double)strokes1.Count * gapCost), new List<int[]>());
            }

            double[,] m = new double[strokes1.Count, strokes2.Count];
            // from which direction?
            // 0->match, -1->fromStroke1, -2->fromStroke2
            int[,] from = new int[strokes1.Count, strokes2.Count];
            for (int i = 0, ilen = strokes1.Count; i < ilen; i++)
            {
                m[i, 0] = (double)i * gapCost;
                from[i, 0] = -1;
            }
            for (int i = 0, ilen = strokes2.Count; i < ilen; i++)
            {
                m[0, i] = (double)i * gapCost;
                from[0, i] = -2;
            }
            from[0, 0] = 0;
            for (int i = 1, ilen = strokes1.Count; i < ilen; i++)
            {
                for (int k = 1, klen = strokes2.Count; k < klen; k++)
                {
                    double d = 0.0;
                    d = CalcStrokeDistance(strokes1[i], strokes2[k]).Distance;
                    //Console.WriteLine("Stroke Sequence Distance: " + d);
                    //double[] scores = new double[] { m[i - 1, k] + gapCost, m[i - 1, k - 1] + d, m[i, k - 1] + gapCost };
                    //Console.WriteLine("Stroke Sequence Distance Candidates: " + scores[0] + "," + scores[1] + "," + scores[2]);
                    //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 = strokes1.Count - 1;
            int g2Pos = strokes2.Count - 1;
            for (int i = strokes1.Count - 1, k = strokes2.Count - 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--;
                        break;
                }
                matchingList.Add(match);
            }
            matchingList.Reverse();
            DPMatchingResult res = new DPMatchingResult(m[strokes1.Count - 1, strokes2.Count - 1] / (Math.Min(strokes1.Count, strokes2.Count)), matchingList);

            return res;
            //return m[strokes1.Count - 1, strokes2.Count - 1] / (Math.Min(strokes1.Count, strokes2.Count));
        }
        /// <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>
        /// Calculate answer process similarity
        /// </summary>
        /// <param name="group1"></param>
        /// <param name="group2"></param>
        /// <returns>Matching result</returns>
        public DPMatchingResult CalcAnswerProcessSimilarity(List<AnswerStep> group1, List<AnswerStep> group2)
        {
            // parameters----------------
            // dynamic programming matching
            double gapCost = 40.0;
            // weight of join cost. 1: without JOIN penalty, 0: with JOIN penalty same as gapCost
            double joinCostWeight = 1.0;
            // ---------------------------

            if (group1.Count == 0 && group2.Count == 0)
            {
                DPMatchingResult result = new DPMatchingResult(0.0, new List<int[]>());
                return result;
            }
            if (group1.Count == 0)
            {
                DPMatchingResult result = new DPMatchingResult(1.0 / ((double)group2.Count * gapCost), new List<int[]>());
                return result;
            }
            if (group2.Count == 0)
            {
                DPMatchingResult result = new DPMatchingResult(1.0 / ((double)group1.Count * gapCost), new List<int[]>());
                return result;
            }

            // DP matching
            double[,] m = new double[group1.Count + 1, group2.Count + 1];
            // from which direction?
            // 0-n->Match, -1->fromGRP1Skip, -2->fromGRP2Skip
            int[,] from = new int[group1.Count + 1, group2.Count + 1];
            double[,] dm = new double[group1.Count, group2.Count];

            m[0, 0] = 0.0;
            // implememtation of corner case
            // entry point for part matching
            for (int i = 1, ilen = group1.Count + 1; i < ilen; i++)
            {
                m[i, 0] = (double)i * gapCost;
                from[i, 0] = -1;
            }
            for (int i = 0, ilen = group2.Count + 1; i < ilen; i++)
            {
                m[0, i] = (double)i * gapCost;
                from[0, i] = -2;
            }
            from[0, 0] = 0;

            for (int i = 1, ilen = group1.Count + 1; i < ilen; i++)
            {
                for (int k = 1, klen = group2.Count + 1; k < klen; k++)
                {
                    double distance_match = double.MaxValue;

                    if (from[i - 1, k - 1] >= 0)
                    {
                        // cannot join
                        distance_match = CalcAnswerStepDistance(group1[i - 1], group2[k - 1]).Distance;
                        from[i, k] = 0;
                    }
                    else if (from[i - 1, k - 1] == -1)
                    {
                        // can join at group 1
                        for (int n = 0; from[i - 1 - n, k - 1] == -1; n++)
                        {
                            // n-> number of joins
                            AnswerStep join_group = new AnswerStep(i - 1, group1[i - 1].Strokes);
                            for (int o = 1; o <= n; o++)
                            {
                                join_group.JoinStrokes(group1[i - 1 - o]);
                            }
                            double distance_tmp = CalcAnswerStepDistance(join_group, group2[k - 1]).Distance;
                            if (distance_tmp < distance_match)
                            {
                                distance_match = distance_tmp;
                                from[i, k] = n;
                            }
                        }
                    }
                    else if (from[i - 1, k - 1] == -2)
                    {
                        // can join at group 2
                        for (int n = 0; from[i - 1, k - 1 - n] == -2; n++)
                        {
                            // n-> number of joins
                            AnswerStep join_group = new AnswerStep(k - 1, group2[k - 1].Strokes);
                            for (int o = 1; o <= n; o++)
                            {
                                join_group.JoinStrokes(group2[k - 1 - o]);
                            }
                            double distance_tmp = CalcAnswerStepDistance(group1[i - 1], join_group).Distance;
                            if (distance_tmp < distance_match)
                            {
                                distance_match = distance_tmp;
                                from[i, k] = n;
                            }
                        }
                    }
                    dm[i - 1, k - 1] = distance_match;
                    //Console.WriteLine("AnswerStep Distance: " + distance_match.ToString());

                    double from_grp1 = m[i - 1, k] + gapCost;
                    double from_grp2 = m[i, k - 1] + gapCost;
                    double from_match = m[i - 1, k - 1] + distance_match - ((double)from[i, k] * gapCost * joinCostWeight);
                    if (1 < i && 1 < k)
                    {
                        // can match, skip
                        ;
                    }
                    else if (i == 1 && k == 1)
                    {
                        // can only match
                        from_grp1 = double.MaxValue;
                        from_grp2 = double.MaxValue;
                    }
                    else if (k == 1)
                    {
                        // can match and group 1 skip
                        from_grp2 = double.MaxValue;
                    }
                    else if (i == 1)
                    {
                        // can match and group 2 skip
                        from_grp1 = double.MaxValue;
                    }

                    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 = group1.Count - 1;
            int g2pos = group2.Count - 1;
            //int matchCnt = 0;
            for (int i = group1.Count, k = group2.Count; 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
                        match = new int[4] { g1pos, g2pos, (int)(dm[g1pos, g2pos] * 100.0), from[i,k] };
                        i--;
                        k--;
                        g1pos--;
                        g2pos--;
                        //matchCnt++;
                        break;
                }
                matchingList.Add(match);
            }
            matchingList.Reverse();

            //DPMatchingResult res = new DPMatchingResult(m[group1.Count, group2.Count] / (group1.Count + group2.Count + 1.0), matchingList);
            DPMatchingResult res = new DPMatchingResult(m[group1.Count, group2.Count] / (Math.Min(group1.Count, group2.Count) + 1.0), matchingList);
            return res;
        }