/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2003-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 <stdio.h>
#include <stdlib.h>
#include "init_file.h"

#define ALLOC malloc
#define REALLOC realloc
#define FREE free 

#define CONTEXT_BUFFER_SIZE 1024

typedef struct {
    HANDLE fd;
    int eof;
    int num;
    int pos;
    char buff[CONTEXT_BUFFER_SIZE];
} FileContext;

static char *read_one_line(FileContext *fc)
{
    char *buff = ALLOC(10);
    int size = 10;
    int num = 0;
    int skipping;
    int escaped;

#define PUSH(Char) 				\
    do {					\
	if (num == size) {			\
	    size += 10;				\
	    buff = REALLOC(buff,size);		\
	}					\
	buff[num++] = (Char);			\
    } while(0)

#define POP() (buff[--num])
#define TOP() (buff[(num-1)])

    skipping = 0;
    escaped = 0;
    for(;;) {
	char current;
	if (fc->eof) {
	    break;
	}
	if (fc->pos == fc->num) {
	    if (!ReadFile(fc->fd, fc->buff, CONTEXT_BUFFER_SIZE, 
			  &(fc->num), NULL) || !(fc->num)) {
		fc->eof = 1;
		break;
	    }
	    fc->pos = 0;
	}
	current = fc->buff[fc->pos];
	++(fc->pos);
	switch (current) {
	case ' ':
	    if (!skipping && num) {
		PUSH(current);
	    }
	    escaped = 0;
	    break;
	case ';':
	    if (!skipping) {
		if (!escaped) {
		    skipping = 1;
		} else {
		    PUSH(current);
		}
	    }
	    escaped = 0;
	    break;
	case '\\':
	    if (!skipping) {
		if (!escaped) {
		    escaped = 1;
		} else {
		    PUSH(current);
		    escaped = 0;
		}
	    }
	    break;
	case '\r':
	    break;
	case '\n':
	    if (!escaped) {
		while (num && TOP() == ' ') {
			POP();
		}
		if (num) {
		    goto done;
		}
	    } 
	    skipping = 0;
	    escaped = 0;
	    break;
	default:
	    if (!skipping) {
		PUSH(current);
	    }
	    escaped = 0;
	    break;
	}
    }
    /* EOF comes here */
    while (num && TOP() == ' ') {
	POP();
    }
    if (!num) {
	FREE(buff);
	return NULL;
    }
 done:
    PUSH('\0');
    return buff;
#undef PUSH
#undef POP
#undef TOP
}

static int is_section_header(char *line) 
{
    int x = strlen(line);
    return (x > 2 && *line == '[' && line[x-1] == ']');
}

static int is_key_value(char *line)
{
    char *p = strchr(line,'=');

    return (p != NULL && p > line);
}

static char *digout_section_name(char *line)
     /* Moving it because it shall later be freed. */
{
    int x = strlen(line);
    memmove(line,line+1,x-1);
    line[x-2] = '\0';
    return line;
}

static void digout_key_value(char *line, char **key, char **value)
{
    char *e = strchr(line,'=');
    *key = line;
    *value = (e+1);
    *e = '\0';
    while (*(--e) == ' ') {
	*e = '\0';
    }
    while (**value == ' ') {
	++(*value);
    }
}

InitFile *load_init_file(char *filename)
{
    HANDLE infile;
    InitFile *inif;
    InitSection *inis;
    InitEntry *inie;
    FileContext fc;
    char *line;
    char **lines;
    int size_lines;
    int num_lines;

    int i;

    if ( (infile = CreateFile(filename, 
			      GENERIC_READ,
			      FILE_SHARE_READ,
			      NULL,
			      OPEN_EXISTING,
			      FILE_ATTRIBUTE_NORMAL,
			      NULL)) == INVALID_HANDLE_VALUE) {
	return NULL;
    }
    
    size_lines = 10;
    num_lines = 0;
    lines = ALLOC(size_lines * sizeof(char *));

    fc.fd = infile;
    fc.eof = 0;
    fc.num = 0;
    fc.pos = 0;
    while ((line = read_one_line(&fc)) != NULL) {
	if (num_lines == size_lines) {
	    size_lines += 10;
	    lines = REALLOC(lines,size_lines * sizeof(char *));
	}
	lines[num_lines] = line;
	++num_lines;
    }
    CloseHandle(infile);
    /* Now check the lines before doing anything else, so that 
       we don't need any error handling while creating the data
       structures */
    /* 
       The file should contain:
       [section]
       Key=Value
       ...
       [section]
       ...
    */
    i = 0;
    while (i < num_lines && is_section_header(lines[i])) {
	++i;
	while (i < num_lines && is_key_value(lines[i])) {
	    ++i;
	}
    }
    if (i < num_lines) {
	for (i = 0; i < num_lines; ++i) {
	    FREE(lines[i]);
	}
	FREE(lines);
	return NULL;
    }
    
    /* So, now we know it's consistent... */
    i = 0;
    inif = ALLOC(sizeof(InitFile));
    inif->num_sections = 0;
    inif->size_sections = 10;
    inif->sections = ALLOC(sizeof(InitSection *) * 10); 
    while (i < num_lines) {
	inis = ALLOC(sizeof(InitSection));
	inis->num_entries = 0;
	inis->size_entries = 10;
	inis->section_name = digout_section_name(lines[i]);
	inis->entries = ALLOC(sizeof(InitEntry *) * 10);
	++i;
	while (i < num_lines && is_key_value(lines[i])) {
	    inie = ALLOC(sizeof(InitEntry));
	    digout_key_value(lines[i], &(inie->key), &(inie->value));
	    if (inis->num_entries == inis->size_entries) {
		inis->size_entries += 10;
		inis->entries = 
		    REALLOC(inis->entries,
			    sizeof(InitEntry *) * inis->size_entries);
	    }
	    inis->entries[inis->num_entries] = inie;
	    ++(inis->num_entries);
	    ++i;
	}
	if (inif->num_sections == inif->size_sections) {
	    inif->size_sections += 10;
	    inif->sections = 
		REALLOC(inif->sections,
			sizeof(InitSection *) * inif->size_sections);
	}
	inif->sections[inif->num_sections] = inis;
	++(inif->num_sections);
    }
    FREE(lines); /* Only the array of strings, not the actual strings, they
		    are kept in the data structures. */
    return inif;
}

int store_init_file(InitFile *inif, char *filename)
{
    char *buff;
    int size = 10;
    int num = 0;
    int i,j;
    HANDLE outfile;

#define PUSH(Char) 				\
    do {					\
	if (num == size) {			\
	    size += 10;				\
	    buff = REALLOC(buff,size);		\
	}					\
	buff[num++] = (Char);			\
    } while(0)

    if ( (outfile = CreateFile(filename, 
			      GENERIC_WRITE,
			      FILE_SHARE_WRITE,
			      NULL,
			      CREATE_ALWAYS,
			      FILE_ATTRIBUTE_NORMAL,
			      NULL)) == INVALID_HANDLE_VALUE) {
	return INIT_FILE_OPEN_ERROR;
    }
    buff = ALLOC(size);

    for(i = 0; i < inif->num_sections; ++i) {
	int len;
	int written;
	InitSection *inis = inif->sections[i];

	if (!WriteFile(outfile,"[",1,&written,NULL) || written != 1) {
	    goto write_error;
	}
	len = strlen(inis->section_name);
	if (!WriteFile(outfile,inis->section_name,len,&written,NULL) ||
	    written != len) {
	    goto write_error;
	}
	if (!WriteFile(outfile,"]\n",2,&written,NULL) || written != 2) {
	    goto write_error;
	}
	for (j = 0; j < inis->num_entries; ++j) {
	    InitEntry *inie = inis->entries[j];
	    char *p = inie->key;
	    num = 0;
	    for (;*p != '\0';++p) {
		switch (*p) {
		case '\\': 
		case ';': 
		    PUSH('\\');
		default:
		    PUSH(*p);
		    break;
		}
	    }
	    PUSH('=');
	    p = inie->value;
	    for (;*p != '\0';++p) {
		switch (*p) {
		case '\\': 
		case ';': 
		    PUSH('\\');
		default:
		    PUSH(*p);
		    break;
		}
	    }
	    PUSH('\n');
	    if (!WriteFile(outfile,buff,num,&written,NULL) || written != num) {
		goto write_error;
	    }
	}
    }
    FREE(buff);
    CloseHandle(outfile);
    return INIT_FILE_NO_ERROR;
 write_error:
    FREE(buff);
    CloseHandle(outfile);
    return INIT_FILE_WRITE_ERROR;
#undef PUSH
}

InitFile *create_init_file(void)
{
    InitFile *inif = ALLOC(sizeof(InitFile));
    inif->num_sections = 0;
    inif->size_sections = 10;
    inif->sections = ALLOC(sizeof(InitSection *) * 10);
    return inif;
}

InitSection *create_init_section(char *section_name)
{
    InitSection *inis = ALLOC(sizeof(InitSection));
    inis->num_entries = 0;
    inis->section_name = ALLOC(sizeof(char) * (strlen(section_name) + 1));
    strcpy(inis->section_name, section_name);
    inis->size_entries = 10;
    inis->entries = ALLOC(sizeof(InitEntry *) * 10);
    return inis;
}

static void free_init_entry(InitEntry *inie)
{
    FREE(inie->key);
    /* Value is part of the same buffer */
    FREE(inie);
}

void free_init_section(InitSection *inis)
{
    int i;
    for (i = 0;i < inis->num_entries; ++i) {
	free_init_entry(inis->entries[i]);
    }
    FREE(inis->entries);
    FREE(inis->section_name);
    FREE(inis);
}

void free_init_file(InitFile *inif)
{
    int i;
    for (i = 0; i < inif->num_sections; ++i) {
	free_init_section(inif->sections[i]);
    }
    FREE(inif->sections);
    FREE(inif);
}

static int find_init_section(InitFile *inif, char *section_name)
{
    int i;
    for (i = 0; i < inif->num_sections; ++i) {
	if (!strcmp(inif->sections[i]->section_name, section_name)) {
	    return i;
	}
    }
    return -1;
}

int delete_init_section(InitFile *inif, char *section_name)
{
    int i;

    if ((i = find_init_section(inif, section_name)) < 0) {
	return INIT_FILE_NOT_PRESENT;
    }

    free_init_section(inif->sections[i]);
    --(inif->num_sections);
    inif->sections[i] = inif->sections[inif->num_sections];

    return INIT_FILE_PRESENT;
}

int add_init_section(InitFile *inif, InitSection *inis)
{
    int i;
    InitSection *oinis;
    if ((i = find_init_section(inif, inis->section_name)) >= 0) {
	oinis = inif->sections[i];
	inif->sections[i] = inis;
	free_init_section(oinis);
	return INIT_FILE_PRESENT;
    } 
    if (inif->num_sections == inif->size_sections) {
	inif->size_sections += 10;
	inif->sections = REALLOC(inif->sections, 
				 sizeof(InitSection *) * inif->size_sections);
    }
    inif->sections[inif->num_sections] = inis;
    ++(inif->num_sections);
    return INIT_FILE_NOT_PRESENT;
}

InitSection *lookup_init_section(InitFile *inif, char *section_name)
{
    int i;
    if ((i = find_init_section(inif,section_name)) < 0) {
	return NULL;
    }
    return inif->sections[i];
}

char *nth_init_section_name(InitFile *inif, int n)
{
    if (n >= inif->num_sections) {
	return NULL;
    }
    return inif->sections[n]->section_name;
}

/* Inefficient... */
InitSection *copy_init_section(InitSection *inis, char *new_name) 
{
    int i;
    char *key;
    InitSection *ninis = create_init_section(new_name);
    i = 0;
    while ((key = nth_init_entry_key(inis,i)) != NULL) {
	add_init_entry(ninis, key, lookup_init_entry(inis, key));
	++i;
    }
    return ninis;
}

static int find_init_entry(InitSection *inis, char *key)
{
    int i;
    for (i = 0; i < inis->num_entries; ++i) {
	if (!strcmp(inis->entries[i]->key,key)) {
	    return i;
	}
    }
    return -1;
}

int add_init_entry(InitSection *inis, char *key, char *value)
{
    int keylen = strlen(key);
    char *buff = ALLOC(sizeof(char) * (keylen + strlen(value) + 2));
    InitEntry *inie;
    char *obuff;
    int i;

    strcpy(buff,key);
    strcpy(buff+keylen+1,value);

    if ((i = find_init_entry(inis,key)) >= 0) {
	inie = inis->entries[i];
	FREE(inie->key);
	inie->key = buff;
	inie->value = buff+keylen+1;
	return INIT_FILE_PRESENT;
    }
    inie = ALLOC(sizeof(InitEntry));
    inie->key = buff;
    inie->value = buff+keylen+1;
    if (inis->num_entries == inis->size_entries) {
	inis->size_entries += 10;
	inis->entries = REALLOC(inis->entries, 
				sizeof(InitEntry *) * inis->size_entries);
    }
    inis->entries[inis->num_entries] = inie;
    ++(inis->num_entries);
    return INIT_FILE_NOT_PRESENT;
}

char *lookup_init_entry(InitSection *inis, char *key)
{
    int i;
    if ((i = find_init_entry(inis,key)) < 0) {
	return NULL;
    }
    return inis->entries[i]->value;
}

char *nth_init_entry_key(InitSection *inis, int n)
{
    if (n >= inis->num_entries) {
	return NULL;
    }
    return inis->entries[n]->key;
}

int delete_init_entry(InitSection *inis, char *key)
{
    int i;
    InitEntry *inie;
    if ((i = find_init_entry(inis, key)) < 0) {
	return INIT_FILE_NOT_PRESENT;
    }
    free_init_entry(inis->entries[i]);
    --(inis->num_entries);
    inis->entries[i] = inis->entries[inis->num_entries];
    return INIT_FILE_PRESENT;
}