/*
# This file is part of the Astrometry.net suite.
# Licensed under a 3-clause BSD style license - see LICENSE
*/

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "cutest.h"
#include "ioutils.h"
#include "fileutils.h"
#include "log.h"
#include "tic.h"
#include "md5.h"

void test_run_command_1(CuTest* tc) {
	int rtn;
	sl* outlines = NULL;
	sl* errlines = NULL;
	char* cmd;
	char* str2;
	char txt[10240];
	log_init(3);

	/*
	str1 = "test test\ntest";
	asprintf_safe(&cmd, "echo '%s'", str1);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "\n");
	printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, TRUE, streq(str1, str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	printf("\n\npart 2\n\n");
	cmd = "(for ((i=0; i<1024; i++)); do /bin/echo -n \"X\"; done) | cat -";
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "\n");
	printf("got string: \"%s\"\n", str2);
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);
	 */
	// test 'buffer full with no newline' branch
	// (single read)
	memset(txt, 'X', 1024);
	txt[1024] = '\0';
	asprintf_safe(&cmd, "printf %%s '%s'", txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "\n");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 1, sl_size(outlines));
	CuAssertIntEquals(tc, 1024, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	// test 'buffer full with no newline' branch
	// (two reads)
	memset(txt, 'X', 512);
	txt[512] = '\0';
	asprintf_safe(&cmd, "printf %%s '%s'; printf %%s '%s'", txt, txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "\n");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 1, sl_size(outlines));
	CuAssertIntEquals(tc, 1024, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	// test 'buffer full with no newline' branch
	// and 'flushing the last line'
	// (three reads, not exactly aligned)
	memset(txt, 'X', 500);
	txt[500] = '\0';
	// oops, this is not portable /bin/sh!
	//asprintf_safe(&cmd, "for ((i=0; i<3; i++)); do printf %%s '%s'; done", txt);
	asprintf_safe(&cmd, "for x in 1 2 3; do printf %%s '%s'; done", txt);
	printf("Command: \"%s\"\n", cmd);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	//rtn = run_command_get_outputs(cmd, NULL, NULL);
	//printf("return value: %i\n", rtn);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 2, sl_size(outlines));
	CuAssertIntEquals(tc, 1500, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	memset(txt, 'X', 500);
	txt[499] = '\n';
	txt[500] = '\0';
	//asprintf_safe(&cmd, "for ((i=0; i<3; i++)); do printf %%s '%s'; done", txt);
	asprintf_safe(&cmd, "for x in 1 2 3; do printf %%s '%s'; done", txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "\n");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 3, sl_size(outlines));
	CuAssertIntEquals(tc, 1499, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	// single line ending with newline.
	memset(txt, 'X', 1024);
	txt[1023] = '\n';
	txt[1024] = '\0';
	asprintf_safe(&cmd, "printf %%s '%s'", txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 1, sl_size(outlines));
	CuAssertIntEquals(tc, 1023, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	// multiple lines -- normal
	// written all at once -- hits 'moved N to start of block' branch.
	memset(txt, 'X', 2000);
	txt[200] = '\n';
	txt[400] = '\n';
	txt[600] = '\n';
	txt[1000] = '\n';
	txt[1500] = '\n';
	txt[1800] = '\n';
	txt[1999] = '\n';
	txt[2000] = '\0';
	asprintf_safe(&cmd, "printf %%s '%s'", txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 7, sl_size(outlines));
	CuAssertIntEquals(tc, 1993, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);

	// multiple lines -- normal
	// written all at once -- hits 'moved N to start of block' branch.
	memset(txt, 'X', 200);
	txt[200] = '\n';
	txt[201] = '\0';
	//asprintf_safe(&cmd, "for ((i=0; i<10; i++)); do printf %%s '%s'; sleep 0.1; done", txt);
	asprintf_safe(&cmd, "for x in 1 2 3 4 5 6 7 8 9 10; do printf %%s '%s'; sleep 0.1; done", txt);
	rtn = run_command_get_outputs(cmd, &outlines, &errlines);
	CuAssertIntEquals(tc, 0, rtn);
	str2 = sl_join(outlines, "");
	//printf("got string: \"%s\"\n", str2);
	CuAssertIntEquals(tc, 10, sl_size(outlines));
	CuAssertIntEquals(tc, 10*200, strlen(str2));
	sl_free2(outlines);
	sl_free2(errlines);
	free(str2);
}


void test_run_command(CuTest* tc) {
	int rtn;
	sl* outlines = NULL;
	sl* errlines = NULL;
	char* cmd;
	FILE* fid;
	char* tmpfn;
	int N;
	int i;
	int trial;
	log_init(3);

	tmpfn = create_temp_file("test_run_command", "/tmp");
	CuAssertPtrNotNull(tc, tmpfn);

	for (trial=0; trial<4; trial++) {
		double t0;
		printf("test_ioutils:test_run_command() trial %i\n", trial);
		fid = fopen(tmpfn, "wb");
		CuAssertPtrNotNull(tc, fid);
		N = 102400;
		for (i=0; i<N; i++) {
			int nw;
			char c;
			if (trial == 0) {
				c = random() % 256;
			} else if (trial == 1) {
				c = '\n';
			} else if ((trial == 2) || (trial == 3)) {
				c = 'A';
			}
			nw = fwrite(&c, 1, 1, fid);
			CuAssertIntEquals(tc, 1, nw);
		}
		rtn = fclose(fid);
		CuAssertIntEquals(tc, 0, rtn);

		if (trial == 3) {
			asprintf(&cmd, "sleep 0.2; dd if=%s bs=1k; sleep 0.2", tmpfn);
		} else {
			asprintf(&cmd, "sleep 0.2; dd if=%s bs=1k 1>&2; sleep 0.2", tmpfn);
		}
		t0 = timenow();
		rtn = run_command_get_outputs(cmd, &outlines, &errlines);
		printf("That took %g sec\n", timenow() - t0);
		CuAssertIntEquals(tc, 0, rtn);
		free(cmd);
		sl_free2(outlines);
		sl_free2(errlines);
	}
}

void test_split_long_string(CuTest* tc) {
	sl* lst;
	lst = split_long_string("", 60, 80, NULL);
	CuAssertPtrNotNull(tc, lst);
	CuAssertIntEquals(tc, 0, sl_size(lst));
	sl_free2(lst);

	lst = split_long_string("really long line     that will get broken into"
							" several pieces", 6, 10, NULL);
	CuAssertPtrNotNull(tc, lst);
	//printf("%s\n", sl_join(lst, "<<\n"));
	CuAssertIntEquals(tc, 7, sl_size(lst));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 0), "really"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 1), "long line"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 2), "that will"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 3), "get broken"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 4), "into"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 5), "several"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 6), "pieces"));
	sl_free2(lst);
	
	// Arguable whether this is correct handling of multiple spaces...
	lst = split_long_string("extremely long line     with ridiculously long words necessitating hyphenationizing (?!)",
							6, 10, NULL);
	CuAssertPtrNotNull(tc, lst);
	//printf("%s\n", sl_join(lst, "<<\n"));
	CuAssertIntEquals(tc, 12, sl_size(lst));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 0), "extre-"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 1), "mely long"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 2), "line"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 3), "with"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 4), "ridiculou-"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 5), "sly long"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 6), "words"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 7), "necessita-"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 8), "ting"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 9), "hyphenati-"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 10), "onizing"));
	CuAssertIntEquals(tc, 1, streq(sl_get(lst, 11), "(?!)"));
	sl_free2(lst);
}

void test_streq_1(CuTest* tc) {
    CuAssertIntEquals(tc, 1, streq(NULL, NULL));
    CuAssertIntEquals(tc, 0, streq(NULL, ""));
    CuAssertIntEquals(tc, 0, streq("", NULL));
    CuAssertIntEquals(tc, 1, streq("", ""));
    CuAssertIntEquals(tc, 0, streq("", "a"));
    CuAssertIntEquals(tc, 1, streq("a", "a"));
    CuAssertIntEquals(tc, 1, streq("yes", "yes"));
}


static void assertCanon(CuTest* tc, char* in, char* out) {
    char* canon = an_canonicalize_file_name(in);
    CuAssertPtrNotNull(tc, canon);
    if (strcmp(canon, out))
        printf("Input \"%s\", expected \"%s\", got \"%s\"\n", in, out, canon);
    CuAssertIntEquals(tc, 0, strcmp(canon, out));
    free(canon);
}

void test_canon_1(CuTest* tc) {
    assertCanon(tc, "//path/to/a/.//./file/with/../junk", "/path/to/a/file/junk");
}

void test_canon_2(CuTest* tc) {
    assertCanon(tc, "/", "/");
}

void test_canon_2b(CuTest* tc) {
    assertCanon(tc, ".", ".");
}

void test_canon_2c(CuTest* tc) {
    assertCanon(tc, "..", "..");
}

void test_canon_3(CuTest* tc) {
    assertCanon(tc, "x/../y", "y");
}

void test_canon_3b(CuTest* tc) {
    assertCanon(tc, "x/../y/../z/a/b/c/d/../../../e", "z/a/e");
}

void test_canon_4(CuTest* tc) {
    // HACK... this probably ISN'T what it should do.
    //assertCanon(tc, "../y", "y");

    assertCanon(tc, "../y", "../y");
}

void test_canon_4b(CuTest* tc) {
    assertCanon(tc, "../../../y", "../../../y");
}

void test_canon_4c(CuTest* tc) {
    assertCanon(tc, "../../../../y", "../../../../y");
}

void test_canon_5(CuTest* tc) {
    assertCanon(tc, "/../..//x", "/x");
}

