/*
  This file is part of the Astrometry.net suite.
  Copyright 2008, 2011 Dustin Lang.

  The Astrometry.net suite is free software; you can redistribute
  it and/or modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation, version 2.

  The Astrometry.net suite 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
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with the Astrometry.net suite ; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/

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

#include "cutest.h"
#include "ioutils.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");
}

