/*
    scconfig - library for listing files in a directory
    Copyright (C) 2009..2012  Tibor Palinkas

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA

		Project page: http://repo.hu/projects/scconfig
		Contact (email and IRC): http://igor2.repo.hu/contact.html
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "db.h"
#include "libs.h"
#include "log.h"
#include "dep.h"

static void destroy_testdir(int logdepth, char *dir)
{
	const char *rm;
	char *cmd, *dir_esc;

	rm = get("fstools/rm");
	if (rm == NULL) {
		logprintf(logdepth, "CAN NOT delete test directory '%s': no rm available\n", dir);
		return;
	}

	if (dir == NULL)
		return;

	logprintf(logdepth, "deleting test directory '%s'\n", dir);

	cmd = malloc(strlen(dir) + strlen(rm) + 4);
	dir_esc = shell_escape_dup(dir);
	sprintf(cmd, "%s %s", rm, dir_esc);
	run_shell(0, cmd, NULL);
	free(cmd);
	free(dir);
	free(dir_esc);
}

static char *create_testdir(int logdepth)
{
	char *dir, *fn, *cmd;
	const char *mkdir;
	int n;
	logprintf(logdepth, "creating test directory\n");

	dir = tempdir_new(logdepth+1, "");
	logprintf(logdepth, "sandbox is: '%s'\n", dir);

	fn = malloc(strlen(dir) + 32);
	for(n = 0; n < 2; n++) {
		FILE *f;
		sprintf(fn, "%s%sfile%d", dir, get("sys/path_sep"), n+1);
		f = fopen(fn, "w");
		if (f != NULL) {
			fclose(f);
			if (!is_file(fn)) {
				logprintf(logdepth, "Can not create file %s\n", fn);
				free(fn);
				destroy_testdir(logdepth, dir);
				return NULL;
			}
		}
	}

	mkdir = get("fstools/mkdir");

	cmd = malloc(strlen(dir) + 64);
	for(n = 0; n < 2; n++) {
		char *fn_esc;
		sprintf(fn, "%s%sdir%d", dir, get("sys/path_sep"), n+1);
		fn_esc = shell_escape_dup(fn);
		sprintf(cmd, "%s %s", mkdir, fn_esc);
		free(fn_esc);
		if (run_shell(logdepth+1, cmd, NULL) || (!is_dir(fn))) {
			logprintf(logdepth, "Can not create directory %s\n", fn);
			free(fn);
			free(cmd);
			destroy_testdir(logdepth, dir);
			return NULL;
		}
	}
	free(cmd);
	free(fn);
	return dir;
}

static int test(int logdepth, int argc, char *argv[])
{
	int dir[2], file[2], n;
	int *arr, idx;

	for(n = 0; n < 2; n++) {
		dir[n] = 0;
		file[n] = 0;
	}

	/* count the list of files, increase arrays by hit */
	for(n = 0; n < argc; n++) {
		arr =  NULL;
		if (strncmp(argv[n], "dir", 3) == 0)  { arr = dir;  idx = atoi(argv[n]+3); }
		if (strncmp(argv[n], "file", 4) == 0) { arr = file; idx = atoi(argv[n]+4); }
		if (arr == NULL) {
			logprintf(logdepth, "test fails: unknown existing file on the list: '%s'\n", argv[n]);
			return 0;
		}
		idx--;
		if ((idx < 0) || (idx > 1)) {
			logprintf(logdepth, "test fails: file name changed: '%s'\n", argv[n]);
			return 0;
		}
		arr[idx]++;
	}

	/* check if every item was found exactly once */
	for(n = 0; n < 2; n++) {
		if ((dir[n] != 1) || (file[n] != 1)) {
			logprintf(logdepth, "test fails: %s%d not found \n", dir[n] ? "file" : "dir", n);
			return 0;
		}
	}

	return 1;
}

static void filelist_extract(char *out, const char *dir, const char *method, int *argc, char ***argv)
{
	char *s, sep, *start, *end;
	int len, allocated = 0, count = 0;
	char **arr = NULL;
	const char *psep;

	psep = get("sys/path_sep");

	len = strlen(dir);

	/* uniform separator */
	if (*method == 'w') {
		/* if word splitting then convert newlines to spaces and convert tabs to spaces */
		for(s = out; *s != '\0'; s++) {
			if ((*s == '\n') || (*s == '\r') || (*s == '\t'))
				*s = ' ';
		}
		sep = ' ';
	}
	else {
		for(s = out; *s != '\0'; s++) {
			if (*s == '\r')
				*s = '\n';
		}
		sep = '\n';
	}

	start = out;
	while((s = str_chr(start, sep)) != NULL) {
		*s = '\0';
		if (strncmp(dir, start, len) == 0)
			start += len;
		while(*start == *psep)
			start++;

		if (*start != '\0') {
			end = str_chr(start, *psep);
			if (end != NULL)
				*end = '\0';

			/* add only if not the same as previous and exists */
			if ((!((count > 0) && (strcmp(arr[count - 1], start) == 0))) && (exists_in(dir, start))) {

				if (count >= allocated) {
					allocated = count + 32;
					arr = realloc(arr, sizeof(char *) * allocated);
				}
				arr[count] = strclone(start);
				count++;
			}
		}

		start = s+1;
		while(*start == sep) start++;
	}
	*argc = count;
	*argv = arr;
}

void filelist_free(int *argc, char ***argv)
{
	int n;

	if (*argv == NULL)
		return;

	for(n = 0; n < *argc; n++)
		free((*argv)[n]);
	free(*argv);
	*argc = 0;
}

static char *filelist_asmcmd(const char *dir, const char *list_cmd)
{
	char *cmd;

	cmd = malloc(strlen(dir) + strlen(list_cmd) + 32);
	sprintf(cmd, list_cmd, dir);
	return cmd;
}

static int try(int logdepth, const char *dir, const char *list_cmd, const char *method)
{
	char *cmd, *out, *dir_esc;
	int argc, res;
	char **argv;

	dir_esc = shell_escape_dup(dir);
	cmd = filelist_asmcmd(dir_esc, list_cmd);
	free(dir_esc);
	logprintf(logdepth, "trying '%s'...\n", cmd);

	run_shell(logdepth+1, cmd, &out);
	if (out != NULL) {
		filelist_extract(out, dir, method, &argc, &argv);
		res = test(logdepth+1, argc, argv);
		filelist_free(&argc, &argv);
		free(out);
	}

	if (res) {
		logprintf(logdepth+1, "Works.", cmd);
		put("/internal/filelist/cmd", list_cmd);
		put("/internal/filelist/method", method);
		report("OK ('%s' with %s split)\n", list_cmd, method);
	}

	free(cmd);
	return res;
}

int find_filelist(const char *name, int logdepth, int fatal)
{
	char *dir;
	char *old_cwd;
	int ret;

	old_cwd = strclone(db_cwd);
	db_cd("/host");

	require("fstools/mkdir", logdepth, fatal);
	require("fstools/rm", logdepth, fatal);


	report("Checking for filelist... ");
	logprintf(logdepth, "find_filelist: trying to find file listing...\n");
	logdepth++;


	dir = create_testdir(logdepth);
	if (dir == NULL) {
		report("Failed to creat sandbox\n");
		ret = 1;
		goto end;
	}

	if (
			try(logdepth, dir, "ls %s", "line") || /* should return one file name per line since the output is redirected */
			try(logdepth, dir, "ls -1 %s", "line") || /* try to force one file name per line */
			try(logdepth, dir, "ls --format=single-column %s", "line") || /* for gnu ls */
			try(logdepth, dir, "find %s", "line") || /* if ls fails, we try find */
	    try(logdepth, dir, "ls %s", "word") ||   /* if that fails too, ls may still have a list in multiple columns */
	    try(logdepth, dir, "dir %s", "word") ||  /* or we are on windows where we need to use dir maybe */
	    try(logdepth, dir, "echo %s/*", "word")) { /* or on a system without ls, dir or anything alike, but shell globbing may still work */

		destroy_testdir(logdepth, dir);
		ret = 0;
		goto end;
	}

	destroy_testdir(logdepth, dir);
	ret = 1;
	end:;
	db_cd(old_cwd);
	free(old_cwd);
	return ret;
}


void filelist(int logdepth, const char *dir, int *argc, char ***argv)
{
	const char *list_cmd, *method;
	char *cmd, *out, *dir_esc;
	char *old_cwd;

	old_cwd = strclone(db_cwd);
	db_cd("/host");

	/* make sure these are set to invalid for easier return in case we fail anywhere later */
	*argc = -1;
	*argv = NULL;

	if (!is_dir(dir))
		goto end;

	require("/internal/filelist/cmd", logdepth, 1);
	require("/internal/filelist/method", logdepth, 1);

	list_cmd = get("/internal/filelist/cmd");
	method   = get("/internal/filelist/method");

	dir_esc = shell_escape_dup(dir);
	cmd = filelist_asmcmd(dir_esc, list_cmd);
	free(dir_esc);
	run_shell(logdepth+1, cmd, &out);
	if (out != NULL) {
		filelist_extract(out, dir, method, argc, argv);
		logprintf(logdepth, "filelist: Getting list of files in %s\n", dir);
		free(out);
	}

	free(cmd);
	end:;
	db_cd(old_cwd);
	free(old_cwd);
}
