#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <sys/time.h>

// #include <omp.h>

#define MAX_NBCITIES 22

typedef int DTab_t[MAX_NBCITIES][MAX_NBCITIES];
typedef int path_t[MAX_NBCITIES];

/* time measurement macro, returns a value in µseconds */
#define TIME_DIFF(t1, t2) \
  ((t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec))

/* last minimum found */
int minimum = INT_MAX;

/* array of distances */
DTab_t distance;

/* number of cities */
int nbCities;

/* nested parallelism level */
int grain;

#define MAXX 100
#define MAXY 100
typedef struct
{
  int x, y;
} coor_t;

typedef coor_t coortab_t[MAX_NBCITIES];

void initialization(int Argc, char *Argv[])
{

  if (Argc < 4 || Argc > 5)
  {
    fprintf(stderr, "Usage: %s  <nbCities> <seed> [grain] <kernel>\n", Argv[0]);
    exit(1);
  }

 grain = (Argc == 5) ? atoi(Argv[3]) : 0;


  /* initialization of distances */
  /* cities are randomly positioned on a MAXX x MAXY map */

  coortab_t cities;

  int i, j;
  int dx, dy;

  nbCities = atoi(Argv[1]);
  if (nbCities > MAX_NBCITIES)
  {
    fprintf(stderr, "Too many cities, increment MAX_NBCITIES\n");
    exit(1);
  }

  srand(atoi(Argv[2]));

  for (i = 0; i < nbCities; i++)
  {
    cities[i].x = rand() % MAXX;
    cities[i].y = rand() % MAXY;
  }

  for (i = 0; i < nbCities; i++)
    for (j = 0; j < nbCities; j++)
    {
      dx = cities[i].x - cities[j].x;
      dy = cities[i].y - cities[j].y;
      distance[i][j] = (int)sqrt((double)((dx * dx) + (dy * dy)));
    }
}

inline int present(int city, int mask)
{
  return mask & (1 << city);
}

void check_minimum(int lg, path_t path)
{
  if (lg + distance[0][path[nbCities - 1]] < minimum)
  {
    minimum = lg + distance[0][path[nbCities - 1]];
    printf("%3d :", minimum);
    for (int i = 0; i < nbCities; i++)
      printf("%2d ", path[i]);
    printf("\n");
  }
}

void tsp_seq(int step, int lg, path_t path, int mask)
{
  int ici, dist;

  if (step == nbCities)
    check_minimum(lg, path);
  else
  {
    ici = path[step - 1];

    for (int i = 1; i < nbCities; i++)
    {
      if (!present(i, mask))
      {
        path[step] = i;
        dist = distance[ici][i];
        tsp_seq(step + 1, lg + dist, path, mask | (1 << i));
      }
    }
  }
}

void tsp_ompfor(int step, int lg, path_t path, int mask)
{
  printf(" To do\n") ; exit(1);
}

int main(int argc, char **argv)
{
  unsigned long time;
  struct timeval t1, t2;
  path_t path;

  initialization(argc, argv);

  printf("nbCities = %3d - grain %d \n", nbCities, grain);

  //omp_set_max_active_levels(grain);

  gettimeofday(&t1, NULL);

  path[0] = 0;

  if (!strcmp(argv[argc-1],"seq")) 
    tsp_seq(1, 0, path, 1);
  else if (!strcmp(argv[argc-1],"ompfor")) 
    tsp_ompfor(1, 0, path, 1);
  else
  {
      printf("unknown kernel\n");
      exit(1);
  }
      
  gettimeofday(&t2, NULL);

  time = TIME_DIFF(t1, t2);
  fprintf(stderr, "%ld.%03ld\n", time / 1000, time % 1000);

  return 0;
}
