/* $Id$ */
/* Copyright (c) 2011-2019 Pierre Pronchery <khorben@defora.org> */
/* Copyright (c) 2012 Baptiste Daroussin <bapt@FreeBSD.org> */
/* This file is part of DeforaOS Devel configure */
/* All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#include <sys/param.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "../config.h"

#ifndef __unused
# define __unused
#endif
#ifndef MAXPATHLEN
# define MAXPATHLEN	1024 /* XXX */
#endif
#ifndef PREFIX
# define PREFIX		"/usr/local"
#endif
#ifndef PROGNAME
# define PROGNAME	"pkg-config"
#endif

#define PKG_CFLAGS		(1 << 0)
#define PKG_CFLAGS_ONLY_I	(1 << 1)
#define PKG_CFLAGS_ONLY_OTHERS	(1 << 2)
#define PKG_LIBS		(1 << 3)
#define PKG_LIBS_ONLY_L		(1 << 4)
#define PKG_LIBS_ONLY_l		(1 << 5)
#define PKG_LIBS_ONLY_OTHERS	(1 << 6)
#define PKG_DESCRIPTION		(1 << 7)
#define PKG_URL			(1 << 8)
#define PKG_STATIC		(1 << 9)
#define PKG_VERSION		(1 << 10)
#define PKG_EXISTS		(1 << 11)
#define PKG_MODVERSION		(1 << 12)
#define PKG_PRINT_REQUIRES	(1 << 13)
#define PKG_PRINT_REQUIRES_PRIV	(1 << 14)


/* pkg-config without glib without pkg-config without glib without pkg-config */
/* private */
/* types */
typedef struct _ListData {
	void *data;
	void (*freefn)(void *free);
} ListData;

typedef struct _PkgList {
	size_t cap;
	size_t len;
	ListData **data;
} PkgList;

typedef struct _PkgConfigVariable
{
	char * name;
	char * value;
} PkgConfigVariable;

typedef enum {
	GT,
	LT,
	GE,
	LE,
	EQ
} operator;

typedef struct _Pkg
{
	char *pkgname;
	char *name;
	char *version;
	char *description;
	char *url;
	PkgList *cflags;
	PkgList *cflags_private;
	PkgList *libs;
	PkgList *libs_private;
	PkgList *requires;
	PkgList *requires_private;
	PkgList *variables;
} Pkg;

typedef struct _PkgRequires
{
	char *name;
	char *version;
	operator op;
	Pkg *pkg;
} PkgRequires;

typedef struct _PkgConfig
{
	/* variables */
	unsigned int flags;

	PkgList *pc_dirs;
	PkgList *pkgs;
} PkgConfig;


/* prototypes */
static int _pkgconfig(PkgConfig * pc, int pkgc, char * pkgv[]);
static int _pkgconfig_error(int ret, char const * format, ...);

/* lists */
static PkgList * _pkglist_new(PkgList **list);
static void _pkglist_delete(PkgList *list);
static void * _pkglist_get(PkgList *list, size_t i);
static void _pkglist_append(PkgList *list, void *data, void (*freefn)(void *));

/* string */
static int _string_append(char ** string, char const * append);
static int _string_append_length(char ** string, char const * append,
		size_t length);

static int _usage(int brief);


/* functions */
/* pkgconfig */
static Pkg * _pkg_new(Pkg **pkg, char const * pkgname);
static void _pkgconfig_variable_delete(PkgConfigVariable *p);
static FILE * _pkgconfig_open(PkgConfig * pc, char const * pkg);
static int _pkgconfig_parse(PkgConfig * p, FILE * fp);
static int _pkgconfig_parse_directive(PkgConfig * pc, Pkg * p, char const * directive,
		char const * value);
static char * _pkgconfig_parse_substitute(PkgList * v, char const * value);
static int _pkgconfig_parse_variable(PkgConfig * pc, Pkg * p, char const * name,
		char const * value);
static int _pkgconfig_parse_name(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_description(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_version(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_cflags(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_cflags_private(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_libs(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_libs_private(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_requires(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_requires_private(PkgConfig *pc, Pkg *p, char *data);
static int _pkgconfig_parse_requires_generic(PkgConfig *pc, Pkg *p, char *data, PkgList *lst);

struct keys {
	const char *key;
	int (*parse)(PkgConfig *, Pkg *, char *);
} keys [] = {
	{ "Name", _pkgconfig_parse_name  },
	{ "Description", _pkgconfig_parse_description },
	{ "Version", _pkgconfig_parse_version },
	{ "Cflags", _pkgconfig_parse_cflags },
	{ "Cflags.private", _pkgconfig_parse_cflags_private },
	{ "Libs", _pkgconfig_parse_libs },
	{ "Libs.private", _pkgconfig_parse_libs_private },
	{ "Requires", _pkgconfig_parse_requires },
	{ "Requires.private", _pkgconfig_parse_requires_private },
	{ NULL, NULL },
};

static PkgRequires * _pkgrequires_new(PkgRequires **p, char *name)
{
	if (*p != NULL)
		return *p;

	if ((*p = malloc(sizeof(PkgRequires))) == NULL) {
		_pkgconfig_error(1, "%s", strerror(errno));
		return NULL;
	}

	(*p)->name = strdup(name);
	(*p)->op = EQ;
	(*p)->version = NULL;
	(*p)->pkg = NULL;

	return *p;
}

static void _pkgrequires_delete(void *p1)
{
	PkgRequires *p = (PkgRequires *)p1;

	if (p == NULL)
		return;

	if (p->name != NULL)
		free(p->name);

	if (p->version != NULL)
		free(p->version);
}

static Pkg * _pkg_new(Pkg **pkg, char const * pkgname)
{
	if (*pkg != NULL)
		return *pkg;

	if ((*pkg = malloc(sizeof(Pkg))) == NULL) {
		_pkgconfig_error(1, "%s", strerror(errno));
		return NULL;
	}

	(*pkg)->name = NULL;
	(*pkg)->version = NULL;
	(*pkg)->description = NULL;
	(*pkg)->url = NULL;
	(*pkg)->pkgname = strdup(pkgname);
	(*pkg)->cflags = NULL;
	(*pkg)->cflags_private = NULL;
	(*pkg)->libs = NULL;
	(*pkg)->libs_private = NULL;
	(*pkg)->variables = NULL;
	(*pkg)->requires = NULL;
	(*pkg)->requires_private = NULL;

	return *pkg;
}

static void _pkg_delete(void *pkg)
{
	Pkg *p = (Pkg *)pkg;

	if (p->pkgname != NULL)
		free(p->pkgname);
	if (p->name != NULL)
		free(p->name);
	if (p->version != NULL)
		free(p->version);
	if (p->description != NULL)
		free(p->description);
	if (p->url != NULL)
		free(p->url);

	_pkglist_delete(p->cflags);
	_pkglist_delete(p->cflags_private);
	_pkglist_delete(p->libs);
	_pkglist_delete(p->libs_private);
	_pkglist_delete(p->variables);
	_pkglist_delete(p->requires);
	_pkglist_delete(p->requires_private);
}

static PkgConfigVariable *_pkgconfig_variable_new(PkgConfigVariable **p)
{
	if (*p != NULL)
		return *p;

	if ((*p = malloc(sizeof(PkgConfigVariable))) == NULL)
	{
		_pkgconfig_error(1, "%s", strerror(errno));
		return NULL;
	}

	(*p)->name = NULL;
	(*p)->value = NULL;

	return *p;
}

static void _pkgconfig_variable_delete(PkgConfigVariable *p)
{
	free(p->name);
	free(p->value);
	free(p);
}
static int _pkgconfig_parse_name(__unused PkgConfig *pc, Pkg *p, char *data)
{
	int ret = 0;

	if (p->name != NULL)
		free(p->name);

	p->name = strdup(data);

	return ret;
}

static int _pkgconfig_parse_description(PkgConfig *pc, Pkg *p, char *data)
{
	int ret = 0;

	if ((pc->flags & PKG_DESCRIPTION) == 0)
		return ret;

	if (p->description != NULL)
		free(p->description);

	p->description = strdup(data);

	return ret;
}

static int _pkgconfig_parse_version(PkgConfig *pc, Pkg *p, char *data)
{
	int ret = 0;

	if ((pc->flags & PKG_MODVERSION) == 0)
		return ret;

	if (p->version != NULL)
		free(p->version);

	p->version = strdup(data);

	return ret;
}

static int split_chr(char *str, char sep)
{
	char *next;
	char *buf = str;
	int nbel = 0;

	while ((next = strchr(buf, sep)) != NULL) {
		nbel++;
		buf = next;
		buf[0] = '\0';
		buf++;
	}

	return nbel;
}

static void _pkgconfig_add_to_list(PkgList *lst, char *data)
{
	size_t k;
	/* do not append something already appended */
	for (k = 0; k < lst->len; k++)
		if (strcmp((char *)_pkglist_get(lst, k), data) == 0)
			break;
	if (k == lst->len)
		_pkglist_append(lst, strdup(data), free);
}

static int _pkgconfig_parse_generic(PkgList *lst, char *data, unsigned int flags, unsigned int type)
{
	int ret = 0;
	int i;
	int nbel = 0;
	size_t next;
	char *walk;

	nbel = split_chr(data, ' ');

	next = strlen(data);
	walk = data;
	for (i = 0; i <= nbel; i++) {
		if (next != 0) {
			if (type == PKG_CFLAGS) {
				if (flags & PKG_CFLAGS_ONLY_I) {
					if (strncmp(walk, "-I", 2) == 0)
						_pkgconfig_add_to_list(lst, walk);
				} else if ((flags & PKG_CFLAGS_ONLY_OTHERS) == PKG_CFLAGS_ONLY_OTHERS) {
					if (strncmp(walk, "-I", 2) != 0)
						_pkgconfig_add_to_list(lst, walk);
				} else {
					_pkgconfig_add_to_list(lst, walk);
				}
			}

			if (type == PKG_LIBS) {
				if (flags & PKG_LIBS_ONLY_l) {
					if (strncmp(walk, "-l", 2) == 0)
						_pkgconfig_add_to_list(lst, walk);
				} else if (flags & PKG_LIBS_ONLY_L) {
					if (strncmp(walk, "-L", 2) == 0)
						_pkgconfig_add_to_list(lst, walk);
				} else if (flags & PKG_LIBS_ONLY_OTHERS) {
					if (strncmp(walk, "-L", 2) != 0 && strncmp(walk, "-l", 2) != 0)
						_pkgconfig_add_to_list(lst, walk);
				} else {
					_pkgconfig_add_to_list(lst, walk);
				}
			}
		}
		if (i != nbel) {
			walk += next + 1;
			next = strlen(walk);
		}
	}
	
	return ret;
}

static int _pkgconfig_parse_cflags(PkgConfig *pc, Pkg *p, char *data)
{
	if ((pc->flags & (PKG_CFLAGS|PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHERS)) == 0)
		return 0;

	_pkglist_new(&p->cflags);
	return _pkgconfig_parse_generic(p->cflags, data, pc->flags, PKG_CFLAGS);
}

static int _pkgconfig_parse_cflags_private(PkgConfig *pc, Pkg *p, char *data)
{
	if ((pc->flags & (PKG_CFLAGS|PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHERS)) == 0)
		return 0;

	_pkglist_new(&p->cflags_private);
	return _pkgconfig_parse_generic(p->cflags_private, data, pc->flags, PKG_CFLAGS);
}

static int _pkgconfig_parse_libs(PkgConfig *pc, Pkg *p, char *data)
{
	if ((pc->flags & (PKG_LIBS|PKG_LIBS_ONLY_L|PKG_LIBS_ONLY_l|PKG_LIBS_ONLY_OTHERS)) == 0)
		return 0;

	_pkglist_new(&p->libs);
	return _pkgconfig_parse_generic(p->libs, data, pc->flags, PKG_LIBS);
}

static int _pkgconfig_parse_libs_private(PkgConfig *pc, Pkg *p, char *data)
{
	if ((pc->flags & (PKG_LIBS|PKG_LIBS_ONLY_L|PKG_LIBS_ONLY_l|PKG_LIBS_ONLY_OTHERS)) == 0)
		return 0;

	_pkglist_new(&p->libs_private);
	return _pkgconfig_parse_generic(p->libs_private, data, pc->flags, PKG_LIBS);
}

static int _pkgconfig_parse_requires_private(PkgConfig *pc ,Pkg *p, char *data)
{
	if (pc->flags & PKG_MODVERSION)
		return 0;

	_pkglist_new(&p->requires_private);
	return _pkgconfig_parse_requires_generic(pc, p, data, p->requires_private);
}

static int _pkgconfig_parse_requires(PkgConfig *pc, Pkg *p, char *data)
{
	if (pc->flags & PKG_MODVERSION)
		return 0;

	_pkglist_new(&p->requires);
	return _pkgconfig_parse_requires_generic(pc, p, data, p->requires);
}

static void * _pkglist_lookup(PkgList *list, void *data, int (*compar)(const char *, const char *))
{
	size_t i;

	for (i = 0; i < list->len; i++)
		if (compar(_pkglist_get(list, i), data) == 0)
			return _pkglist_get(list, i);

	return NULL;
}

static void printout(PkgList *list, char * toprint)
{
	if (_pkglist_lookup(list, toprint, strcmp) != NULL)
		return;

	printf("%s ", toprint);
	_pkglist_append(list, toprint, NULL);
}

static int _pkgconfig(PkgConfig * pc, int pkgc, char * pkgv[])
{
	int ret = 0;
	PkgList *printed = NULL;
	char out = '\0';

	char const format[] = "Package %s was not found in the " PROGNAME
		" search path.\n"
		"Perhaps you should add the directory containing `%s.pc'\n"
		"to the PKG_CONFIG_PATH environment variable\n"
		"No package '%s' found\n";
	char const * libdir, * libpath;
	int i;
	size_t j, k;
	FILE * fp;
	Pkg *p;

	/* default values */
	_pkglist_append(pc->pc_dirs, "/usr/lib/pkgconfig", NULL);
	_pkglist_append(pc->pc_dirs, "/usr/libdata/pkgconfig", NULL);
	_pkglist_append(pc->pc_dirs, PREFIX"/libdata/pkgconfig", NULL);
	_pkglist_append(pc->pc_dirs, PREFIX"/lib/pkgconfig", NULL);
	_pkglist_append(pc->pc_dirs, PREFIX"/share/pkgconfig", NULL);

	/* environment variables */
	libdir = getenv("PKG_CONFIG_LIBDIR");
	libpath = getenv("PKG_CONFIG_PATH");
	_pkglist_new(&printed);

	/* TODO parse libpath and libdir */
	if (libpath != NULL) {
		_pkglist_delete(pc->pc_dirs);
	} else if (libdir != NULL) {
		_pkglist_delete(pc->pc_dirs);
	}
	/* packages */
	for(i = 0; i < pkgc; i++)
	{
		/* Do not try to load already loaded pkgs */
		for (j = 0; j < pc->pkgs->len; j++)
			if (strcmp(((Pkg *) _pkglist_get(pc->pkgs, j))->pkgname, pkgv[i]) == 0)
				break;

		if (j < pc->pkgs->len)
			continue;

		if ((fp = _pkgconfig_open(pc, pkgv[i])) != NULL)
		{
			if ((pc->flags & PKG_EXISTS) == 0)
				ret |= _pkgconfig_parse(pc, fp);
			fclose(fp);
		} else {
			if ((pc->flags & PKG_EXISTS) == 0)
				return _pkgconfig_error(1, format, pkgv[i], pkgv[i], pkgv[i]);
			else
				return 1;
		}
	}
	if (pc->flags & PKG_MODVERSION) {
		for (j = 0; j < pc->pkgs->len; j++) {
			p = (Pkg*)_pkglist_get(pc->pkgs, j);
			printf("%s\n",p->version);
		}
		_pkglist_delete(printed);
		return 0;
	}
	if (pc->flags & (PKG_PRINT_REQUIRES|PKG_PRINT_REQUIRES_PRIV)) {
		for (j = 0; j < pc->pkgs->len; j++) {
			p = (Pkg *)_pkglist_get(pc->pkgs, j);
			if (pc->flags & PKG_PRINT_REQUIRES && p->requires != NULL)
				for (k = 0; k < p->requires->len; k++)
					printf("%s\n", ((PkgRequires *)_pkglist_get(p->requires, k))->name);
			if (pc->flags & PKG_PRINT_REQUIRES_PRIV && p->requires_private != NULL)
				for (k = 0; k < p->requires_private->len; k++)
					printf("%s\n", ((PkgRequires *)_pkglist_get(p->requires_private, k))->name);
		}
		_pkglist_delete(printed);
		return 0;
	}
	if (pc->flags & (PKG_CFLAGS|PKG_CFLAGS_ONLY_OTHERS|PKG_CFLAGS_ONLY_I)) {
		for (j = 0; j < pc->pkgs->len; j++) {
			p = (Pkg*)_pkglist_get(pc->pkgs, j);
			if (p->cflags == NULL)
				continue;
			for (k = 0; k < p->cflags->len; k++) {
				printout(printed, (char *)_pkglist_get(p->cflags, k));
				out='\n';
			}
		}
	}
	if (pc->flags & (PKG_LIBS|PKG_LIBS_ONLY_L|PKG_LIBS_ONLY_l|PKG_LIBS_ONLY_OTHERS)) {
		for (j = 0; j < pc->pkgs->len; j++) {
			p = (Pkg *)_pkglist_get(pc->pkgs, j);
			if (p->libs == NULL)
				continue;
			for (k = 0; k < p->libs->len; k++) {
				printout(printed, (char *)_pkglist_get(p->libs, k));
				out='\n';
			}
			if (pc->flags & PKG_STATIC && p->libs_private != NULL) {
				for (k = 0; k < p->libs_private->len; k++) {
					printout(printed, (char *)_pkglist_get(p->libs_private, k));
					out='\n';
				}
			}
		}
	}
	printf("%c", out);
	_pkglist_delete(printed);
	return ret;
}

static FILE * _pkgconfig_open(PkgConfig *pc, char const * pkg)
{
	FILE * fp;
	size_t i;
	char path[MAXPATHLEN];
	Pkg *p = NULL;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, pkg);
#endif
	for (i = 0; i < pc->pc_dirs->len; i++)
	{
		snprintf(path, sizeof(path), "%s/%s.pc",
				(char *)_pkglist_get(pc->pc_dirs, i), pkg);
		if ((fp = fopen(path, "r")) != NULL) {
			p = _pkg_new(&p, pkg);
			_pkglist_append(pc->pkgs, p, _pkg_delete);
			return fp;
		}
	}
	return NULL;
}

static int _pkgconfig_parse(PkgConfig * pc, FILE * fp)
{
	Pkg *p;
	char * line;
	const size_t len = 256;
	size_t i;

	p = _pkglist_get(pc->pkgs, pc->pkgs->len - 1);

	if((line = malloc(len)) == NULL)
		return -_pkgconfig_error(1, "%s", strerror(errno));
	while(fgets(line, len, fp) != NULL)
	{
		i = strlen(line);
		if(line[i - 1] != '\n')
			return -_pkgconfig_error(1, "%s: %s", p->pkgname,
					"Line too long");
		line[i - 1] = '\0';
		/* detect empty lines or comments */
		for(i = 0; line[i] != '\0'
				&& isspace((unsigned char)line[i]); i++);
		if(line[i] == '\0' || line[i] == '#')
			continue;
		/* look for a '=' or a ':' in the line */
		for(i = 0; line[i] != '\0'
				&& (isalnum((unsigned char)line[i])
					|| line[i] == '_' || line[i] == '.');
				i++);
		if(line[i] == '=')
		{
			line[i] = '\0';
			for(i += 1; line[i] != '\0'
					&& isspace((unsigned char)line[i]);
					i++);
			if (line[i] == '\0')
				continue;
			if(_pkgconfig_parse_variable(pc, p, line, &line[i])
					!= 0)
				return -1;
		}
		else if(line[i] == ':')
		{
			line[i] = '\0';
			for(i += 1; line[i] != '\0'
					&& isspace((unsigned char)line[i]);
					i++);
			if (line[i] == '\0')
				continue;
			if(_pkgconfig_parse_directive(pc, p, line, &line[i])
					!= 0)
				return -1;
		}
#ifdef DEBUG
		else
			fprintf(stderr, "DEBUG: %s\n", line);
#endif
	}
	free(line);
	return 0;
}

static int _pkgconfig_parse_directive(PkgConfig * pc, Pkg *pkg, char const * directive,
		char const * value)
{
	int ret = 0;
	char * p;
	int i = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, directive,
			value);
#endif
	if((p = _pkgconfig_parse_substitute(pkg->variables, value)) == NULL)
		return -1;

	for (i = 0; keys[i].key != NULL; i++) {
		if (strcmp(directive, keys[i].key) == 0) {
			keys[i].parse(pc, pkg, p);
		}
	}
	free(p);
	return ret;
}

static int _pkgconfig_parse_requires_generic(PkgConfig *pc, Pkg * pkg, char * requires, PkgList *lst)
{
	int ret = 0;
	int i;
	size_t j;
	char * p;
	Pkg *reqp;
	FILE * fp;
	int expect_version = 0;
	operator op;
	int nbel;
	char *walk;
	size_t next;
	PkgRequires *req = NULL;

	if (pc->flags & PKG_MODVERSION)
		return 0;

	if (pc->flags & (PKG_LIBS|PKG_LIBS_ONLY_L|PKG_LIBS_ONLY_l|PKG_LIBS_ONLY_L) && (pc->flags & PKG_STATIC) == 0)
		return 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, requires);
#endif

	if((p = _pkgconfig_parse_substitute(pkg->variables, requires)) == NULL)
		return -1;

	nbel = split_chr(p, ' ');
	nbel += split_chr(p, '\t');
	nbel += split_chr(p, ',');

	next = strlen(p);
	walk = p;
	for (i = 0; i <= nbel; i++) {
		if (next == 0) {
			walk += next + 1;
			next = strlen(walk);
			continue;
		}

		if (walk[0] == '>') {
			if (req == NULL)
				return -_pkgconfig_error(1, "malformed entry");
			op = GT;
			if (walk[0] == '=')
				op = GE;
			expect_version = 1;
		} else if (walk[0] == '<') {
			if (req == NULL)
				return -_pkgconfig_error(1, "malformed entry");
			op = LT;
			if (walk[0] == '=')
				op = LE;
			expect_version = 1;
		} else if (walk[0] == '=') {
			if (req == NULL)
				return -_pkgconfig_error(1, "malformed entry");
			op = EQ;
			expect_version = 1;
		} else {
			if (!expect_version) {
				req = NULL;
				_pkgrequires_new(&req, walk);
				for (j = 0; j < pc->pkgs->len; j++) {
					reqp = (Pkg*)_pkglist_get(pc->pkgs, j);
					if (strcmp(reqp->pkgname, walk) == 0) {
						req->pkg = reqp;
						break;
					}
				}
				_pkglist_append(lst, req, _pkgrequires_delete);
				if (req->pkg == NULL) {
					if ((fp = _pkgconfig_open(pc, req->name)) != NULL) {
						if ((pc->flags & (PKG_PRINT_REQUIRES|PKG_PRINT_REQUIRES_PRIV)) == 0) {
							ret |= _pkgconfig_parse(pc, fp);
							reqp = (Pkg *)_pkglist_get(pc->pkgs, pc->pkgs->len -1);
							req->pkg = reqp;
						}
						fclose(fp);
					} else {
						ret |= _pkgconfig_error(1, "%s: %s", req->name, strerror(errno));
					}
				}
			} else {
				req->version = strdup(walk);
				expect_version = 0;
			}
		}
		walk += next + 1;
		next = strlen(walk);
	}
	free(p);
	return ret;
}

static char * _pkgconfig_parse_substitute(PkgList * vars, char const * value)
{
	char * ret = NULL;
	PkgConfigVariable *var = NULL;
	size_t i;
	size_t j;
	size_t k;

	for(i = 0; value[i] != '\0'; i++)
	{
		if(value[i] != '$' || value[i + 1] != '{')
		{
			/* XXX not efficient */
			if(_string_append_length(&ret, &value[i], 1) != 0)
			{
				free(ret);
				return NULL;
			}
			continue;
		}
		for(j = i + 2; value[j] != '\0' && value[j] != '}'; j++);
		if(value[j] != '}')
		{
			free(ret);
			return NULL;
		}
		for(k = 0; k < vars->len; k++) {
			var = (PkgConfigVariable *)_pkglist_get(vars, k);
			if(strncmp(var->name, &value[i + 2],
						j - i - 2) == 0)
				break;
			var = NULL;
		}
		if(var == NULL)
		{
			/* FIXME report error */
			free(ret);
			return NULL;
		}
		if(_string_append(&ret, var->value) != 0)
		{
			free(ret);
			return NULL;
		}
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s => %s\n", var->name, var->value);
#endif
		i = j;
	}
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() => \"%s\"\n", __func__, ret);
#endif
	return ret;
}

static int _pkgconfig_parse_variable(__unused PkgConfig * pc, Pkg *pkg, char const * name,
		char const * value)
{
	PkgConfigVariable * p = NULL;
	char * q;
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, name, value);
#endif

	if((q = _pkgconfig_parse_substitute(pkg->variables, value)) == NULL)
		return -1;

	_pkglist_new(&pkg->variables);

	/* check if the variable already exists */
	for(i = 0; i < pkg->variables->len; i++)
	{
		p = (PkgConfigVariable *)_pkglist_get(pkg->variables, i);
		if(strcmp(p->name, name) == 0)
		{
			free(p->value);
			p->value = q;
			return 0;
		}
	}

	/* allocate a new variable */
	p = NULL;
	p = _pkgconfig_variable_new(&p);

	if((p->name = strdup(name)) == NULL)
	{
		_pkgconfig_variable_delete(p);
		return -_pkgconfig_error(1, "%s", strerror(errno));
	}
	p->value = q;
	/* TODO cleanup function */
	_pkglist_append(pkg->variables, p, NULL);
	return 0;
}


/* pkgconfig_error */
static int _pkgconfig_error(int ret, char const * format, ...)
{
	va_list ap;

	fputs(PROGNAME ": ", stderr);
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fputc('\n', stderr);
	return ret;
}


/* string_append */
static int _string_append(char ** string, char const * append)
{
	size_t slen = (*string != NULL) ? strlen(*string) : 0;
	size_t alen = (append != NULL) ? strlen(append) : 0;
	char * p;

	if(alen == 0)
		return 0;
	if((p = realloc(*string, slen + alen + 1)) == NULL)
		return -_pkgconfig_error(1, "%s", strerror(errno));
	*string = p;
	strcpy(*string + slen, append);
	return 0;
}


/* string_append_length */
static int _string_append_length(char ** string, char const * append,
		size_t length)
{
	size_t slen = (*string != NULL) ? strlen(*string) : 0;
	size_t alen = (append != NULL) ? length : 0;
	char * p;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %zu)\n", __func__, *string,
			append, length);
#endif
	if(alen == 0)
		return 0;
	if((p = realloc(*string, slen + alen + 1)) == NULL)
		return -_pkgconfig_error(1, "%s", strerror(errno));
	*string = p;
	strncpy(*string + slen, append, alen);
	(*string)[slen + alen] = '\0';
	return 0;
}

/* lists */
static PkgList * _pkglist_new(PkgList ** pkglist)
{
	if (*pkglist != NULL)
		return *pkglist;

	if((*pkglist = malloc(sizeof(PkgList))) == NULL)
	{
		_pkgconfig_error(1, "%s", strerror(errno));
		return NULL;
	}
	(*pkglist)->len = 0;
	(*pkglist)->cap = 0;
	(*pkglist)->data = NULL;

	return *pkglist;
}

static void * _pkglist_get(PkgList *pkglist, size_t i)
{
	if (i >= pkglist->len)
		return NULL;

	return pkglist->data[i]->data;
}

static void _pkglist_delete(PkgList *pkglist)
{
	size_t i;

	if (pkglist == NULL)
		return;

	for (i = 0; i < pkglist->len; i++) {
		if (pkglist->data[i]->freefn != NULL)
			pkglist->data[i]->freefn(_pkglist_get(pkglist,i));
	}

	free(pkglist);
}

static void  _pkglist_append(PkgList *pkglist, void *data, void (*freefn)(void *))
{
	ListData *ldata;
	if (pkglist->cap <= pkglist->len) {
		pkglist->cap |= 1;
		pkglist->cap *= 2;
		if ((pkglist->data = realloc(pkglist->data, pkglist->cap * sizeof (ListData))) == NULL) {
			_pkgconfig_error(1, "%s", strerror(errno));
			return;
		}
	}
	if ((ldata = malloc(sizeof(ListData))) == NULL)
	{
		_pkgconfig_error(1, "%s", strerror(errno));
		return;
	}

	ldata->data = data;
	ldata->freefn = freefn;
	pkglist->data[pkglist->len++] = ldata;
}


/* usage */
static int _usage(int brief)
{
	if(brief)
		fputs("Usage: " PROGNAME " [-?] [--version] [--modversion]\n",
				stderr);
	else
		fputs("Usage: " PROGNAME " [OPTIONS...] [PACKAGES...]\n"
"  --cflags			Output all pre-processor and compiler flags\n"
"  --cflags-only-I		Output -I flags\n"
"  --cflags-only-other		Output non -I flags\n"
"  --libs			Output all linker flags\n"
"  --libs_only_L		Ouput -L flags\n"
"  --libs_only_l		Ouput -l flags\n"
"  --libs_only_other		other libs (e.g. -pthread)\n"
"  --modversion			Output version for package\n"
"  --print-requires		Output the requires packages\n"
"  --print-requires_private	Output the requires private packages\n"
"  --static			Output linker flags for static linking\n"
"  --version			Output version of " PROGNAME "\n"
"\n"
"Help options:\n"
"  -?, --help		Show this help message\n"
"  --usage		Display brief usage message\n", stderr);
	return 1;
}


/* main */
static int _main_option(PkgConfig * pc, char const * option);
static int _main_option_cflags(PkgConfig * pc);
static int _main_option_cflags_only_I(PkgConfig * pc);
static int _main_option_cflags_only_other(PkgConfig * pc);
static int _main_option_exists(PkgConfig * pc);
static int _main_option_help(PkgConfig * pc);
static int _main_option_libs(PkgConfig * pc);
static int _main_option_libs_only_l(PkgConfig * pc);
static int _main_option_libs_only_L(PkgConfig * pc);
static int _main_option_libs_only_other(PkgConfig * pc);
static int _main_option_static(PkgConfig * pc);
static int _main_option_usage(PkgConfig * pc);
static int _main_option_version(PkgConfig * pc);
static int _main_option_modversion(PkgConfig * pc);
static int _main_option_print_requires(PkgConfig * pc);
static int _main_option_print_requires_private(PkgConfig * pc);
static struct
{
	char const * option;
	int (*callback)(PkgConfig * pc);
} _main_options[] = {
	{ "cflags",			_main_option_cflags			},
	{ "cflags-only-I",		_main_option_cflags_only_I		},
	{ "cflags-only-other",		_main_option_cflags_only_other		},
	{ "exists",			_main_option_exists			},
	{ "help",			_main_option_help			},
	{ "libs",			_main_option_libs			},
	{ "libs-only-l",		_main_option_libs_only_l		},
	{ "libs-only-L",		_main_option_libs_only_L		},
	{ "libs-only-other",		_main_option_libs_only_other		},
	{ "modversion",			_main_option_modversion			},
	{ "print-requires",		_main_option_print_requires		},
	{ "print-requires-private",	_main_option_print_requires_private	},
	{ "static",			_main_option_static			},
	{ "usage",			_main_option_usage			},
	{ "version",			_main_option_version			}
};

int main(int argc, char * argv[])
{
	PkgConfig pc;
	int optind;

	memset(&pc, 0, sizeof(pc));
	_pkglist_new(&pc.pkgs);
	_pkglist_new(&pc.pc_dirs);
	pc.flags = '\0';
	/* getopt() is too complicated for GNU */
	/* XXX stupid GNU accepts options even after actual arguments */
	/* -- khorben: no want fix it cause this meant as troll */
	for(optind = 1; optind < argc; optind++)
	{
		if(strcmp(argv[optind], "-?") == 0)
			return _usage(0);
		if(strncmp(argv[optind], "--", 2) != 0)
			break;
		if(argv[optind][2] == '\0')
		{
			optind++;
			break;
		}
		if(_main_option(&pc, argv[optind]) != 0)
			return 1;
	}

	/* check if any package was specified */
	if(optind == argc)
	{
		fputs("Must specify package names on the command line\n",
				stderr);
		return 1;
	}
	return _pkgconfig(&pc, argc - optind, &argv[optind]);
}

static int _main_option(PkgConfig * pc, char const * option)
{
	size_t i;

	for(i = 0; i < sizeof(_main_options) / sizeof(*_main_options); i++)
		if(strcmp(_main_options[i].option, &option[2]) == 0)
			return _main_options[i].callback(pc);
	fprintf(stderr, "%s: Unknown option\n", option);
	return 1;
}

static int _main_option_cflags(PkgConfig * pc)
{
	pc->flags |= PKG_CFLAGS;
	return 0;
}

static int _main_option_cflags_only_I(PkgConfig * pc)
{
	pc->flags |= PKG_CFLAGS_ONLY_I;
	return 0;
}

static int _main_option_cflags_only_other(PkgConfig * pc)
{
	pc->flags |= PKG_CFLAGS_ONLY_OTHERS;
	return 0;
}

static int _main_option_exists(PkgConfig * pc)
{
	pc->flags |= PKG_EXISTS;
	return 0;
}

static int _main_option_help(__unused PkgConfig * pc)
{
	return _usage(0);
}

static int _main_option_libs(PkgConfig * pc)
{
	pc->flags |= PKG_LIBS;
	return 0;
}

static int _main_option_libs_only_L(PkgConfig * pc)
{
	pc->flags |= PKG_LIBS_ONLY_L;
	return 0;
}

static int _main_option_libs_only_l(PkgConfig * pc)
{
	pc->flags |= PKG_LIBS_ONLY_l;
	return 0;
}

static int _main_option_libs_only_other(PkgConfig * pc)
{
	pc->flags |= PKG_LIBS_ONLY_OTHERS;
	return 0;
}

static int _main_option_modversion(PkgConfig * pc)
{
	pc->flags |= PKG_MODVERSION;
	return 0;
}

static int _main_option_print_requires(PkgConfig * pc)
{
	pc->flags |= PKG_PRINT_REQUIRES;
	return 0;
}

static int _main_option_print_requires_private(PkgConfig * pc)
{
	pc->flags |= PKG_PRINT_REQUIRES_PRIV;
	return 0;
}

static int _main_option_static(PkgConfig * pc)
{
	pc->flags |= PKG_STATIC;
	return 0;
}

static int _main_option_usage(__unused PkgConfig * pc)
{
	return _usage(1);
}

static int _main_option_version(__unused PkgConfig * pc)
{
	puts("0.26");
	exit(0);
	return -1;
}
