#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

#include "header.h"
#include "rpmlib.h"
#include "part.h"
#include "spec.h"
#include "parse.h"
#include "read.h"
#include "misc.h"

static int checkSources(Spec spec);
static void setStandardMacros(Spec spec, char *arch, char *os);

int parseSpec(Spec *specp, char *specFile, char *buildRoot,
	      int inBuildArch, char *passPhrase, char *cookie)
{
    int parsePart = PART_PREAMBLE;
    int initialPackage = 1;
    char *name, *arch, *os;
    char *saveArch;
    Package pkg;
    int x, index;
    Spec spec;
    
    /* Set up a new Spec structure with no packages. */
    spec = newSpec();

    spec->specFile = strdup(specFile);
    if (buildRoot) {
	spec->gotBuildRoot = 1;
	spec->buildRoot = strdup(buildRoot);
    }
    spec->docDir = strdup(rpmGetVar(RPMVAR_DEFAULTDOCDIR));
    spec->inBuildArchitectures = inBuildArch;
    if (passPhrase) {
	spec->passPhrase = strdup(passPhrase);
    }
    if (cookie) {
	spec->cookie = strdup(cookie);
    }

    if (rpmGetVar(RPMVAR_TIMECHECK)) {
	if (parseNum(rpmGetVar(RPMVAR_TIMECHECK), &(spec->timeCheck))) {
	    rpmError(RPMERR_BADSPEC, "Timecheck value must be an integer: %s",
		     rpmGetVar(RPMVAR_TIMECHECK));
	    freeSpec(spec);
	    return RPMERR_BADSPEC;
	}
    } else {
	spec->timeCheck = 0;
    }

    /* Add some standard macros */
    rpmGetArchInfo(&arch, NULL);
    rpmGetOsInfo(&os, NULL);
    setStandardMacros(spec, arch, os);

    /* All the parse*() functions expect to have a line pre-read */
    /* in the spec's line buffer.  Except for parsePreamble(),   */
    /* which handles the initial entry into a spec file.         */
    
    while (parsePart != PART_NONE) {
	switch (parsePart) {
	  case PART_PREAMBLE:
	    parsePart = parsePreamble(spec, initialPackage);
	    initialPackage = 0;
	    break;
	  case PART_PREP:
	    parsePart = parsePrep(spec);
	    break;
	  case PART_BUILD:
	  case PART_INSTALL:
	  case PART_CLEAN:
	    parsePart = parseBuildInstallClean(spec, parsePart);
	    break;
	  case PART_CHANGELOG:
	    parsePart = parseChangelog(spec);
	    break;
	  case PART_DESCRIPTION:
	    parsePart = parseDescription(spec);
	    break;

	  case PART_PRE:
	  case PART_POST:
	  case PART_PREUN:
	  case PART_POSTUN:
	  case PART_VERIFYSCRIPT:
	  case PART_TRIGGERIN:
	  case PART_TRIGGERUN:
	  case PART_TRIGGERPOSTUN:
	    parsePart = parseScript(spec, parsePart);
	    break;

	  case PART_FILES:
	    parsePart = parseFiles(spec);
	    break;

	}

	if (parsePart < 0) {
	    freeSpec(spec);
	    return parsePart;
	}

	if (parsePart == PART_BUILDARCHITECTURES) {
	    spec->buildArchitectureSpecs =
		malloc(sizeof(Spec) * spec->buildArchitectureCount);
	    x = 0;
	    index = 0;
	    while (x < spec->buildArchitectureCount) {
		if (rpmMachineScore(RPM_MACHTABLE_BUILDARCH,
				    spec->buildArchitectures[x])) {
		    rpmGetMachine(&saveArch, NULL);
		    saveArch = strdup(saveArch);
		    rpmSetMachine(spec->buildArchitectures[x], NULL);
		    if (parseSpec(&(spec->buildArchitectureSpecs[index]),
				  specFile, buildRoot, 1,
				  passPhrase, cookie)) {
			spec->buildArchitectureCount = index;
			freeSpec(spec);
			return RPMERR_BADSPEC;
		    }
		    rpmSetMachine(saveArch, NULL);
		    free(saveArch);
		    index++;
		}
		x++;
	    }
	    spec->buildArchitectureCount = index;
	    if (! index) {
		freeSpec(spec);
		rpmError(RPMERR_BADSPEC, "No buildable architectures");
		return RPMERR_BADSPEC;
	    }
	    closeSpec(spec);
	    *specp = spec;
	    return 0;
	}
    }

    /* Check for description in each package and add arch and os */
    pkg = spec->packages;
    while (pkg) {
	headerGetEntry(pkg->header, RPMTAG_NAME, NULL, (void **) &name, NULL);
	if (!headerIsEntry(pkg->header, RPMTAG_DESCRIPTION)) {
	    rpmError(RPMERR_BADSPEC, "Package has no %%description: %s", name);
	    freeSpec(spec);
	    return RPMERR_BADSPEC;
	}

	headerAddEntry(pkg->header, RPMTAG_OS, RPM_STRING_TYPE, os, 1);
	headerAddEntry(pkg->header, RPMTAG_ARCH, RPM_STRING_TYPE, arch, 1);

	pkg = pkg->next;
    }

    closeSpec(spec);
    *specp = spec;

    if (checkSources(spec)) {
	freeSpec(spec);
	return 1;
    }

    return 0;
}

static int checkSources(Spec spec)
{
    struct Source *p;
    Package pkg;
    char buf[BUFSIZ];
    struct stat sb;
    int res = 0;
    
    p = spec->sources;
    while (p) {
	sprintf(buf, "%s/%s", rpmGetVar(RPMVAR_SOURCEDIR), p->source);
	lstat(buf, &sb);
	if (! S_ISREG(sb.st_mode)) {
	    rpmError(RPMERR_BADSPEC, "Source file not regular: %s", buf);
	    res = 1;
	}
	p = p->next;
    }

    pkg = spec->packages;
    while (pkg) {
	p = pkg->icon;
	while (p) {
	    sprintf(buf, "%s/%s", rpmGetVar(RPMVAR_SOURCEDIR), p->source);
	    lstat(buf, &sb);
	    if (! S_ISREG(sb.st_mode)) {
		rpmError(RPMERR_BADSPEC, "Source file not regular: %s", buf);
		res = 1;
	    }
	    p = p->next;
	}
	pkg = pkg->next;
    }

    return res;
}

static void setStandardMacros(Spec spec, char *arch, char *os)
{
    char buf[BUFSIZ];
    int x;

    addMacro(&spec->macros, "sourcedir", rpmGetVar(RPMVAR_SOURCEDIR));
    addMacro(&spec->macros, "builddir", rpmGetVar(RPMVAR_BUILDDIR));
    addMacro(&spec->macros, "optflags", rpmGetVar(RPMVAR_OPTFLAGS));
    addMacro(&spec->macros, "buildarch", arch);
    addMacro(&spec->macros, "buildos", os);
    
    x = 0;
    while (arch[x]) {
	buf[x] = tolower(arch[x]);
	x++;
    }
    buf[x] = '\0';
    addMacro(&spec->macros, "buildarch_lc", buf);
    x = 0;
    while (os[x]) {
	buf[x] = tolower(os[x]);
	x++;
    }
    buf[x] = '\0';
    addMacro(&spec->macros, "buildos_lc", buf);
}
