/* $OpenBSD: dump.c,v 1.5 2013/05/22 12:14:08 espie Exp $ */
/*
 * Copyright (c) 2012 Marc Espie.
 *
 * 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 OPENBSD PROJECT 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 OPENBSD
 * PROJECT 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 <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ohash.h>
#include "defines.h"
#include "gnode.h"
#include "dump.h"
#include "targ.h"
#include "var.h"
#include "memory.h"
#include "suff.h"
#include "lst.h"
#include "timestamp.h"
#include "dir.h"

/* since qsort doesn't have user data, this needs to be a global... */
static ptrdiff_t cmp_offset;
static void targ_dump(bool);

static int 
compare_names(const void *a, const void *b)
{
	const char **pa = (const char **)a;
	const char **pb = (const char **)b;
	return strcmp((*pa) + cmp_offset, (*pb) + cmp_offset);
}

void *
sort_ohash_by_name(struct ohash *h)
{
	cmp_offset = h->info.key_offset;

	return sort_ohash(h, compare_names);
}

void *
sort_ohash(struct ohash *h, int (*comparison)(const void *, const void *))
{
	unsigned int i, j;
	void *e;
	size_t n = ohash_entries(h);
	void **t = emalloc(sizeof(void *) * (n+1));
	cmp_offset = h->info.key_offset;

	for (i = 0, e = ohash_first(h, &j); e != NULL; e = ohash_next(h, &j))
	    	t[i++] = e;
	qsort(t, n, sizeof(void *), comparison);
	/* add an extra entry to be able to figure out the end without needing
	 * to keep a counter */
	t[n] = NULL;
	return t;
}

static void
TargPrintName(void *gnp)
{
	GNode *gn = (GNode *)gnp;
	printf("%s ", gn->name);
}

static void
TargPrintOnlySrc(GNode *gn)
{
	if (OP_NOP(gn->type) && gn->special == SPECIAL_NONE &&
	    !(gn->type & OP_DUMMY)) {
	    	if (gn->path != NULL)
			printf("#\t%s [%s]\n", gn->name, 
			    strcmp(gn->path, gn->name) == 0 ? "=" : gn->path);
		else
			printf("#\t%s\n", gn->name);
    	}
}

static void
TargPrintNode(GNode *gn, bool full)
{
	if (OP_NOP(gn->type))
		return;
	switch((gn->special & SPECIAL_MASK)) {
	case SPECIAL_SUFFIXES:
	case SPECIAL_PHONY:
	case SPECIAL_ORDER:
	case SPECIAL_NOTHING:
	case SPECIAL_MAIN:
	case SPECIAL_IGNORE:
		return;
	default:
		break;
	}
	if (full) {
		printf("# %d unmade prerequisites\n", gn->unmade);
		if (! (gn->type & (OP_JOIN|OP_USE|OP_EXEC))) {
			if (!is_out_of_date(gn->mtime)) {
				printf("# last modified %s: %s\n",
				      time_to_string(&gn->mtime),
				      status_to_string(gn));
			} else if (gn->built_status != UNKNOWN) {
				printf("# non-existent (maybe): %s\n",
				    status_to_string(gn));
			} else {
				printf("# unmade\n");
			}
		}
	}
	if (!Lst_IsEmpty(&gn->parents)) {
		printf("# parent targets: ");
		Lst_Every(&gn->parents, TargPrintName);
		fputc('\n', stdout);
	}
	if (gn->impliedsrc)
		printf("# implied prerequisite: %s\n", gn->impliedsrc->name);

	printf("%-16s", gn->name);
	switch (gn->type & OP_OPMASK) {
	case OP_DEPENDS:
		printf(": "); break;
	case OP_FORCE:
		printf("! "); break;
	case OP_DOUBLEDEP:
		printf(":: "); break;
	}
	Targ_PrintType(gn->type);
	Lst_Every(&gn->children, TargPrintName);
	fputc('\n', stdout);
	Lst_Every(&gn->commands, Targ_PrintCmd);
	printf("\n\n");
	if (gn->type & OP_DOUBLEDEP) {
		LstNode ln;

		for (ln = Lst_First(&gn->cohorts); ln != NULL; ln = Lst_Adv(ln))
			TargPrintNode((GNode *)Lst_Datum(ln), full);
	}
}

static void
dump_special(GNode **t, const char *name, int prop)
{
	unsigned int i;
	bool first = true;

	for (i = 0; t[i] != NULL; i++)
		if (t[i]->type & prop) {
			if (first) {
				printf("%s:", name);
				first = false;
			}
			printf(" %s", t[i]->name);
	    	}
	if (!first)
		printf("\n\n");
}

static void
targ_dump(bool full)
{
	GNode **t = sort_ohash_by_name(targets_hash());
	unsigned int i;

	printf("#   Input graph:\n");
	for (i = 0; t[i] != NULL; i++)
		TargPrintNode(t[i], full);
	printf("\n\n");

	dump_special(t, ".PHONY", OP_PHONY);
	dump_special(t, ".PRECIOUS", OP_PRECIOUS);
	dump_special(t, ".SILENT", OP_SILENT);
	dump_special(t, ".IGNORE", OP_IGNORE);
	printf("#   Other target names:\n");
	for (i = 0; t[i] != NULL; i++)
		TargPrintOnlySrc(t[i]);
	printf("\n");
	free(t);
}

static bool dumped_once = false;

void
dump_data(void)
{
	Var_Dump();
	Suff_PrintAll();
	targ_dump(false);
	dumped_once = true;
}

void
post_mortem(void)
{
	if (!dumped_once) {
		Var_Dump();
		Suff_PrintAll();
	}
	targ_dump(true);
}