/*
  EIMILFile.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <EIMIL.h>
#include "EIMILint.h"

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

#define EIMIL_CLASS_SEPARATOR '.'
#define EIMIL_DIR_SEPARATOR '/'

static char EIMIL_default_filename[] = "default.xml";

typedef struct EIMIL_class {
    unsigned char *classname;
    unsigned char *topdir;
} EIMIL_class;
typedef struct EIMIL_classlist {
    int num;
    EIMIL_class *pclasses;
} EIMIL_classlist;

typedef struct EIMIL_file {
    unsigned char *pathname;
    CARD32BIT symbolid;
    unsigned char *classname;
} EIMIL_file;
typedef struct EIMIL_filelist {
    int num;
    EIMIL_file *pfiles;
} EIMIL_filelist;

static THREAD_SYNC_OBJECT clsstg;
static EIMIL_classlist classlist;
static EIMIL_filelist filelist;
#define LOCK_CLASSLIST LOCK_SYNC_OBJECT(clsstg)
#define UNLOCK_CLASSLIST UNLOCK_SYNC_OBJECT(clsstg)

static int
normalize_document(
    EIMIL_data* ped,
    Ebyte *pdoc,
    int len
)
{
    unsigned char *p, *pcst, *pend;
    pend = pdoc + len;

    /* TODO:when code conversion is required,
       implement it here. */

    /* strip comment */
    for (p = pdoc; p < pend; ) {
	if ((p = memchr(p, '<', len))
	    && ((pend - p) > 4)) {
	    pcst = p;
	    p++;
	    if ((p[0] == '!')
		&& (p[1] == '-')
		&& (p[2]== '-')) {
		p += 3;
		len = pend - p;
		for (;;) {
		    if ((p = memchr(p, '-', len)) != NULL) {
			p++;
			if (*p != '-') continue;
			p++;
			if (*p != '>') goto comment_error;
			p++;
			/* remove comment */
			while(pcst < p) *pcst++ = ' ';
			break;
		    }else{
			goto comment_error;
		    }
		    len = pend - p;
		}
	    }
	}else{
	    break;
	}
	len = pend - p;
    }

    return 1;

comment_error:
    EIMIL_set_error(ped, "Invalid comment.");
    return 0;
}

static int
load_file(
    const char *filename,
    EIMIL_data *ped
)
{
    EIMIL_parser_state* pps = &ped->pcommon->ps;
    int size;
    unsigned char *p;
    FILE *fp;
    struct stat st;

    if (!filename) return 0;
    if (stat(filename, &st) != 0) return 0;
    size = st.st_size;
    p = (unsigned char*) malloc(sizeof(unsigned char) * size);
    if (!p) {
	EIMIL_set_out_of_memory(ped);
	return 0;
    }
    fp = fopen(filename, "r");
    if ((!fp)
	|| (fread(p, 1, size, fp) != size)) {
	EIMIL_set_error(ped, "Fail to read file:%s", filename);
	if (fp) fclose(fp);
	free(p);
	return 0;
    }
    pps->buf = p;

    normalize_document(ped, p, size);

    pps->start = p;
    pps->end = p + size;
    pps->current = p; 
    pps->lineno = 1;
    ped->errstr[0] = '\0';

    fclose(fp);

    return 1;
}

int
EIMIL_register_class(
    unsigned char *classname,
    unsigned char *dirname
)
{
    int i, n, len;
    EIMIL_class *pec;

    LOCK_CLASSLIST;
    n = classlist.num;
    pec = classlist.pclasses;
    for (i = 0; i < n; i++, pec++) {
	if (strcmp(pec->classname, classname) == 0) break;
    }
    if (i == n) {
	pec = classlist.pclasses;
	pec = (EIMIL_class*) realloc(pec, sizeof(EIMIL_class) * (n + 1));
	if (!pec) return 0;
	classlist.pclasses = pec;

	/*
	  longer classname takes higher order.
	  Should we apply more efficient algorithm?
	*/
	len = strlen(classname);
	for (i = 0;i < n;i++) {
	    if (len > strlen(pec->classname)) break;
	}
	if (i < n) memmove(pec + 1, pec, sizeof(EIMIL_class));
	pec->topdir = NULL;
	pec->classname = strdup(classname);
	if (!pec->classname) return 0;
	classlist.num++;
    }
    if (pec->topdir) free(pec->topdir);
    pec->topdir = strdup(dirname);
    if (!pec->topdir) return 0;
    UNLOCK_CLASSLIST;

    return 1;
}

static int match_classname(unsigned char *cst, const unsigned char *csn,
			   unsigned char **unmatched)
{
	for (;;) {
		if (!*cst) {
			*unmatched = (unsigned char*)csn;
			return 1;
		}
		if (*cst != *csn) {
			*unmatched = NULL;
			return 0;
		}
		cst++;
		csn++;
	}
	return 0;
}

static int
find_file(
    unsigned char *basedir,
    const unsigned char *unmatched,
    const unsigned char *name,
    unsigned char *buf,
    int bufsize
)
{
    struct stat st;
    unsigned char *p;
    int totlen, baselen;

    p = buf;

    baselen = strlen(basedir);
    totlen = baselen + 1 + strlen(unmatched) + 1;
    if (name)
	totlen += strlen(name);
    else
	totlen += sizeof(EIMIL_default_filename);

    if (totlen > bufsize) return 0;

    strcpy(p, basedir);
    p += baselen;
    while (*unmatched) {
	if (*unmatched == EIMIL_CLASS_SEPARATOR)
	    *p = EIMIL_DIR_SEPARATOR;
	else
	    *p = *unmatched;
	unmatched++;
	p++;
    }
    *p++ = '/';
    if (name)
	strcpy(p, name);
    else
	strcpy(p, EIMIL_default_filename);

    if (stat(buf, &st) != 0) return 0;

    return 1;
}

unsigned char*
EIMIL_find_file(
    const unsigned char *classname,
    const unsigned char *name
)
{
    int i, n;
    unsigned char buf[MAXPATHLEN];
    unsigned char *unmatched;
    EIMIL_class *pec;

    LOCK_CLASSLIST;
    n = classlist.num;
    pec = classlist.pclasses;
    for (i = 0;i < n;i++, pec++) {
	if ((match_classname(pec->classname, classname, &unmatched))
	    && (find_file(pec->topdir, unmatched, name, buf, sizeof(buf)))) {
	    UNLOCK_CLASSLIST;
	    return strdup(buf);
	}
    }
    UNLOCK_CLASSLIST;

    return NULL;
}

int
EIMIL_enumerate_class(
    unsigned char *classname
)
{
    return -1;
}

int
EIMIL_file_symbolid(
    unsigned char *classname,
    unsigned char *name
)
{
    return -1;
}

int
EIMIL_parse_file(
    EIMIL_handle *peh,
    const unsigned char *filename
)
{
    int ret;
    EIMIL_data *ped;
    EIMIL_parser_state *pps;

    ped = EIMIL_make_handle_data(NULL);
    if (!ped) return 0;

    pps = &ped->pcommon->ps;

    if (!load_file(filename, ped)) return 0;

    ret = EIMIL_parse_start(ped);

    free(pps->buf);
    pps->buf = NULL;

    *peh = ped;

    return ret;
}

int
EIMILFile_init()
{
    INIT_SYNC_OBJECT(clsstg);
    return 1;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
