#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "idmap.h"

struct idMap_s {
   struct idElement * byId;
   int numEntries;
};

typedef struct idMap_s * idMap;

struct idElement {
   long int id;
   char * name;
};

typedef void * (*iterFn)(void);
typedef int (*infoFn)(void * item, struct idElement * el);

static idMap uidMap = NULL;
static idMap gidMap = NULL;

static int idCmp(const void * a, const void * b) {
   const struct idElement * one = a;
   const struct idElement * two = b;

   if (one->id < two->id)
       return -1;
   else if (one->id > two->id)
       return 1;

   return 0;
}

static idMap readmap(iterFn fn, infoFn info) {
   idMap map;
   int alloced;
   void * res;
   struct idElement * newEntries;

   map = malloc(sizeof(*map));
   if (!map) {
       return NULL;
   }

   alloced = 5;
   map->byId = malloc(sizeof(*map->byId) * alloced);
   if (!map->byId) {
       free(map);
       return NULL;
   }
   map->numEntries = 0;

   while ((res = fn())) {
       if (map->numEntries == alloced) {
           alloced += 5;
           newEntries = realloc(map->byId,
                                       sizeof(*map->byId) * alloced);
           if (!newEntries) {
               /* FIXME: this doesn't free the id names */
               free(map->byId);
               free(map);
               return NULL;
           }

           map->byId = newEntries;
       }

       if (info(res, map->byId + map->numEntries++)) {
           /* FIXME: this doesn't free the id names */
           free(map->byId);
           free(map);
           return NULL;
       }
   }

   map->byId = realloc(map->byId,
                               sizeof(*map->byId) * map->numEntries);

   qsort(map->byId, map->numEntries, sizeof(*map->byId), idCmp);

   return map;
}

static int pwInfo(struct passwd * pw, struct idElement * el) {
   el->id = pw->pw_uid;
   el->name = strdup(pw->pw_name);

   return el->name == NULL;
}

static int grInfo(struct group * gr, struct idElement * el) {
   el->id = gr->gr_gid;
   el->name = strdup(gr->gr_name);

   return el->name == NULL;
}

idMap readUIDmap(void) {
   idMap result;

   result = readmap((void *) getpwent, (void *) pwInfo);
   endpwent();

   return result;
}

idMap readGIDmap(void) {
   idMap result;

   result = readmap((void *) getgrent, (void *) grInfo);
   endgrent();

   return result;
}

char * idSearchByUid(long int id) {
   struct idElement el = { id, NULL };
   struct idElement * match;

   match = bsearch(&el, uidMap->byId, uidMap->numEntries,
                  sizeof(*uidMap->byId), idCmp);

   if (match) return match->name; else return NULL;
}

char * idSearchByGid(long int id) {
   struct idElement el = { id, NULL };
   struct idElement * match;

   match = bsearch(&el, gidMap->byId, gidMap->numEntries,
                  sizeof(*gidMap->byId), idCmp);

   if (match) return match->name; else return NULL;
}

int idInit(void) {
   if (!(uidMap = readUIDmap())) return 1;
   if (!(gidMap = readGIDmap())) return 1;

   return 0;
}