#region Using directives

using System;
using System.Collections;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Math;
using OpenTK.Input;
using OpenTK.Platform;

#endregion

namespace DataProcessing
{
    public class HoughTransformation
    {
        #region variable definitions

        //kolekce bodu pro zpracovani
        public ArrayList vertices;
        //kolekce detekovanych car
        public ArrayList lines = new ArrayList();

        // Nastaveni kvality vypoctu
        private int stepsPerDegree;
        private int houghWidth;
        private double thetaStep;
        private int houghRatio;
        private float maxDistance;
        private int minPoints;

        // predvypocitane hodnoty sinu a cosinu
        private double[] sinMap;
        private double[] cosMap;
        // Houghova mapa
        private Bin[,] houghMap;        

        // sirka a vyska zpracovavaneho "obrazu"
        private int width;
        private int height;
        private int transformationRow;

        public int HoughRatio
        {
            get { return houghRatio; }
            set { houghRatio = value; } 
        }

        public float MaxDistance
        {
            get { return maxDistance; }
            set { maxDistance = value; } 
        }

        public int MinPoints
        {
            get { return minPoints; }
            set { minPoints = value; } 
        }

        //podle uhloveho kroku se predvypocitaji hodnoty sinu a cosinu
        public int StepsPerDegree
        {
            get { return stepsPerDegree; }
            set
            {
                stepsPerDegree = Math.Max(1, Math.Min(60, value));
                houghWidth = (int)(180 / stepsPerDegree);
                thetaStep = Math.PI / houghWidth;

                // precalculate Sine and Cosine values
                sinMap = new double[houghWidth];
                cosMap = new double[houghWidth];

                for (int i = 0; i < houghWidth; i++)
                {
                    sinMap[i] = Math.Sin(i * thetaStep);
                    cosMap[i] = Math.Cos(i * thetaStep);
                }
            }
        }

        #endregion define variables

        //trida jednoho policka houghovy mapy 
        class Bin
        {
            public ArrayList binVertices = new ArrayList();
        }

        //konstruktor
        public HoughTransformation(int Width, int Height)//, int Ratio, float newDegree, float distance, int points)
        {
            vertices = new ArrayList();
            width = Width;
            height = Height;
        }        

        // samotna Houghova transformace
        public ArrayList Transformation(int row, int diffX, int diffZ)
        {
            transformationRow = row;

            int halfWidth = width / 2;
            int halfHeight = height / 2;

            // vypocet velikosti Houghovy mapy
            int halfHoughHeight = (int)Math.Sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
            int houghHeight = halfHoughHeight * 2;

            //houghova mapa
            houghMap = new Bin[houghHeight / houghRatio, houghWidth];

            //pro kazdy bod se otestuje patricne mnoztvi primek
            foreach (Vertex vertex in vertices)
            {
                int X = (int)vertex.X - diffX;
                int Z = (int)vertex.Z - diffZ;

                for (int theta = 0; theta < houghWidth; theta++)
                {
                    int radius = (int)Math.Round((((X * cosMap[theta] + Z * sinMap[theta])) / houghRatio), 0, MidpointRounding.AwayFromZero);

                    if ((radius < 0) || (radius >= houghHeight / houghRatio))
                        continue;

                    Bin tempBin = (Bin)houghMap[radius, theta];
                    if (tempBin == null)
                    {
                        tempBin = new Bin();
                    }
                    tempBin.binVertices.Add(vertex);
                    houghMap[radius, theta] = tempBin;
                }

            }
            
            //vybereme cary z mapy
            CollectLines();
            return lines;
        } 

        //fce pro zpracovani car z Houghovy mapy
        private void CollectLines()
        {
            lines.Clear();
            Line tempLine = null;

            //prochazime celou mapu
            for (int i = 0; i < houghMap.GetLength(0); i++)
            {
                for (int j = 0; j < houghMap.GetLength(1); j++)
                {
                    Bin tempBin = (Bin)houghMap[i, j];

                    if (tempBin == null)
                        continue;

                    //pokud je v policku min nez pozadovanych bodu deme dal
                    if (tempBin.binVertices.Count < minPoints)
                        continue;

                    //vezmeme prvni bod v policku mapy a otestujeme vzdalenost k dalsimu atd.
                    //pokud je vzdalenost vetsi nez dovolena usecku ukoncime
                    //pokud nema pocet pozadovanych bodu tak ji vypustime, jinak pridame do kolekce usecek a pokracujem dale
                    for (int k = 0; k < (tempBin.binVertices.Count - 1); k++)
                    {
                        if (tempLine == null)
                        {
                            tempLine = new Line();
                            tempLine.Theta = j;
                            tempLine.Radius = i;                            
                            tempLine.StartPoint = (Vertex)tempBin.binVertices[k];
                            tempLine.Row = transformationRow;
                            tempLine.PointsCount++;
                        }

                        float distance = Calculate.VertexDistance2((Vertex)tempBin.binVertices[k], (Vertex)tempBin.binVertices[k + 1]);

                        if (distance > maxDistance)
                        {
                            if (tempLine.PointsCount >= minPoints)
                                lines.Add(tempLine);

                            tempLine = null;
                        }
                        else
                        {
                            tempLine.PointsCount++;
                            tempLine.EndPoint = (Vertex)tempBin.binVertices[k + 1];
                        }
                    }

                    if (tempLine != null)
                    {
                        if (tempLine.PointsCount >= minPoints)
                            lines.Add(tempLine);

                        tempLine = null;
                    }
                }
            }
        }
    }
}
