/*
 * Copyright (c) 1987, 1988, 1989 Stanford University
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Stanford not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  Stanford makes no representations about
 * the suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * STANFORD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
 * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Property sheet management.
 */

#include <InterViews/propsheet.h>
#include <InterViews/strpool.h>
#include <InterViews/strtable.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>

extern StringTable* nameTable;

PropertySheet* properties;

static StringPool* propvalues;

class PropList {
    friend class PropDir;
    friend class PropertySheet;
protected:
    PropertyName name;
    PropList* next;
    PropList* prev;

    PropList();
    PropList(PropertyName);
    virtual ~PropList();

    void Append(PropList*);
    void DeleteAll();
    PropList* Find(PropertyName);
    void Remove();
};

class AttrList : public PropList {
    friend class PropDir;
    friend class PropertySheet;

    PropertyValue value;
    PropertyType type;

    AttrList();
    AttrList(PropertyName);

    boolean FindAttr(PropertyDef&);
};

class DirList : public PropList {
    friend class PropDir;
    friend class PropertySheet;

    PropDir* info;

    DirList();
    DirList(PropertyName);
    ~DirList();

    boolean FindSubDir(PropertyName, PropDir*&);
};

class PropDir {
public:
    PropDir();
    ~PropDir();
private:
    friend class PropertySheet;

    PropDir* parent;
    AttrList* attrs;
    DirList* dirs;
    AttrList* vattrs;
    DirList* vdirs;

    PropDir* MakeDirs(const char*&);
};

static const int pathClusterSize = 20;

class PropPathElement {
    friend class PropertySheet;

    PropDir* dir;
    boolean sibling;
};

class PropPath {
    friend class PropertySheet;

    PropPathElement element[pathClusterSize];
    PropPathElement* top;
    PropPath* next;
    PropPath* prev;

    PropPath();
};

static char* Strdup (const char* str, int n) {
    char* s = new char[n+1];
    strncpy(s, str, n);
    s[n] = '\0';
    return s;
}

/*
 * class PropList
 */

PropList::PropList () {
    name = nil;
    next = this;
    prev = this;
}

PropList::PropList (PropertyName n) {
    name = n;
}

PropList::~PropList () {
    /* nothing to do */
}

void PropList::Append (PropList* p) {
    p->next = prev->next;	/* link element to old tail->next */
    p->prev = prev;		/* link element to old tail */
    prev->next = p;		/* link old tail to element */
    prev = p;			/* set tail to element */
}

void PropList::Remove () {
    next->prev = prev;
    prev->next = next;
}

PropList* PropList::Find (PropertyName n) {
    register PropList* p;

    for (p = next; p != this; p = p->next) {
	if (p->name == n) {
	    return p;
	}
    }
    return nil;
}

void PropList::DeleteAll () {
    register PropList* p, * pnext;

    for (p = next; p != this; p = pnext) {
	pnext = p->next;
	delete p;
    }
    next = this;
    prev = this;
}

/*
 * class AttrList
 */

AttrList::AttrList () {
    value = nil;
    type = nil;
}

AttrList::AttrList (PropertyName n) : (n) {
    value = nil;
    type = nil;
}

boolean AttrList::FindAttr (register PropertyDef& p) {
    register AttrList* a;

    for (a = (AttrList*)next; a != this; a = (AttrList*)a->next) {
	if (a->name == p.name) {
	    p.type = a->type;
	    p.value = a->value;
	    return true;
	}
    }
    return false;
}

/*
 * class DirList
 */

DirList::DirList () {
    info = nil;
}

DirList::DirList (PropertyName n) : (n) {
    info = new PropDir;
}

boolean DirList::FindSubDir (PropertyName n, PropDir*& subdir) {
    register DirList* d;

    for (d = (DirList*)next; d != this; d = (DirList*)d->next) {
	if (d->name == n) {
	    subdir = d->info;
	    return true;
	}
    }
    return false;
}

DirList::~DirList () {
    delete info;
}

/*
 * class PropDir
 */

PropDir::PropDir () {
    parent = nil;
    attrs = new AttrList;
    dirs = new DirList;
    vattrs = new AttrList;
    vdirs = new DirList;
}

PropDir::~PropDir () {
    dirs->DeleteAll();
    delete dirs;
    vdirs->DeleteAll();
    delete vdirs;
    attrs->DeleteAll();
    delete attrs;
    vattrs->DeleteAll();
    delete vattrs;
}

PropDir* PropDir::MakeDirs (const char*& name) {
    register const char* p;
    PropDir* dir;
    DirList* cur, * sub;
    PropertyName n;
    int c;

    dir = this;
    c = '.';
    for (p = name; *p != '\0'; p++) {
	if (*p == '.' || *p == '*') {
	    if (p > name) {
		n = nameTable->Id(name, p - name);
		cur = (c == '*') ? dir->vdirs : dir->dirs;
		if (!cur->FindSubDir(n, dir)) {
		    sub = new DirList(n);
		    sub->info->parent = dir;
		    cur->Append(sub);
		    dir = sub->info;
		}
	    }
	    c = *p;
	    name = p + 1;
	}
    }
    return dir;
}

/*
 * class PropPath
 */

PropPath::PropPath () {
    top = &element[0];
    next = nil;
    prev = nil;
}

/*
 * class PropertySheet
 */

PropertySheet::PropertySheet () {
    cur = new PropDir;
    head = new PropPath;
    tail = head;
    head->element[0].dir = cur;
    head->element[0].sibling = false;
    if (propvalues == nil) {
	propvalues = new StringPool;
    }
}

PropertySheet::~PropertySheet () {
    register PropPath* p, * pnext;

    delete cur;
    for (p = head; p != nil; p = pnext) {
	pnext = p->next;
	delete p;
    }
}

boolean PropertySheet::Get (PropertyDef& prop) {
    register PropPath* p;
    register PropPathElement* i;

    /* look for instance.attr */
    p = tail;
    i = tail->top;
    if (i->dir->attrs->FindAttr(prop)) {
	return true;
    }
    /* look for class.attr (if prev path element is class for instance) */
    if (i->sibling) {
	/* prev path element is class -- look for class.attr */
	--i;
	if (i < &p->element[0]) {
	    i = p->prev->top;
	}
	if (i->dir->attrs->FindAttr(prop)) {
	    return true;
	}
    }
    /* look for path*attr */
    for (p = tail; p != nil; p = p->prev) {   /* Segmentberschreitung
                                                 verhindern */
	for (i = p->top; i >= &p->element[0] && (i < (i + 1)); i--) {
	    if (i->dir->vattrs->FindAttr(prop)) {
		return true;
	    }
	}
    }
    prop.value = nil;
    return false;
}

/*
 * Special lookup under current directory for .attr  or *attr.
 */

boolean PropertySheet::GetLocal (PropDir* dir, PropertyDef& prop) {
    if (dir->attrs->FindAttr(prop) || dir->vattrs->FindAttr(prop)) {
	return true;
    }
    prop.value = nil;
    return false;
}

/*
 * Add an empty directory to the property sheet.
 */

PropDir* PropertySheet::MakeDir (const char* path) {
    const char* name = path;
    PropDir* dir = cur;
    DirList* d = (name > path && *(name-1) == '*') ? dir->vdirs : dir->dirs;
    PropertyName n = nameTable->Id(name);
    if (!d->FindSubDir(n, dir)) {
	DirList* sub = new DirList(n);
	sub->info->parent = dir;
	d->Append(sub);
	dir = sub->info;
    }
    return dir;
}

/*
 * Put an attribute in the property sheet, starting the path search
 * at a given directory (typically the root).  If the name is already
 * defined and the "override" parameter is false, then do nothing.
 */

void PropertySheet::DoPut (
    PropDir* root, const char* path, const char* value, const char* type,
    boolean override
) {
    const char* name = path;
    boolean newvalue = override;
    PropDir* dir = root->MakeDirs(name);
    AttrList* alist =
	(name > path && *(name-1) == '*') ? dir->vattrs : dir->attrs;
    PropertyName n = nameTable->Id(name);
    register AttrList* a = (AttrList*)alist->Find(n);
    if (a == nil) {
	a = new AttrList(n);
	alist->Append(a);
	newvalue = true;
    }
    if (newvalue) {
	register const char* v;

	for (v = value; isspace(*v); v++);
	a->value = propvalues->Append(v, strlen(v) + 1);
	a->type = type;
    }
}

/*
 * Look up the path for a subdirectory matching the given name.
 */

PropDir* PropertySheet::Find (PropertyName name) {
    register PropPath* p;
    register PropPathElement* e;
    PropDir* dir;

    dir = nil;
    /* look for path.name */
    if (tail->top->dir->dirs->FindSubDir(name, dir)) {
	return dir;
    }
    /* look for path*name */
    for (p = tail; p != nil; p = p->prev) {  /* Segmentberschreitung
                                                verhindern */
	for (e = p->top; e >= &p->element[0] && (e < (e + 1)); e--) {
	    if (e->dir->vdirs->FindSubDir(name, dir)) {
		return dir;
	    }
	}
    }
    return nil;
}

void PropertySheet::Push (PropDir* dir, boolean b) {
    register PropPath* p;
    register PropPathElement* e;

    p = tail;
    e = p->top + 1;
    if (e >= &p->element[pathClusterSize]) {
	p = new PropPath;
	p->prev = tail;
	tail->next = p;
	tail = p;
	e = p->top;
    } else {
	p->top = e;
    }
    e->dir = dir;
    e->sibling = b;
}

void PropertySheet::Pop () {
    register PropPath* p = tail;
    if (p == nil) {
	/* ignore underflow */
    } else if (p->top > &p->element[0]) {
	p->top -= 1;
    } else {
	tail = p->prev;
	tail->next = nil;
	delete p;
    }
}

PropDir* PropertySheet::Root () {
    return cur;
}

static int line;	/* for error handling */

/*
 * Load a single property from a string containing
 * the name and value, terminated by either a newline or null.
 */

class PropSheetBuf {
public:
    PropSheetBuf(int);
    ~PropSheetBuf();

    char* data() { return _data; }
private:
    char* _data;
    char _smallbuf[256];
};

PropSheetBuf::PropSheetBuf(int n) {
    _data = (n < sizeof(_smallbuf)) ? _smallbuf : new char[n + 1];
}

PropSheetBuf::~PropSheetBuf() {
    if (_data != _smallbuf) {
	delete _data;
    }
}

void PropertySheet::LoadProperty (const char* s) {
    register const char* src;
    register char* dst;
    const char* value;

    for (src = s; *src == ' ' || *src == '\t'; src++);
    if (*src == '#' || *src == '\n' || *src == '\0') {
	return;
    }
    PropSheetBuf buf(strlen(src));
    dst = buf.data();
    value = nil;
    for (; *src != '\n' && *src != '\0'; src++) {
	if (*src == '\\') {
	    ++src;
	    if (*src == 'n') {
		*dst++ = '\n';
	    } else if (*src == '\n') {
		++line;
	    } else {
		*dst++ = *src;
	    }
	} else if (value == nil && *src == ':') {
	    if (dst == buf.data()) {
		fprintf(stderr, "%d: empty path\n", line);
		return;
	    }
	    *dst++ = '\0';
	    value = dst;
	} else {
	    *dst++ = *src;
	}
    }
    *dst = '\0';
    if (value == nil) {
	fprintf(stderr, "%d: missing value for %s\n", line, buf.data());
	return;
    }
    PutLower(buf.data(), value);
}

/*
 * Parse property sheet information from a string.
 */

void PropertySheet::LoadList (const char* data) {
    register const char* p, * start;

    start = data;
    for (p = strchr(data, '\n'); p != nil; p = strchr(p+1, '\n')) {
	if (p > start && *(p-1) != '\\') {
	    LoadProperty(start);
	}
	start = p+1;
    }
}

/*
 * Read a property sheet from a file (e.g., Xdefaults).
 */

boolean PropertySheet::LoadFile (const char* filename) {
    FILE* f;
    char buf[4096];
    register int i;

    if (filename == nil) {
	f = stdin;
    } else {
	f = fopen(filename, "r");
	if (f == nil) {
	    return false;
	}
    }
    line = 0;
    i = 0;
    while (fgets(&buf[i], sizeof(buf) - i, f) != nil) {
	i = strlen(buf);
	if (buf[i-1] == '\n' && buf[i-2] != '\\') {
	    LoadProperty(buf);
	    i = 0;
	} else {
	    if (i == sizeof(buf)) {
		fprintf(stderr, "%s: %d: line too long\n", filename, line);
		return true;
	    }
	}
    }
    return true;
}
