#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#include <math.h>
#include <sqlite3.h>

typedef struct Face {
  long double descriptor[128];
  int faceId;
  long double *distances;
  struct Face *next;
  struct Face *prev;
} Face;

char fileBuf[5000];
char pathBuf[1028];

Face *readFaceDescriptor(int id, char *path) {
  FILE *f;
  Face *pFace = (Face *)malloc(sizeof(Face));
  memset(pFace, 0, sizeof(Face));
  f = fopen(path, "r");
  if (!f) {
    free(pFace);
    return NULL;
  }
  size_t s = fread(fileBuf, 1, sizeof(fileBuf), f);
  fclose(f);

  char *p = fileBuf;
  fileBuf[s] = 0;
  while (*p && *p != '-' && *p != '+' && (*p < '0' || *p > '9')) {
    p++;
  }
  for (int i = 0; i < 128; i++) {
    char *start = p;
    while (*p && *p != ',' && *p != ']' && *p != ' ' && *p != '\n') {
      p++;
    }
    if (!*p) {
      break;
    }
    *p++ = 0;
    sscanf(start, "%Lf", &pFace->descriptor[i]);
  }

  pFace->faceId = id;
  pFace->next = pFace->prev = NULL;

  return pFace;
}

long double euclideanDistance(long double *a, long double *b) {
  long double sum = 0.0;
  for (int i = 0; i < 128; i++) {
    long double delta = a[i] - b[i];
    sum += delta * delta;
  }
  return sqrtl(sum);
}

int main(int argc, char *argv[]) {

  int len = 0;
  int i;
  Face *pChain = NULL;
  for (i = 0; i < 100; i++) {
    sprintf(pathBuf, "%s/face-data/%d", argv[1], i);
    DIR *faceDir = opendir(pathBuf);
    fprintf(stderr, "Reading %s...\n", pathBuf);

    if (!faceDir) {
      fprintf(stderr, "Can not open %s\n", pathBuf);
      continue;
    } 

    struct dirent *ent;
    while ((ent = readdir(faceDir)) != NULL) {
      if (strstr(ent->d_name, ".json") == NULL) {
        continue;
      }
      int id = 0;
      char *p = ent->d_name;
      while (*p && *p != '-') {
        id *= 10;
        id += *p - '0';
        p++;
      }
      char path[1028*2];
      sprintf(path, "%s/%s", pathBuf, ent->d_name);
      Face *pFace = readFaceDescriptor(id, path);
      if (!pFace) {
        continue;
      }
      len++;
      if (len % 1000 == 0) {
        fprintf(stderr, "...read %d...\n", len);
      }
      if (pChain) {
        pFace->next = pChain;
      }
      pChain = pFace;
    }
    closedir(faceDir);
  }

  fprintf(stderr, "Read %d face descriptors...\n", len);

  /* Allocate storage for all distances */
  Face *pLink = pChain;
  while (pLink) {
    pLink->distances = (long double *)malloc(sizeof(long double) * len);
    pLink = pLink->next;
  }

  sqlite3 *db;

  int rc = sqlite3_open("db/photos.db", &db);
  if (rc != SQLITE_OK) {
    fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    return 1;
  }

  fprintf(stderr, "DB opened.");

  char *err_msg = NULL;
  char *sql = 
    "DELETE FROM facedistances;"
    "BEGIN TRANSACTION;";

  rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
  if (rc != SQLITE_OK ) {
    fprintf(stderr, "SQL error: %s\n", err_msg);
    sqlite3_free(err_msg);        
    sqlite3_close(db);
    return 1;
  } 

  fprintf(stderr, "facedistances deleted and transaction started.\n");

  char sqlBuf[1024];
  pLink = pChain;
  int sourceIndex = 0, lines = 0;
  while (pLink) {
    int targetIndex = 0;
    Face *pTarget = pChain;
    while (pTarget) {
      if (targetIndex == sourceIndex) {
        pLink->distances[targetIndex] = 0.0;
        pTarget->distances[sourceIndex] = 0.0;
      } else {
        if (pLink->distances[targetIndex] == 0.0) {
          pLink->distances[targetIndex] = 
            pTarget->distances[sourceIndex] = euclideanDistance(pLink->descriptor, pTarget->descriptor);
          if (pLink->distances[targetIndex] < 0.5) {
            sprintf(sqlBuf, "INSERT INTO facedistances (face1Id,face2Id,distance) VALUES (%d,%d,%Lf);", 
              ((pLink->faceId < pTarget->faceId) ? pLink->faceId : pTarget->faceId),
              ((pLink->faceId < pTarget->faceId) ? pTarget->faceId : pLink->faceId),
              pLink->distances[targetIndex]);
            rc = sqlite3_exec(db, sqlBuf, 0, 0, &err_msg);
            if (rc != SQLITE_OK ) {
              fprintf(stderr, "SQL error: %s\n", err_msg);
              sqlite3_free(err_msg);        
              sqlite3_close(db);
              return 1;
            } 
            lines++;
            if (lines % 1000 == 0) {
              fprintf(stderr, "...output %d DB lines (%0.2f complete)...\n", lines, (float)(1. * sourceIndex / (1. * len)));
            }
          }
        }
      }
      pTarget = pTarget->next;
      targetIndex++;
    }
    pLink = pLink->next;
    sourceIndex++;
  }
  rc = sqlite3_exec(db, "COMMIT;", 0, 0, &err_msg);
  if (rc != SQLITE_OK ) {
    fprintf(stderr, "SQL error: %s\n", err_msg);
    sqlite3_free(err_msg);        
    sqlite3_close(db);
    return 1;
  } 

  sqlite3_close(db);

  return 0;
}