/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1998-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
#include <windows.h>
#include <winsvc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "erlsrv_global.h"
#include "erlsrv_registry.h"

#define LOG_TYPE "System"
#define LOG_ROOT \
"SYSTEM\\CurrentControlSet\\Services\\EventLog\\" LOG_TYPE "\\"
#define LOG_APP_KEY APP_NAME


#define BASE_KEY HKEY_LOCAL_MACHINE
#define PRODUCT_NAME APP_NAME
#define OLD_PRODUCT_VERSION "1.0"
#define PRODUCT_VERSION "1.1"
#define PROG_KEY "SOFTWARE\\Ericsson\\Erlang\\" PRODUCT_NAME "\\" PRODUCT_VERSION
#define OLD_PROG_KEY "SOFTWARE\\Ericsson\\Erlang\\" PRODUCT_NAME "\\" OLD_PRODUCT_VERSION

#define MAX_KEY_LEN MAX_PATH

static const char * const noString = "\0";

#define MAX_MANDATORY_REG_ENTRY 10 /* InternalServiceName == reg_entries[10] */
static RegEntry reg_entries[] = {
  {"StopAction",REG_SZ,NULL},
  {"OnFail",REG_DWORD,NULL},
  {"Machine",REG_EXPAND_SZ,NULL},
  {"Env", REG_MULTI_SZ,NULL},
  {"WorkDir", REG_EXPAND_SZ,NULL},
  {"Priority",REG_DWORD,NULL},
  {"SName",REG_SZ,NULL},
  {"Name",REG_SZ,NULL},
  {"Args",REG_EXPAND_SZ,NULL},
  {"DebugType",REG_DWORD,NULL},
  {"InternalServiceName",REG_SZ,NULL},
  /* Non mandatory follows */
  {"Comment",REG_SZ,NULL}
};


int num_reg_entries = sizeof(reg_entries)/sizeof(RegEntry);

RegEntry *empty_reg_tab(void){
  RegEntry *ret = malloc(num_reg_entries * sizeof(RegEntry));
  memcpy(ret,reg_entries,num_reg_entries * sizeof(RegEntry));
  return ret;
}

void free_keys(RegEntry *keys){
  int i;

  for(i=0;i<num_reg_entries && keys[i].name != NULL;++i){
    if((keys[i].type == REG_SZ || keys[i].type == REG_EXPAND_SZ ||
	keys[i].type == REG_MULTI_SZ) && 
       keys[i].data.bytes != noString){
      free(keys[i].data.bytes);
      if(keys[i].type == REG_EXPAND_SZ && 
	 keys[i].data.expand.unexpanded != noString)
	free(keys[i].data.expand.unexpanded);
    }
  }
  free(keys);
}

void free_all_keys(RegEntryDesc *descs){
  RegEntryDesc *tmp = descs;
  for(;tmp->servicename != NULL; ++tmp){
    free_keys(tmp->entries);
    free(tmp->servicename);
  }
  free(descs);
}

RegEntry *get_keys(char *servicename){
  RegEntry *res = NULL;
  HKEY prog_key;
  int key_opened = 0;
  int i;
  DWORD ret;
  char *copy;
  char *tmpbuf;
  DWORD tmpbuflen;

  char key_to_open[MAX_KEY_LEN];

  DWORD val_type;
  char *val_data = malloc(MAX_KEY_LEN);
  DWORD val_datalen;
  DWORD val_datasiz = MAX_KEY_LEN;

  if(strlen(PROG_KEY) + strlen(servicename) + 2 > MAX_KEY_LEN)
    goto error;
  sprintf(key_to_open,"%s\\%s",PROG_KEY,servicename);
  
  if(RegOpenKeyEx(BASE_KEY,
		  key_to_open,
		  0,
		  KEY_QUERY_VALUE,
		  &prog_key) != ERROR_SUCCESS)
    goto error;
  key_opened = 1;

  res = malloc(num_reg_entries*sizeof(RegEntry));
  for(i=0;i<num_reg_entries;++i)
    res[i].name = NULL;

  for(i=0;i<num_reg_entries;++i){
    for(;;){
      val_datalen = val_datasiz;
      ret = RegQueryValueEx(prog_key,
			    reg_entries[i].name,
			    NULL,
			    &val_type,
			    (BYTE *) val_data,
			    &val_datalen);
      if(ret == ERROR_SUCCESS){
	if(reg_entries[i].type == val_type)
	  break;
	else
	  goto error;
      } else if(ret == ERROR_MORE_DATA){
	val_data = realloc(val_data,val_datasiz = val_datalen);
      } else if (i > MAX_MANDATORY_REG_ENTRY && ret == ERROR_FILE_NOT_FOUND) {
	  /* Non mandatory entries, look at the type... */
	  switch (reg_entries[i].type){
	  case REG_EXPAND_SZ:
	  case REG_SZ:
	  case REG_MULTI_SZ:
	      val_datalen = 0;
	      break;
	  case REG_DWORD:
	      { 
		  DWORD dummy = 0;
		  memcpy(val_data,&dummy,(val_datalen = sizeof(DWORD)));
	      }
	      break;
	  default:
	      goto error;
	  }
	  break; /* for(;;) */
      } else {
	goto error;
      }
    }
    res[i] = reg_entries[i];
    copy = NULL;
    switch(reg_entries[i].type){
    case REG_EXPAND_SZ:
      if(!val_datalen || val_data[0] == '\0'){
	copy = (char *) noString;
	res[i].data.expand.unexpanded = (char *) noString;
      } else {
	tmpbuf = malloc(MAX_KEY_LEN);
	tmpbuflen = (DWORD) MAX_KEY_LEN;
	for(;;){
	  ret = ExpandEnvironmentStrings(val_data,tmpbuf,tmpbuflen);
	  if(!ret){
	    free(tmpbuf);
	    goto error;
	  }else if(ret > tmpbuflen){
	    tmpbuf=realloc(tmpbuf,tmpbuflen=ret);
	  } else {
	    copy = strdup(tmpbuf);
	    free(tmpbuf);
	    break;
	  }
	}
	res[i].data.expand.unexpanded = strdup(val_data);
      }
    case REG_MULTI_SZ:
    case REG_SZ:
      if(!copy){
	if(!val_datalen || 
	   ((val_datalen == 1 && val_data[0] == '\0') ||
	    (val_datalen == 2 && val_data[0] == '\0' && 
	     val_data[1] == '\0'))){
	  copy = (char *) noString;
	} else {
	  copy = malloc(val_datalen);
	  memcpy(copy,val_data,val_datalen);
	}
      }
      res[i].data.bytes = copy;
      break;
    case REG_DWORD:
      memcpy(&res[i].data.value,val_data,sizeof(DWORD));
      break;
    default:
      goto error;
    }
  }
  RegCloseKey(prog_key);
  free(val_data);
  return res;
error:
  free(val_data);
  if(res != NULL) 
    free_keys(res);
  if(key_opened)
    RegCloseKey(prog_key);
  return NULL;
}

int set_keys(char *servicename, RegEntry *keys){
  HKEY prog_key;
  int key_opened = 0;
  int i;
  char key_to_open[MAX_KEY_LEN];
  DWORD disposition;

  if(strlen(PROG_KEY) + strlen(servicename) + 2 > MAX_KEY_LEN)
    goto error;
  sprintf(key_to_open,"%s\\%s",PROG_KEY,servicename);
  
  if(RegOpenKeyEx(BASE_KEY,
		  key_to_open,
		  0,
		  KEY_SET_VALUE,
		  &prog_key) != ERROR_SUCCESS){
    if(RegCreateKeyEx(BASE_KEY,
		      key_to_open, 
		      0, 
		      NULL, 
		      REG_OPTION_NON_VOLATILE,
		      KEY_SET_VALUE, 
		      NULL, 
		      &prog_key, 
		      &disposition) != ERROR_SUCCESS)
    goto error;
  }
  key_opened = 1;

  
  for(i=0;i<num_reg_entries;++i){
    void *ptr;
    DWORD siz;
    int j;
    switch(keys[i].type){
    case REG_SZ:
      ptr = keys[i].data.bytes;
      siz = strlen(ptr)+1;
      break;
    case REG_EXPAND_SZ:
      ptr = keys[i].data.expand.unexpanded;
      siz = strlen(ptr)+1;
      break;
    case REG_MULTI_SZ:
      ptr = keys[i].data.bytes;
      for(j=0;!(((char *)ptr)[j] == '\0' && 
		((char *)ptr)[j+1] == '\0');++j)
	;
      siz=(DWORD)j+2;
      break;
    case REG_DWORD:
      ptr = &keys[i].data.value;
      siz = sizeof(DWORD);
      break;
    default:
      goto error;
    }
#ifdef HARDDEBUG
    fprintf(stderr,"%s %s:%d\n",keys[i].name,
	    (keys[i].type == REG_DWORD) ? "(dword)" : ptr,siz);
#endif
    if(RegSetValueEx(prog_key,
		     keys[i].name,
		     0,
		     keys[i].type,
		     ptr,
		     siz) != ERROR_SUCCESS)
      goto error;
  }
  RegCloseKey(prog_key);
  return 0;
error:
  if(key_opened)
    RegCloseKey(prog_key);
  return 1;
}

static int do_remove_keys(char *servicename, const char *prog_key_name){
  HKEY prog_key;
  if(RegOpenKeyEx(BASE_KEY,
		  prog_key_name,
		  0,
		  KEY_ALL_ACCESS,
		  &prog_key) != ERROR_SUCCESS)
    return -1;
  if(RegDeleteKey(prog_key,servicename) != ERROR_SUCCESS){
    RegCloseKey(prog_key);
    return -1;
  }
  RegCloseKey(prog_key);
  return 0;
}

int remove_keys(char *servicename){
    int ret;

    if((ret = do_remove_keys(servicename, PROG_KEY)) < 0){
	if(!do_remove_keys(servicename, OLD_PROG_KEY))
	    return 1;
	else
	    return -1;
    } 
    return ret;
}
  

RegEntryDesc *get_all_keys(void){
  RegEntryDesc *res = malloc(10*sizeof(RegEntryDesc));
  int res_siz = 10;
  int ndx = 0;
  HKEY prog_key;
  int key_opened = 0;
  DWORD enum_index;
  char name[MAX_KEY_LEN];
  DWORD namelen;
  char class[MAX_KEY_LEN];
  DWORD classlen;
  FILETIME ft;
  
  res[ndx].servicename = NULL;
  if(RegOpenKeyEx(BASE_KEY, PROG_KEY, 0, 
		  KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
		  &prog_key) != ERROR_SUCCESS)
    goto error;
  key_opened = 1;
  for(enum_index = 0, namelen = MAX_KEY_LEN, classlen = MAX_KEY_LEN;
      ERROR_SUCCESS == RegEnumKeyEx(prog_key,
				   enum_index,
				   name,
				   &namelen,
				   NULL,
				   class,
				   &classlen,
				   &ft);
      ++enum_index, namelen = MAX_KEY_LEN, classlen = MAX_KEY_LEN){
    if(ndx >= res_siz - 1)
      res = realloc(res, (res_siz += 10)*sizeof(RegEntryDesc));
    if(!(res[ndx].entries = get_keys(name)))
      goto error;
    res[ndx].servicename = strdup(name);
    res[++ndx].servicename = NULL;
  }
  RegCloseKey(prog_key);
  return res;
error:
  if(key_opened)
    RegCloseKey(prog_key);
  free_all_keys(res);
  return NULL;
}

int register_logkeys(void){
  HKEY key;
  DWORD disposition;
  DWORD types = EVENTLOG_ERROR_TYPE |
    EVENTLOG_WARNING_TYPE |
    EVENTLOG_INFORMATION_TYPE;
  DWORD catcount=1;
  char filename[2048];
  DWORD fnsiz=2048;

  if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,
		    LOG_ROOT LOG_APP_KEY, 0, 
		    NULL, REG_OPTION_NON_VOLATILE,
		    KEY_SET_VALUE, NULL, 
		    &key, &disposition) != ERROR_SUCCESS)
    return -1;
  if(!GetModuleFileName(NULL, filename, fnsiz))
    return -1;
  if(RegSetValueEx(key, "EventMessageFile",
		0, REG_EXPAND_SZ, (LPBYTE) filename,
		strlen(filename)+1) != ERROR_SUCCESS)
    return -1;
  if(RegSetValueEx(key, "TypesSupported",
		0, REG_DWORD, (LPBYTE) &types,
		sizeof(DWORD)) != ERROR_SUCCESS)
    return -1;
  return 0;
}