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

namespace EdgeDetector1
{
    class EdgeDetector
    {
        private static double[] convolution(double[] input, double[] filter)
        {
            var result = new double[input.Length + filter.Length - 1];
            for (var i = 0; i < input.Length; i++)
            {
                for (var j = 0; j < filter.Length; j++)
                {
                    result[i + j] += input[i] * filter[j];
                }
            }
            return result;
        }

        private static int[] detectEdges(double[] data, int scale, int width, double threshold)
        {
            double[] g = getKernel(8, 3, 1);
            //padding
            double[] padData = new double[2 * g.Length + data.Length];
            for (int i = 0; i < g.Length; i++)
            {
                padData[i] = data[0];
                padData[padData.Length - 1 - i] = data[data.Length - 1];
            }
            data.CopyTo(padData, g.Length); //naplnim datami

            //konvolucia
            double[] fData = convolution(padData, g);

            //odstranenie paddingu
            double[] filtData = new double[data.Length]; //matica s normalnou velkostou
            int offset = (fData.Length - data.Length) / 2;
            for (int i = offset; i < filtData.Length + offset; i++)
            {
                filtData[i - offset] = fData[i];
            }

            // ziskam extremy
            double[] extremas = findLocalExtrema(filtData, threshold, scale);
            return getExtremasIndexes(extremas);
        }

        private static double[] findLocalExtrema(double[] data, double threshold, int scale)
        {
            double[] rdataMax = new double[data.Length];
            data.CopyTo(rdataMax, 0);
            double[] rdataMin = new double[data.Length];
            data.CopyTo(rdataMin, 0);
            double max = rdataMax.Max();
            double min = rdataMin.Min();
            for (int i = 0; i < rdataMax.Length; i++)
            {
                rdataMax[i] /= max; //znormuje data podla max
                rdataMin[i] /= min;
            }
            //sliding windows
            double[] winmax = new double[data.Length];
            double[] winmin = new double[data.Length];

            //priechod sliding window polom
            double[] tempwind = new double[scale * 2];
            int count = 0;
            for (int i = 0 + scale; i < data.Length - scale; i++)
            {
                for (int j = i - scale; j < i + scale; j++)
                {

                    tempwind[count] = rdataMax[j];
                    count++;
                }
                winmax[i] = tempwind.Max();
                count = 0;
            }
            for (int i = 0 + scale; i < data.Length - scale; i++)
            {
                for (int j = i - scale; j < i + scale; j++)
                {

                    tempwind[count] = rdataMin[j];
                    count++;
                }

                winmin[i] = tempwind.Max();
                count = 0;

            }
            //maxima a minima polia pre lokalne extremy
            double[] maxima = new double[data.Length];
            double[] minima = new double[data.Length];
            double[] extrema = new double[data.Length];

            //najde lokalne extremy
            for (int i = 0 + scale; i < data.Length - scale; i++)
            {
                maxima[i] = Convert.ToDouble((rdataMax[i] >= threshold) & (rdataMax[i] >= winmax[i]));
                minima[i] = Convert.ToDouble((rdataMin[i] >= threshold) & (rdataMin[i] >= winmin[i]));
                extrema[i] = maxima[i] + minima[i];
            }
            return extrema;
        }

        private static int[] getExtremasIndexes(double[] extremas)
        {
            //vrati indexy extremov ako List
            LinkedList<int> a = new LinkedList<int>();
            for (int i = 0; i < extremas.Length; i++)
            {
                if (extremas[i] != 0)
                {
                    a.AddLast(i);
                }
            }
            int[] b = new int[a.Count];
            a.CopyTo(b, 0);
            return b;
        }

        public static List<int> getEventLocIndex(double[] data)
        {
            //zavolam edgedetektor
            int[] hrany = detectEdges(data, 4, 3, 0.5); //0.1

            //orezem velkost dat po prvhu hranu -10
            int firstedgepos = hrany[0];
            double[] data2 = new double[firstedgepos - 10];
            for (int i = 0; i < data2.Length; i++)
            {
                data2[i] = data[i];
            }

            //detekcia hran v data2 (skrateny signal)
            int[] hrany2 = detectEdges(data2, 8, 3, 0.3);

            //umiestnim hranu na koniec signalu
            int[] hranycelk = new int[hrany2.Length + 1];
            hrany2.CopyTo(hranycelk, 0);
            hranycelk[hranycelk.Length - 1] = data2.Length;

            //zmenim na List, aby bol vystup podobny so SOR parserom
            List<int> hranyout = new List<int>(hranycelk);
            return hranyout;
        }

        public static double[] smooth(double[] data) //vyhladi hrany 
        {
            double[] kernel = getKernel(8, 3, 0);

            //padding
            double[] padData = new double[2 * kernel.Length + data.Length];
            for (int i = 0; i < kernel.Length; i++)
            {
                padData[i] = data[0];
                padData[padData.Length - 1 - i] = data[data.Length - 1];
            }
            data.CopyTo(padData, kernel.Length);

            double[] filtdata = convolution(padData, kernel);

            //odstranim padding
            double[] outdata = new double[data.Length]; //vystup po posunuti
            int offset = (filtdata.Length - data.Length) / 2;
            for (int i = offset; i < outdata.Length + offset; i++)
            {
                outdata[i - offset] = filtdata[i];
            }
            return outdata;
        }

        private static double[] getKernel(int scale, int width, int deriv)
        //deriv urcuje stupen derivacie gauss. filtra
        {
            int sigma = scale;
            //naplnenie pola range hodnotami 1 az dlzka jadra
            double[] range = new double[2 * width * sigma + 1];
            for (int i = 0; i < range.Length; i++)
            {
                range[i] = i + 1;
            }
            //ziskanie hodnoty v strede
            double center = range[((range.Length - 1) / 2)];

            //vypocet hodnot Gauss. jadra
            //vypocet kernel je dost zlozity, preto som rozdelil na kernel1,2 a 3
            double[] rangecentered = new double[range.Length];
            for (int i = 0; i < range.Length; i++)
            {
                rangecentered[i] = range[i] - center;
            }

            //kernel1
            double kernel1 = (1 / (sigma * Math.Sqrt(2 * Math.PI)));

            //kernel2
            double[] kernel2 = new double[range.Length];
            for (int i = 0; i < kernel2.Length; i++)
            {
                kernel2[i] = Math.Exp(-(rangecentered[i] * rangecentered[i]) / (2 * sigma * sigma));
            }

            //kernel3
            double[] kernel3 = new double[range.Length];
            for (int i = 0; i < kernel3.Length; i++)
            {
                kernel3[i] = (-rangecentered[i] / (sigma * sigma));
            }

            //kernel
            double[] kernel = new double[range.Length];
            if (deriv == 1)//prva derivacia gauss. filtra
            {
                for (int i = 0; i < kernel.Length; i++)
                {
                    kernel[i] = kernel2[i] * kernel3[i] * kernel1;
                }
            }
            else //nulta derivacia gauss. filtra
            {
                for (int i = 0; i < kernel.Length; i++)
                {
                    kernel[i] = kernel2[i] * kernel1;
                }

            }
            return kernel;
        }

        //public()




    }
}
