/***********************************************************/
/*                                                         */
/*        registry.c  - Bad Seal registry functions        */
/*                                                         */
/*                          version 0.3                    */
/*                                                         */
/*                    Copyright (C) 2001                   */
/*          by Kostas Michalopoulos aka Bad Sector         */
/*                                                         */
/***********************************************************/

#include <stdio.h>
#include <string.h>
#include "oldreg.h"

char *registry_file;

void set_rec(int pos, t_registry_key rec)
{
  FILE *f = fopen(registry_file, "rb+");

  if (f) {
    strupr(rec.name);
    fseek(f, 64+pos*sizeof(t_registry_key), SEEK_SET);
    fwrite(&rec, sizeof(t_registry_key), 1, f);
    fclose(f);
  };
}

int add_rec(t_registry_key rec)
{
  FILE *f = fopen(registry_file, "rb+");
  int pos;

  if (f) {
    pos = (filelength(fileno(f))-64) / sizeof(t_registry_key);
    fclose(f);
    set_rec(pos, rec);
    return pos;
  };
  return -1;
}

t_registry_key get_rec(int pos)
{
  t_registry_key rec;
  FILE *f = fopen(registry_file, "rb");

  rec.type = KT_UNKNOWN;
  if (f) {
    fseek(f, 64+pos*sizeof(t_registry_key), SEEK_SET);
    fread(&rec, sizeof(t_registry_key), 1, f);
    fclose(f);
  };

  return rec;
}

char *get_path_name(char *path, int index)
{
  int i, c, idx;
  char name[128];
  idx = 1;
  c = 0;
  for (i=0;i<strlen(path);i++) if (path[i] == '/') {
    if (idx == index) {
      name[c] = 0;
      strupr(name);
      return strdup(name);
    };
    idx++;
    c = 0;
  } else {
    name[c] = path[i];
    c++;
  };
  if (idx == index) {
    name[c] = 0;
    strupr(name);
    return strdup(name);
  };
  return NULL;
}

int get_path_keys(char *path)
{
  int i, cnt;
  cnt = 1;
  for (i=0;i<strlen(path);i++) if (path[i] == '/') {
    cnt++;
  };
  return cnt;
}

int get_key_pos_in_node(int first, char *key, int use_links)
{
  t_registry_key rec;
  int pos;

  key = strdup(key);
  strupr(key);

  pos = first;
  while (1) {
    rec = get_rec(pos);
    if (strcmp(rec.name, key) == 0) {
      if (use_links)
      if (rec.type == KT_LINK) {
        while (1) {
          memcpy(&pos, &rec.data, 4);
          rec = get_rec(pos);
          if (rec.type != KT_LINK) break;
        };
        if (rec.type == KT_DELETED) {
          free(key);
          return -1;
        };
      };
      free(key);
      return pos;
    };
    pos = rec.next;
    if (pos == -1) {
      free(key);
      return -1;
    };
  };
}

int get_path_pos(char *path, int use_links)
{
  t_registry_key rec;
  int path_keys = get_path_keys(path);
  int pos, node_start, path_key = 1;
  char *key = get_path_name(path, path_key);

  node_start = 0;   /* root node */
  while (1) {
    pos = get_key_pos_in_node(node_start, key, use_links);
    if (pos == -1) {
      free(key);
      return -1;
    };
    rec = get_rec(pos);
    node_start = rec.child;
    free(key);
    path_key++;
    if (path_key > path_keys) return pos;  /* reached last key */
    key = get_path_name(path, path_key);

    if (node_start == -1) return -1;
  };
}

int get_next_avail_pos(int start)
{
  int pos;
  t_registry_key rec;

  pos = start;
  while (0) {
    rec = get_rec(pos);
    if (rec.next == -1) return pos;
    pos = rec.next;
  };
}

char *get_path_from_pos(int pos)
{
  t_registry_key rec;
  char *path = (char *) malloc(2);
  strcpy(path, "");

  do {
    char *npath;
    rec = get_rec(pos);
    npath = key_in_path(rec.name, path);
    free(path);
    path = npath;
    pos = rec.parent;
    if (pos == -1) break;
  } while (1);
  if (path[strlen(path)-1] == '/') {
    path[strlen(path)-1] = 0;
    path = (char *) realloc(path, strlen(path));
  };

  return path;
}

/*
Combines path and key and returns the result.
Example:
    key_in_path("system/seal", "distribution")

    returns "system/seal/distribution"

you must free allocated memory after
*/
char *key_in_path(char *path, char *key)
{
  char *result = (char *) malloc(strlen(path)+strlen(key)+2);
  strcpy(result, path);
  strcat(result, "/");
  strcat(result, key);
  return result;
}

/*
Returns 1 (true) if key exists, else 0 (false).
*/
int key_exists(char *key)
{
  return get_path_pos(key, 1)!=-1?1:0;
}

/*
Creates a new key named `name' in `path'. if 'use_links' is 1 then
uses links for finding path. F.e.

key 'current' is defined as link to 'users/default'. if you want to
create a key in 'current' you must enable links or you'll just waste
disk space because created key will not be visible.

If success returns RE_SUCCESS
*/
int create_key_ex(char *path, char *name, int use_links)
{
  t_registry_key rec, nrec;
  int path_pos;
  int new_key_pos;
  int pos;
  char *key_with_path;

  if (strcmp(path, "") == 0 || strcmp(name, "") == 0 || path == NULL || name == NULL) return RE_NO_KEY_SPECIFIED;

  key_with_path = key_in_path(path, name);
  path_pos = get_path_pos(path, use_links);

  if (path_pos == -1) return RE_PATH_NOT_FOUND;
  if (key_exists(key_with_path)) {
    free(key_with_path);
    return RE_SUCCESS;
  };
  free(key_with_path);

  strcpy(nrec.name, name);
  strupr(nrec.name);
  nrec.type = KT_NODATA;
  nrec.size = 0;
  nrec.parent = path_pos;
  nrec.child = -1;
  nrec.prev = -1;
  nrec.next = -1;
  new_key_pos = add_rec(nrec);
  rec = get_rec(path_pos);
  if (rec.child == -1) {
    rec.child = new_key_pos;
    set_rec(path_pos, rec);
    return RE_SUCCESS;
  };
  pos = rec.child;
  while (1) {
    rec = get_rec(pos);
    if (rec.next == -1) {
      rec.next = new_key_pos;
      set_rec(pos, rec);
      nrec.prev = pos;
      set_rec(new_key_pos, nrec);
      return RE_SUCCESS;
    };
    pos = rec.next;
  };
}

/*
Like create_key_ex but always uses links.
*/
int create_key(char *path, char *name)
{
  return create_key_ex(path, name, 1);
}

/*
Creates a new link named `name' to a key `dest'. Link key is created
in `path'
*/
int create_link_key(char *path, char *name, char *dest)
{
  t_registry_key rec, nrec;
  int path_pos, r;
  char *key_with_path;

  if (strcmp(path, "") == 0 || strcmp(name, "") == 0 || strcmp(dest, "") == 0 || path == NULL || name == NULL || dest == NULL) return RE_NO_KEY_SPECIFIED;

  r = create_key_ex(path, name, 0);
  if (r != RE_SUCCESS) return r;

  key_with_path = key_in_path(path, name);
  path_pos = get_path_pos(key_with_path, 0);
  free(key_with_path);

  rec = get_rec(path_pos);
  rec.type = KT_LINK;

  r = get_path_pos(dest, 1);
  memcpy(&rec.data, &r, 4);
  set_rec(path_pos, rec);

  return RE_SUCCESS;
}

/*
Changes/sets the data of key `key' to `data' and it's type to `type'.
`size' is the size of `data' in bytes.
*/
int set_data_and_type(char *key, int type, void *data, int size)
{
  t_registry_key rec;
  int key_pos;

  if (strcmp(key, "") == 0 || key == NULL) return RE_NO_KEY_SPECIFIED;
  if (get_path_keys(key) == 1) return RE_ROOT_KEY_SPECIFIED;

  key_pos = get_path_pos(key, 1);

  if (key_pos == -1) return RE_KEY_NOT_FOUND;
  rec = get_rec(key_pos);
  rec.type = type;
  rec.size = size;
  memcpy(&rec.data, data, size);
  set_rec(key_pos, rec);

  return RE_SUCCESS;
}

/*
Sets key `key''s data to `str' and it's type to KT_STRING
*/
int set_key(char *key, char *str)
{
  return set_data_and_type(key, KT_STRING, str, strlen(str)+1);
}

/*
Sets key `key''s data to `b' and it's type to KT_BYTE
*/
int set_key_byte(char *key, unsigned char b)
{
  return set_data_and_type(key, KT_SMALL, &b, 1);
}

/*
Sets key `key''s data to `s' and it's type to KT_SMALL
*/
int set_key_small(char *key, short s)
{
  return set_data_and_type(key, KT_SMALL, &s, 2);
}

/*
Sets key `key''s data to `i' and it's type to KT_INTEGER
*/
int set_key_integer(char *key, long i)
{
  return set_data_and_type(key, KT_INTEGER, &i, 4);
}

/*
Sets key `key''s data to `big' and it's type to KT_BIG
*/
int set_key_big(char *key, long long big)
{
  return set_data_and_type(key, KT_BIG, &big, 8);
}

/*
Sets key `key''s data to `f' and it's type to KT_FLOAT
*/
int set_key_float(char *key, float f)
{
  return set_data_and_type(key, KT_FLOAT, &f, 4);
}

/*
Sets key `key''s data to `data', it's type to KT_DATA and it's size to `size'
*/
int set_key_data(char *key, void *data, int size)
{
  return set_data_and_type(key, KT_DATA, data, size);
}

/*
Returns data, type and size from key `key'.
*/
t_registry_get_info get_data_and_type(char *key)
{
  t_registry_key rec;
  t_registry_get_info inf;
  int key_pos;

  if (strcmp(key, "") == 0 || key == NULL) {
    inf.err = RE_NO_KEY_SPECIFIED;
    return inf;
  };

  inf.err = RE_SUCCESS;

  key_pos = get_path_pos(key, 1);
  if (key_pos == -1) {
    inf.err = RE_KEY_NOT_FOUND;
    return inf;
  };
  rec = get_rec(key_pos);
  inf.type = rec.type;
  inf.size = rec.size;
  memcpy(&inf.data, &rec.data, rec.size);

  return inf;
}

/*
Returns key `key''s value as string
*/
char *get_key(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return NULL; else return strdup(inf.data);
}

/*
Returns key `key''s value as byte (char)
*/
char get_key_byte(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    char r;
    memcpy(&r, &inf.data, 1);
    return r;
  };
}

/*
Returns key `key''s value as small (short)
*/
short get_key_small(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    short r;
    memcpy(&r, &inf.data, 2);
    return r;
  };
}

/*
Returns key `key''s value as integer (long)
*/
long get_key_integer(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    long r;
    memcpy(&r, &inf.data, 4);
    return r;
  };
}

/*
Returns key `key''s value as big integer (long long)
*/
long long get_key_big(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    long long r;
    memcpy(&r, &inf.data, 8);
    return r;
  };
}

/*
Returns key `key''s value as float
*/
float get_key_float(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    float r;
    memcpy(&r, &inf.data, 4);
    return r;
  };
}

/*
Returns key `key''s value as data
*/
void *get_key_data(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else {
    char *r = (char *) malloc(256);
    int i;
    for (i=0;i<inf.size;i++) r[i] = inf.data[i];
    return r;
  };
}

/*
Returns key `key''s size (usually used with get_key_data)
*/
int get_key_size(char *key)
{
  t_registry_get_info inf = get_data_and_type(key);
  if (inf.err != RE_SUCCESS) return 0; else return inf.size;
}

/*
Deletes key `key' and it's childs.
*/
int delete_key(char *key)
{
  t_registry_key rec, rec2;
  int key_pos;
  int pos;

  if (strcmp(key, "") == 0 || key == NULL) return RE_NO_KEY_SPECIFIED;
  key_pos = get_path_pos(key, 1);
  if (key_pos == -1) return RE_KEY_NOT_FOUND;
  if (get_path_keys(key) == 1) return RE_ROOT_KEY_SPECIFIED;

  rec = get_rec(key_pos);
  rec2 = get_rec(rec.parent);
  if (rec2.child == key_pos) {
    rec2.child = rec.next;
    set_rec(rec.parent, rec2);
  };
  if (rec.prev != -1) {
    rec2 = get_rec(rec.prev);
    rec2.next = rec.next;
    set_rec(rec.prev, rec2);
  };
  if (rec.next != -1) {
    rec2 = get_rec(rec.next);
    rec2.prev = rec.prev;
    set_rec(rec.next, rec2);
  };
  if (rec.child != -1) {
    pos = rec.child;
    while (1) {
      char *keyname;
      rec2 = get_rec(pos);
      keyname = key_in_path(key, rec2.name);
      delete_key(keyname);
      free(keyname);
      pos = rec2.next;
      if (pos == -1) break;
    };
  };
  rec.type = KT_DELETED;
  set_rec(key_pos, rec);
  return RE_SUCCESS;
}

/*
With reg_find_next is used to return all childs of key `path'. Returns 0
if no key found or there are no childs. Information about current key
is stored in `info'.
*/
int reg_find_first(char *path, p_registry_search info)
{
  int key_pos;
  t_registry_key rec;

  if (strcmp(path, "") == 0 || path == NULL) {
    info->parent_name = NULL;
    info->key = get_rec(0);
    info->name = strdup("USERS");
    info->pos = 0;
    return 1;
  };
  if (!key_exists(path)) return 0;

  key_pos = get_path_pos(path, 1);
  rec = get_rec(key_pos);

  if (rec.child == -1) return 0;

  info->parent_name = get_path_from_pos(key_pos);
  info->key = get_rec(rec.child);
  info->name = key_in_path(info->parent_name, info->key.name);
  info->pos = rec.child;

  return 1;
}

/*
See reg_find_first for info about using those two functions.
*/
int reg_find_next(p_registry_search info)
{
  if (info->key.next == -1) {
    free(info->parent_name);
    return 0;
  };

  info->pos = info->key.next;
  info->key = get_rec(info->key.next);
  if (info->parent_name) info->name = key_in_path(info->parent_name, info->key.name); else info->name = strdup(info->key.name);

  return 1;
}

void init_registry()
{
  FILE *f;
  t_registry_key rec;

  registry_file = strdup(default_registry_file);

  f = fopen(registry_file, "rb");
  if (!f) {
    int p = 6;

    f = fopen(registry_file, "wb");
    fclose(f);
    /* Write root keys */
    strcpy(rec.name, KEY_USERS);
    rec.type = KT_NODATA;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = -1;
    rec.next = 1;
    set_rec(0, rec);
    strcpy(rec.name, KEY_CURRENT);
    rec.type = KT_LINK;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = 0;
    rec.next = 2;
    memcpy(&rec.data, &p, 4);
    add_rec(rec);
    strcpy(rec.name, KEY_GLOBAL);
    rec.type = KT_NODATA;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = 1;
    rec.next = 3;
    add_rec(rec);
    strcpy(rec.name, KEY_CONFIG);
    rec.type = KT_NODATA;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = 2;
    rec.next = 4;
    add_rec(rec);
    strcpy(rec.name, KEY_SOFTWARE);
    rec.type = KT_NODATA;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = 3;
    rec.next = 5;
    add_rec(rec);
    strcpy(rec.name, KEY_SYSTEM);
    rec.type = KT_NODATA;
    rec.size = 0;
    rec.parent = -1;
    rec.child = -1;
    rec.prev = 4;
    rec.next = -1;
    add_rec(rec);

    create_key("users", "default");
  } else fclose(f);
};
