/** \file
 * \brief Hough Transform
 *
 * See Copyright Notice in im_lib.h
 */

#include <im.h>
#include <im_util.h>
#include <im_complex.h>
#include <im_convert.h>
#include <im_counter.h>

#include "im_process_glo.h"
#include "im_process_counter.h"

#include <stdlib.h>
#include <memory.h>


#ifndef M_PI
#define M_PI    3.14159265358979323846
#endif

static double *costab=NULL, *sintab=NULL;

static int hgAbs(int x)
{
  return x < 0? -x: x;
}

typedef struct _point
{
  int rho, theta, count;
} point;

typedef struct _listnode
{
  struct _listnode *next;
  point pt;
} listnode;

static listnode* listnew(point *pt)
{
  listnode* node = (listnode*)malloc(sizeof(listnode));
  node->next = NULL;
  node->pt = *pt;
  return node;
}

static listnode* listadd(listnode* node, point *pt)
{
  node->next = listnew(pt);
  return node->next;
}

/* minimum angle to match similar angles */
#define THETA_DELTA1   0.05  /* radians */
#define THETA_DELTA2   3     /* degrees */

static int ptNear(point* pt1, point* pt2, int rho_delta)
{
  int theta_diff = hgAbs(pt1->theta - pt2->theta);
  if ((hgAbs(pt1->rho - pt2->rho) < rho_delta && theta_diff < THETA_DELTA2) ||
      (hgAbs(pt1->rho + pt2->rho) < rho_delta && 180-theta_diff < THETA_DELTA2))
  {
    if (pt2->count > pt1->count)
      return 2;   /* replace the line */
    else
      return 1;
  }
  else
    return 0;
}

static listnode* listadd_filtered(listnode* list, listnode* cur_node, point *pt, int rho_delta)
{
  int ret;
  listnode* lt = list;
  while (lt)
  {
    ret = ptNear(&lt->pt, pt, rho_delta);
    if (ret)
    {
      if (ret == 2)
        lt->pt = *pt;  /* replace the line */
      return cur_node;
    }
    lt = lt->next;
  }

  cur_node->next = listnew(pt);
  return cur_node->next;
}

/*C* Initial version from XITE

        houghLine
        $Id: im_houghline.cpp,v 1.4 2012-03-20 23:44:06 scuri Exp $
        Copyright 1990, Blab, UiO
        Image processing lab, Department of Informatics
        University of Oslo
        E-mail: blab@ifi.uio.no
________________________________________________________________

  houghLine - Hough transform for line detection

  Description:
    Performs a Hough transform to detect lines. Every band in the
    input image 'inimage' is transformed to a two dimensional
    Hough space, a (theta, rho) space.

		After creating the transform, the Hough space may be searched
		for local maxima. Within each band, only the largest local
		maximum (maxima) within a 'ws'x'ws' area is registered.
		Besides, only maxima with number of updates above a limit
		given by the ul option are used.

		updateLimit determines the minimum number of updates for a maximum
		to be used. The minimum number is determined from 'updateLimit'
		and the size of the hough space image:
		| updateLimit * MAX(horizontal size, vertical size)
		Default: 0.1.

    All pixels above zero in the 'input' band are
		transformed to (theta,rho) space in the 'output'
		band. The 'input' band may have any size, while
		the 'output' band currently must be at least
		| xsize: 180
		| ysize: 2 * sqrt(inputXsize*inputXsize +
		|             inputYsize*inputYsize) + 1

		Notice that band x coordinates 1..180 correspond
		to angles theta = 0 .. 179, and y coordinates
		1..YSIZE correspond to rho = -(ysize/2) .. ysize/2.

  Restrictions:
    'input' must have pixel type imbyte.
    'output' must have pixel type int.

  Author:		Tor Lønnestad, BLAB, Ifi, UiO
*/

static void init_sin_cos_table(int thetamax)
{
  int theta;

  costab = (double*)malloc(thetamax*sizeof(double));
  sintab = (double*)malloc(thetamax*sizeof(double));

  for (theta = 0; theta < thetamax; theta++)
  {
    double th = (M_PI * theta) / (double)thetamax;
    costab[theta] = cos(th);
    sintab[theta] = sin(th);
  }
}

static int houghLine(const imImage* input, imImage* output, int counter)
{
  int ixsize, iysize, ixhalf, iyhalf, thetamax, x, y, rho, theta, rhomax;
  imbyte *input_map = (imbyte*)input->data[0];
  int *output_map = (int*)output->data[0];

  ixsize = input->width;
  iysize = input->height;
  ixhalf = ixsize/2;
  iyhalf = iysize/2;

  thetamax = output->width;   /* theta max = 180 */
  rhomax = output->height/2;  /* rho shift to 0, -rmax <= r <= +rmax */

  init_sin_cos_table(thetamax);

  IM_INT_PROCESSING;

#ifdef _OPENMP
#pragma omp parallel for if (IM_OMP_MINCOUNT(input->count))
#endif
  for (y=0; y < iysize; y++)
  {
#ifdef _OPENMP
#pragma omp flush (processing)
#endif
    IM_BEGIN_PROCESSING;

    for (x = 0; x < ixsize; x++)
    {
      if (input_map[y*ixsize + x])
      {
        for (theta=0; theta < thetamax; theta++)
        {
          rho = imRound((x-ixhalf)*costab[theta] + (y-iyhalf)*sintab[theta]);
          if (rho > rhomax) continue;
          if (rho < -rhomax) continue;
          output_map[(rho+rhomax)*thetamax + theta]++;
	      }
      }
    }

    IM_COUNT_PROCESSING;
#ifdef _OPENMP
#pragma omp flush (processing)
#endif
    IM_END_PROCESSING;
  }

  return processing;
}

static listnode* findMaxima(const imImage* hough_points, int *line_count, const imImage* hough)
{
  int x, y, xsize, ysize, rhomax, offset, rho_delta = 0;
  listnode* maxima = NULL, *cur_node = NULL;
  point pt;
  imbyte *map = (imbyte*)hough_points->data[0];
  int *hough_map = NULL;

  xsize = hough_points->width;   /* X = theta */
  ysize = hough_points->height;  /* Y = rho   */
  rhomax = ysize/2;
  
  if (hough)
  {
    hough_map = (int*)hough->data[0];
    rho_delta = (int)(rhomax*tan(THETA_DELTA1));
  }

  for (y=0; y < ysize; y++)
  {
    for (x=0; x < xsize; x++)
    {
      offset = y*xsize + x;

      if (map[offset])
      {
        pt.theta = x;
        pt.rho = y-rhomax;

        if (!maxima)
        {
          cur_node = maxima = listnew(&pt);
          (*line_count)++;
        }
        else
        {
          if (hough_map)
          {
            listnode* old_node = cur_node;
            pt.count = hough_map[offset];
            cur_node = listadd_filtered(maxima, cur_node, &pt, rho_delta);
            if (cur_node != old_node)
              (*line_count)++;
          }
          else
          {
            cur_node = listadd(cur_node, &pt);
            (*line_count)++;
          }
        }
	    }
    }
  }

  return maxima;
}

#define SWAPINT(a, b) {int t = a; a = b; b = t; }

static void drawLine(imImage* image, int theta, int rho)
{
  int xsize, ysize, xstart, xstop, ystart, ystop, xhalf, yhalf;
  double a, b;
  imbyte *map = (imbyte*)image->data[0];  // gray or red plane

  xsize = image->width;
  ysize = image->height;
  xhalf = xsize/2;
  yhalf = ysize/2;

  if (theta == 0)  /* vertical line */
  {
    int y;
    if (rho+xhalf < 0 || rho+xhalf > xsize-1) return;
    for (y=0; y < ysize; y++)
      map[y*xsize + rho+xhalf]=254;

    return;
  }

  if (theta == 90)  /* horizontal line */
  {
    int x;
    if (rho+yhalf < 0 || rho+yhalf > ysize-1) return;
    for (x=0; x < xsize; x++)
      map[(rho+yhalf)*xsize + x]=254;

    return;
  }

  a = -costab[theta]/sintab[theta];
  b = (rho + xhalf*costab[theta] + yhalf*sintab[theta])/sintab[theta];

  {
    int x[2];
    int y[2];
    int c = 0;
    int y1 = imRound(b);              /* x = 0 */
    int y2 = imRound(a*(xsize-1)+b);  /* x = xsize-1 */

    int x1 = imRound(-b/a);           /* y = 0 */
    int x2 = imRound((ysize-1-b)/a);  /* y = ysize-1 */

    if (y1 >= 0 && y1 < ysize)
    {
      y[c] = y1;
      x[c] = 0;
      c++;
    }

    if (y2 >= 0 && y2 < ysize)
    {
      y[c] = y2;
      x[c] = xsize-1;
      c++;
    }

    if (c < 2 && x1 >= 0 && x1 < xsize)
    {
      x[c] = x1;
      y[c] = 0;
      c++;
    }

    if (c < 2 && x2 >= 0 && x2 < xsize)
    {
      x[c] = x2;
      y[c] = ysize-1;
      c++;
    }

    if (c < 2) return;

    ystart = y[0];
    xstart = x[0];
    ystop = y[1];
    xstop = x[1];
  }

  {
    int x, y;
    if (45 <= theta && theta <= 135)
    {
      if (xstart > xstop)
        SWAPINT(xstart, xstop);

      for (x=xstart; x <= xstop; x++)
      {
        y = imRound(a*x + b);
        if (y < 0) continue;
        if (y > ysize-1) continue;
        map[y*xsize + x]=254;
      }
    }
    else
    {
      if (ystart > ystop)
        SWAPINT(ystart, ystop);

      for (y=ystart; y <= ystop; y++)
      {
        x = imRound((y-b)/a);
        if (x < 0) continue;
        if (x > xsize-1) continue;
        map[y*xsize + x]=254;
      }
    }
  }
}

int imProcessHoughLines(const imImage* src_image, imImage *dst_image)
{
  int counter = imProcessCounterBegin("HoughLines");
  imCounterTotal(counter, src_image->height, "Processing...");

  int ret = houghLine(src_image, dst_image, counter);

  free(costab); costab = NULL;
  free(sintab); sintab = NULL;

  imProcessCounterEnd(counter);

  return ret;
}

static void DrawPoints(imImage *image, listnode* maxima)
{
  listnode* cur_node;
  while (maxima)
  {
    cur_node = maxima;
    drawLine(image, cur_node->pt.theta, cur_node->pt.rho);
    maxima = cur_node->next;
    free(cur_node);
  }
}

static void ReplaceColor(imImage* image)
{
  int i;
  imbyte* map = (imbyte*)image->data[0];  // gray or red plane

  if (image->color_space == IM_GRAY)
  {
    image->color_space = IM_MAP;
    image->palette[254] = imColorEncode(255, 0, 0);
  }

  for (i = 0; i < image->count; i++)
  {
    if (map[i] == 254)
      map[i] = 255;
  }
}

int imProcessHoughLinesDraw(const imImage* src_image, const imImage *hough, const imImage *hough_points, imImage *dst_image)
{
  int line_count = 0;

  if (src_image != dst_image)
    imImageCopyData(src_image, dst_image);

  listnode* maxima = findMaxima(hough_points, &line_count, hough);

  ReplaceColor(dst_image);

  init_sin_cos_table(180);

  DrawPoints(dst_image, maxima);

  free(costab); costab = NULL;
  free(sintab); sintab = NULL;

  return line_count;
}

