using System.Collections.Generic;
using System.Reflection;
using System;
using System.Windows.Media.Imaging;
using SVM;
using System.IO;
using System.Diagnostics;

namespace FindIT
{
    public delegate List<CPoint[]> PrepareAdaptiveOptimizer(WriteableBitmap img, int[] bt);
    /// <summary>
    /// Класс для работы с machine learning
    /// </summary>
    public static class ML
    {
        /// <summary>
        /// Оптимизаторы
        /// </summary>
        private static Dictionary<Model, AdaptivePriorityOptimizer[]> opt;

        private static int PARALLEL_DEGREE;

        private static PrepareAdaptiveOptimizer HARRIS_PREPARE = (img, bt) =>
        {
             List<CPoint> h = img.Harris(bt);
             List<CPoint[]> data = new List<CPoint[]>(h.Count * h.Count);

             foreach (CPoint p in h)
             {
                 // if (data.Count > 200) break;
                 foreach (CPoint p2 in h)
                 {
                     // if (data.Count > 200) break;
                     if (p == p2) continue;
                     int wa = p2.X - p.X;
                     int ha = p2.Y - p.Y;
                     if (wa < (double)Functions.ImageWidth / 2 || ha < (double)Functions.ImageHeight / 2) continue;
                     if (p2.X > p.X && p2.Y > p.Y && 3 * (p2.X - p.X) > p2.Y - p.Y && 3 * (p2.Y - p.Y) > p2.X - p.X)
                     {
                         data.Add(new CPoint[] { p, p2 });
                     }
                 }
             }
         //   List<CPoint[]> data = new List<CPoint[]>(Functions.ImageHeight);

            int x = 0, y = 0;
            while (x < Functions.ImageWidth / 2 - Functions.ImageWidth / 10 && y < Functions.ImageHeight / 2 - Functions.ImageHeight / 10)
            {
                data.Add(new CPoint[] { new CPoint(x, y, img.Harris(bt, x, y)), new CPoint(Functions.ImageWidth - 1 - x, Functions.ImageHeight - 1 - y, img.Harris(bt, Functions.ImageWidth - 1 - x, Functions.ImageHeight - 1 - y)) });
                x += Functions.ImageWidth / 20;
                y += Functions.ImageHeight / 20;
            }

            //x = Functions.ImageWidth - 1;
            //y = Functions.ImageHeight - 1;
            //while (x > Functions.ImageWidth / 4 && y > Functions.ImageHeight / 4)
            //{
            //    data.Add(new CPoint[] { new CPoint(Functions.ImageWidth - x - 1, Functions.ImageHeight - y - 1, img.Harris(bt, Functions.ImageWidth - x - 1, Functions.ImageHeight - y - 1)), new CPoint(Functions.ImageWidth - 1, Functions.ImageHeight - 1, img.Harris(bt, Functions.ImageWidth - 1, Functions.ImageHeight - 1)) });
            //    data.Add(new CPoint[] { new CPoint(Functions.ImageWidth - x - 1, 0, img.Harris(bt, Functions.ImageWidth - x - 1, 0)), new CPoint(Functions.ImageWidth - 1, y, img.Harris(bt, Functions.ImageWidth - 1, y)) });
            //    data.Add(new CPoint[] { new CPoint(0, 0, img.Harris(bt, 0, 0)), new CPoint(x, y, img.Harris(bt, x, y)) });
            //    data.Add(new CPoint[] { new CPoint(0, Functions.ImageHeight-y-1, img.Harris(bt, 0, y)), new CPoint(x, Functions.ImageHeight - 1, img.Harris(bt, x, Functions.ImageHeight - 1)) });
            //    x -= Functions.ImageWidth / 20;
            //    y -= Functions.ImageHeight / 20;
            //}

            return data;
        };

        static ML()
        {
            opt = new Dictionary<Model, AdaptivePriorityOptimizer[]>();
            PARALLEL_DEGREE = 1;
        }

        /// <summary>
        /// Сделать из массива ноды для svm
        /// </summary>
        /// <param name="ar"></param>
        /// <returns></returns>
        public static Node[] GetNodes(double[] ar)
        {
            Node[] nodes = new Node[ar.Length];
            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i] = new Node();
                nodes[i].Index = i + 1;
                nodes[i].Value = ar[i];
            }
            return nodes;
        }

        /// <summary>
        /// Загружает SVM из файла
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static Model getSVMObject(Stream file)
        {
            AdaptivePriorityOptimizer[] ad = new AdaptivePriorityOptimizer[PARALLEL_DEGREE];
            for (int i = 0; i < ad.Length; i++)
            {
                ad[i] = new AdaptivePriorityOptimizer();
            }

            Model m = new Model();
            m.Parameter = new Parameter();
            file.ReadInt(); // svm_type
            file.ReadInt(); // kernel_type
            m.Parameter.SvmType = SvmType.C_SVC;
            m.Parameter.KernelType = KernelType.RBF;
            m.Parameter.Degree = file.ReadInt();
            m.Parameter.Gamma = file.ReadDouble();
            m.Parameter.Coefficient0 = file.ReadDouble();
            m.Parameter.CacheSize = file.ReadDouble();
            m.Parameter.EPS = file.ReadDouble();
            m.Parameter.C = file.ReadDouble();
            file.ReadInt(); // nr_weight
            m.Parameter.Nu = file.ReadDouble();
            m.Parameter.P = file.ReadDouble();
            m.Parameter.Shrinking = file.ReadInt() == 1;
            m.Parameter.Probability = file.ReadInt() == 1;
            double[] weight = new double[file.ReadInt()];
            for (int i = 0; i < weight.Length; i++)
            {
                weight[i] = file.ReadDouble();
            }
            int[] weight_labels = new int[file.ReadInt()];
            for (int i = 0; i < weight_labels.Length; i++)
            {
                weight_labels[i] = file.ReadInt();
            }
            for (int i = 0; i < weight_labels.Length; i++)
            {
                m.Parameter.Weights.Add(weight_labels[i], weight[i]);
            }

            m.NumberOfClasses = file.ReadInt();
            m.ClassLabels = new int[file.ReadInt()];
            m.SupportVectorCount = file.ReadInt();
            m.SupportVectors = new Node[m.SupportVectorCount][];
            for (int i = 0; i < m.SupportVectorCount; i++)
            {
                m.SupportVectors[i] = new Node[file.ReadInt()];
                for (int j = 0; j < m.SupportVectors[i].Length; j++)
                {
                    m.SupportVectors[i][j] = new Node(file.ReadInt(), file.ReadDouble());
                }
            }
            m.SupportVectorCoefficients = new double[file.ReadInt()][];
            for (int i = 0; i < m.SupportVectorCoefficients.Length; i++)
            {
                m.SupportVectorCoefficients[i] = new double[file.ReadInt()];
                for (int j = 0; j < m.SupportVectorCoefficients[i].Length; j++)
                {
                    m.SupportVectorCoefficients[i][j] = file.ReadDouble();
                }
            }
            m.Rho = new double[file.ReadInt()];
            for (int i = 0; i < m.Rho.Length; i++)
            {
                m.Rho[i] = file.ReadDouble();
            }
            int sv_indices = file.ReadInt();
            for (int i = 0; i < sv_indices; i++)
            {
                file.ReadInt(); // sv_indices
            }
            m.ClassLabels = new int[file.ReadInt()];
            for (int i = 0; i < m.ClassLabels.Length; i++)
            {
                m.ClassLabels[i] = file.ReadInt();
            }
            m.NumberOfSVPerClass = new int[file.ReadInt()];
            for (int i = 0; i < m.NumberOfSVPerClass.Length; i++)
            {
                m.NumberOfSVPerClass[i] = file.ReadInt();
            }
            m.PairwiseProbabilityA = new double[file.ReadInt()];
            for (int i = 0; i < m.PairwiseProbabilityA.Length; i++)
            {
                m.PairwiseProbabilityA[i] = file.ReadDouble();
            }
            m.PairwiseProbabilityB = new double[file.ReadInt()];
            for (int i = 0; i < m.PairwiseProbabilityB.Length; i++)
            {
                m.PairwiseProbabilityB[i] = file.ReadDouble();
            }
            opt.Add(m, ad);
            return m;
        }

        /// <summary>
        /// Распознать изображение BitmapImage
        /// </summary>
        /// <param name="svm"></param>
        /// <param name="img"></param>
        /// <returns></returns>
        public static Queue<RecognizedRectangle> RecognizeAll(this Model svm, WriteableBitmap img, int[] pix, out int n)
        {
            return RecognizeAll(new Model[] { svm }, img, pix, out n);
        }

        /// <summary>
        /// Харрисом
        /// </summary>
        /// <param name="svm"></param>
        /// <param name="img"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        public static Queue<RecognizedRectangle> RecognizeAll(this Model[] svm, WriteableBitmap img, int[] pix, out int n)
        {
            return svm.RecognizeAll(img, pix, out n, HARRIS_PREPARE);
        }

        /// <summary>
        /// Распознать изображение BitmapImage мультиклассификатором
        /// </summary>
        /// <param name="svm"></param>
        /// <param name="img"></param>
        /// <returns></returns>
        public static Queue<RecognizedRectangle> RecognizeAll(this Model[] svm, WriteableBitmap img, int[] pix, out int n, PrepareAdaptiveOptimizer pr)
        {
            int max_size = 1;
            Queue<RecognizedRectangle> l = new Queue<RecognizedRectangle>();
            int num = 0;
            int indx = 0;
            List<CPoint[]> points = pr(img, pix);
            foreach (CPoint[] p in points)
            {
                indx = (indx + 1) % opt[svm[0]].Length;
                for (int i = 0; i < svm.Length; i++)
                {
                    opt[svm[i]][indx].AddPoint(p[0], p[1]);
                }
            }
            // Debug.WriteLine("The count of harris point: " + points.Count);
            AdaptivePriorityOptimizer a2 = opt[svm[0]][0];
            img.HoG(0, 0, 0, 0);
            double d2 = 0;
            while (!a2.Empty())
            {
                if (l.Count >= max_size) break;
                CPoint p, p2;

                a2.GetPoints(out p, out p2);
                num++;

                d2 = -1;
                for (int md = 0; md < svm.Length; md++)
                {
                    double[] hf = img.HoG(p.X, p.Y, p2.X - p.X, p2.Y - p.Y, md);
                    Model svc = svm[md];
                    double key = 0;
                    if ((key = Prediction.Predict(svc, GetNodes(hf))) != d2 && d2 != -1)
                    {
                        d2 = 0;
                        break;
                    }
                    d2 = key;
                }

                if (d2 > 0)
                {
                    l.Enqueue(new RecognizedRectangle(p, p2, d2));
                    for (int i = 0; i < svm.Length; i++)
                        for (int j = 0; j < opt[svm[i]].Length; j++)
                            opt[svm[i]][j].Add(p.Color, p2.Color, p2.X - p.X, p2.Y - p.Y);
                }

            }
            //Debug.WriteLine("The count of rectangles: " + l.Count);
            n = num;
            // Debug.WriteLine("N: " + n);

            return l;
        }
    }

    /// <summary>
    /// Распознанная прямоугольная область
    /// </summary>
    public class RecognizedRectangle
    {
        public double prob;
        public int X, Y, Width, Height;

        public CPoint left, right;

        public RecognizedRectangle(int x, int y, int w, int h)
            : this(x, y, w, h, 1)
        {
        }

        public RecognizedRectangle(int x, int y, int w, int h, double prob)
        {
            this.X = x;
            this.Y = y;
            this.Width = w;
            this.Height = h;
            this.prob = prob;
        }

        public RecognizedRectangle(CPoint l, CPoint r, double prob)
        {
            left = l;
            right = r;

            this.X = l.X;
            this.Y = l.Y;
            this.Width = r.X - X;
            this.Height = r.Y - Y;
            this.prob = prob;
        }
    }
}
