/**
 * @file
 */

// Local configuration
#include "config.h"

// STDC++
#include <iostream>
#include <set>

// C++ Libraries
#include <ui-utilcpp/Sys.hpp>
#include <ui-utilcpp/Recoder.hpp>
#include <ui-utilcpp/CharsetMagic.hpp>
#include <ui-utilcpp/File.hpp>
#include <ui-utilcpp/Time.hpp>
#include <ui-utilcpp/PosixRegex.hpp>

#include <ui-utilcpp/Http.hpp>

#ifdef WIN32
	#define BOOST_TEST_MAIN
	#include <boost/test/unit_test.hpp>
#else
	#define BOOST_AUTO_TEST_MAIN
	#define BOOST_TEST_DYN_LINK
	#include <boost/test/unit_test.hpp>
#endif

namespace UI {
namespace Util {

BOOST_AUTO_TEST_CASE(test_Exceptions)
{
	// Tool class for static helpers
	class T
	{
	public:
		static void showException(UI::Exception const & e)
		{
			std::cout << "Exception what : " << e.what() << std::endl;
			std::cout << "Exception debug: " << e.getDebug() << std::endl;
			std::cout << "Exception errno: " << e.getErrno() << std::endl;
		}
	};

	// Errno simple I
	try
	{
		Sys::chmod("AyIuiePKIyBRudLB28YFzEzwLECuaMCoax0oBc9GBftQHJwEiveXvfqnExnTQr9I", 0);
	}
	catch (UI::Exception const & e)
	{
		T::showException(e);
		BOOST_CHECK(e.getErrno() == ENOENT);
	}

	// Errno simple II
	try
	{
		Sys::chmod("AyIuiePKIyBRudLB28YFzEzwLECuaMCoax0oBc9GBftQHJwEiveXvfqnExnTQr9I", 0);
	}
	catch (Sys::Exception const & e)
	{
		T::showException(e);
		BOOST_CHECK(e.getErrno() == ENOENT);
	}
	catch (UI::Exception const & e)
	{
		T::showException(e);
		BOOST_CHECK(false && "We should have been catched already as Sys::Exception");
	}
}

BOOST_AUTO_TEST_CASE(test_Encoding)
{
	unsigned char const c_ucs4[] = { 0x00, 0x00, 0x00, 0xf6, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xdf, 0x00, 0x00, 0x20, 0xac};
	std::string const ucs4((char *)c_ucs4, 20);

	unsigned char const c_utf16be[] = { 0x00, 0xf6, 0x00, 0xe4, 0x00, 0xfc, 0x00, 0xdf, 0x20, 0xac };
	std::string const utf16be((char *)c_utf16be, 10);

	unsigned char const c_utf8[] = { 0xc3, 0xb6, 0xc3, 0xa4, 0xc3, 0xbc, 0xc3, 0x9f, 0xe2, 0x82, 0xac };
	std::string const utf8((char *)c_utf8, 11);

	unsigned char const c_iso_8859_15[] = { 0xf6, 0xe4, 0xfc, 0xdf, 0xa4 };
	std::string const iso_8859_15((char *)c_iso_8859_15, 5);

	// Some non-sloppy full conversions
	BOOST_CHECK(UI::Util::RecoderCache::get("UCS-4", "ISO-8859-15").run(ucs4) == iso_8859_15);
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-16BE", "ISO-8859-15").run(utf16be) == iso_8859_15);
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "ISO-8859-15").run(utf8) == iso_8859_15);
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "UTF-8").run(iso_8859_15) == utf8);
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "UTF-16BE").run(iso_8859_15) == utf16be);

	// Non-ISO-8859 character "ACCUTE ACCENT"
	char const c_utf8_noniso[] = { (char) 0xc2, (char) 0xb4 };
	std::string const utf8_noniso(c_utf8_noniso, 2);
	// Non-sloppy must fail with Conversion::Exception
	try
	{
		UI::Util::RecoderCache::get("UTF-8", "ISO-8859-15").run(utf8_noniso);
		BOOST_CHECK(false && "UTF-8..ISO-8859-15 exact conversion of ACUTE ACCENT should have failed");
	}
	catch (Converter::Exception const &)
	{}
	// Sloppy must succeed to "'"
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "ISO-8859-15", true).run(utf8_noniso) == "'");

	// b64..data tests
	std::string const b64("SFVNQlVH");
	std::string const data("HUMBUG");
	BOOST_CHECK(UI::Util::RecoderCache::get("b64", "data").run(b64) == data);
	BOOST_CHECK(UI::Util::RecoderCache::get("data", "b64").run(data) == b64);

	std::string const url("eins+zwei+drei");
	std::string const plain("eins zwei drei");
	BOOST_CHECK(UI::Util::RecoderCache::get("data", "url").run(plain) == url);
	BOOST_CHECK(UI::Util::RecoderCache::get("url", "data").run(url) == plain);

	std::string const percentBugPlain("95%");
	std::string const percentBugURL("95%25");
	BOOST_CHECK(UI::Util::RecoderCache::get("data", "url").run(percentBugPlain) == percentBugURL);

	std::string const nonASCIIBugURL("%F6%E4%FC");
	std::string const nonASCIIBugPlain("öäü");
	BOOST_CHECK(UI::Util::RecoderCache::get("data", "url").run(nonASCIIBugPlain) == nonASCIIBugURL);
	BOOST_CHECK(UI::Util::RecoderCache::get("url", "data").run(nonASCIIBugURL) == nonASCIIBugPlain);

	{
		// This generates a bug with librecode0, version 3.6
		// Fixed by a debian patch since 3.6-7.
		// http://bugs.debian.org/cgi-bin/bugreport.cgi?archive=no&bug=156635
		UI::Util::Recoder recode("UTF-8","ISO-8859-15");
		std::string const a("Bitte_Xenden_Sie_dieYe_Mail_zurÃ¼ck,_um_Zich_anzumelden.");
		std::string const b("C000035A300000006DBCD1409@pmt.1and1.com");

		std::string const a2(recode.run(a));
		std::string const b2(recode.run(b));

		BOOST_CHECK(b == b2);
	}

	// rfc2047 (MIME)
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "rfc2047").run(iso_8859_15) == "=?ISO-8859-15?Q?=F6=E4=FC=DF=A4?=");
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "rfc2047").run(utf8) == "=?UTF-8?Q?=C3=B6=C3=A4=C3=BC=C3=9F=E2=82=AC?=");
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "rfc2047").run("Données du formulaire") == "=?UTF-8?Q?Donn=E9es_du_formulaire?=");

	// rfc3490 (IDN)
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "rfc3490").run("tinc-test-fÃ¼nf.de") == "xn--tinc-test-fnf-6ob.de");
	BOOST_CHECK(UI::Util::RecoderCache::get("rfc3490", "UTF-8").run("xn--tinc-test-fnf-6ob.de") == "tinc-test-fÃ¼nf.de");

	// IDN-EMAIL
	BOOST_CHECK(UI::Util::RecoderCache::get("UTF-8", "idn-email").run("test@tinc-test-fÃ¼nf.de") == "test@xn--tinc-test-fnf-6ob.de");
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "idn-email").run("test@tinc-test-fünf.de") == "test@xn--tinc-test-fnf-6ob.de");
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "idn-email").run("<The Test> test@tinc-test-fünf.de(Theo Test),a@würfel.de,c@d.de a@süß.de Humbug")
	            == "<The Test> test@xn--tinc-test-fnf-6ob.de(Theo Test),a@xn--wrfel-kva.de,c@d.de a@xn--sss-hoa.de Humbug");
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "idn-email").run("") == "");
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "idn-email").run("@") == "@");
	BOOST_CHECK(UI::Util::RecoderCache::get("ISO-8859-15", "idn-email").run("@ö") == "@xn--nda");
}

BOOST_AUTO_TEST_CASE(test_Strtok)
{
	// strtok: delim can be any string (default is ",").
	{
		std::vector<std::string> t(strtok(""));
		BOOST_CHECK(t.size() == 0);
	}
	{
		std::vector<std::string> t(strtok(","));
		BOOST_CHECK(t.size() == 0);
	}
	{
		std::vector<std::string> t(strtok("A,Bee,Zeh"));
		BOOST_CHECK(t.size() == 3 && t.at(0) == "A" && t.at(1) == "Bee" && t.at(2) == "Zeh");
	}
	{
		std::vector<std::string> t(strtok(",A,Bee,Zeh,"));
		BOOST_CHECK(t.size() == 3 && t.at(0) == "A" && t.at(1) == "Bee" && t.at(2) == "Zeh");
	}
	{
		std::vector<std::string> t(strtok(",A,Bee,Zeh,", "Bee"));
		BOOST_CHECK(t.size() == 2 && t.at(0) == ",A," && t.at(1) == ",Zeh,");
	}
	// strtoks: all single chars in delims act as delimiter (default is " \t\n\r").
	{
		std::vector<std::string> t(strtoks(""));
		BOOST_CHECK(t.size() == 0);
	}
	{
		std::vector<std::string> t(strtoks(","));
		BOOST_CHECK(t.size() == 1 && t.at(0) == ",");
	}
	{
		std::vector<std::string> t(strtoks("A Bee \t Zeh\n"));
		BOOST_CHECK(t.size() == 3 && t.at(0) == "A" && t.at(1) == "Bee" && t.at(2) == "Zeh");
	}
	{
		std::vector<std::string> t(strtoks("Bettwurstende", "ewu"));
		BOOST_CHECK(t.size() == 4 && t.at(0) == "B" && t.at(1) == "tt" && t.at(2) == "rst" && t.at(3) == "nd");
	}
}

BOOST_AUTO_TEST_CASE(test_join)
{
	// strtok: delim can be any string (default is ",").
	{
		std::vector<std::string> t;
		std::string s( join( "xxx", t ) );
		BOOST_CHECK(s.size() == 0);
	}
	{
		std::vector<std::string> t;
		t.push_back( "aa" );
		t.push_back( "bb" );
		t.push_back( "cc" );
		t.push_back( "dd" );
		std::string s( join( ",", t ) );
		BOOST_CHECK( s == "aa,bb,cc,dd" );
	}
	{
		std::set<std::string> t;
		t.insert( "aa" );
		t.insert( "bb" );
		t.insert( "cc" );
		t.insert( "dd" );
		std::string s( join( ",", t ) );
		BOOST_CHECK( s == "aa,bb,cc,dd" );
	}
}
BOOST_AUTO_TEST_CASE(test_HttpCookie)
{
	{
		Http::Cookies c;
		BOOST_CHECK(c.size() == 0);
	}
	{
		Http::Cookies c("a=aval");
		BOOST_CHECK(c.size() == 1);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
	}
	{
		Http::Cookies c("a=aval;");
		BOOST_CHECK(c.size() == 1);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
	}
	{
		Http::Cookies c("a=aval; b=bval;");
		BOOST_CHECK(c.size() == 2);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
		BOOST_CHECK(c.at(1).getName() == "b" && c.at(1).getValue() == "bval");
	}
	{
		Http::Cookies c("a=aval;b=bval;");
		BOOST_CHECK(c.size() == 2);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
		BOOST_CHECK(c.at(1).getName() == "b" && c.at(1).getValue() == "bval");
	}
	{
		Http::Cookies c("a=aval; b=bval");
		BOOST_CHECK(c.size() == 2);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
		BOOST_CHECK(c.at(1).getName() == "b" && c.at(1).getValue() == "bval");
	}
	{
		Http::Cookies c("a=aval;b=bval");
		BOOST_CHECK(c.size() == 2);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
		BOOST_CHECK(c.at(1).getName() == "b" && c.at(1).getValue() == "bval");
	}
	{
		Http::Cookies c("a=a val; b=bval");
		BOOST_CHECK(c.size() == 1);
		BOOST_CHECK(c.at(0).getName() == "b" && c.at(0).getValue() == "bval");
	}
	{
		Http::Cookies c("a=aval;foo;b=bval;c=cval");
		BOOST_CHECK(c.size() == 2);
		BOOST_CHECK(c.at(0).getName() == "a" && c.at(0).getValue() == "aval");
		BOOST_CHECK(c.at(1).getName() == "c" && c.at(1).getValue() == "cval");
	}

	{
		Http::Header header;
		std::string const line("pre0_eins=1; pre0_zwei=2; pre1_drei=3; pre0_=no_name");
		header.add("Cookie: " + line);
		BOOST_CHECK(Http::Cookies(header).getLine() == line);
		BOOST_CHECK(Http::Cookies(header, "pre0_").getLine() == "eins=1; zwei=2; =no_name");
		BOOST_CHECK(Http::Cookies(header, "pre1_").getLine() == "drei=3");
		BOOST_CHECK(Http::Cookies(header, "pre0_").getLine("proll_") == "proll_eins=1; proll_zwei=2; proll_=no_name");
	}
}

BOOST_AUTO_TEST_CASE(test_HttpSetCookie)
{
	Http::SetCookie("sessid").setLine("sessid=test;");
}

BOOST_AUTO_TEST_CASE(test_HttpHeader)
{
	Http::Header iHeader;
	iHeader.
		add("Content-Length: 1000").
		add("X-PREFIX-PH-0: PV-0").
		add("Multiple: MV-0").
		add("X-PREFIX-PH-1: PV-1").
		add("Multiple: MV-1").
		add("X-PREFIX-PH-2: PV-2").
		add("Multiple: MV-2").
		add("X-Non-ASCII: äöüÄÖÜbertragÜ").
		add("empty0:").
		add("empty1:    ");

	// Check plain header
	BOOST_CHECK(iHeader.size() == 10);
	BOOST_CHECK(iHeader.get<int>("Content-Length") == 1000);
	BOOST_CHECK(iHeader.get("X-PREFIX-PH-1") == "PV-1");
	BOOST_CHECK(iHeader.get("X-Non-ASCII") == "??????bertrag?");
	BOOST_CHECK(iHeader.get("empty0") == "");
	BOOST_CHECK(iHeader.get("empty1") == "");

	// Get multi header fields (exact match)
	Http::Header multiHeader(iHeader.getMulti("Multiple"));
	BOOST_CHECK(multiHeader.size() == 3);
	BOOST_CHECK(multiHeader.at(0).getName() == "Multiple" && multiHeader.at(0).getValue() == "MV-0");
	BOOST_CHECK(multiHeader.at(1).getName() == "Multiple" && multiHeader.at(1).getValue() == "MV-1");
	BOOST_CHECK(multiHeader.at(2).getName() == "Multiple" && multiHeader.at(2).getValue() == "MV-2");

	// Get all headerfields w/ prefix
	Http::Header prefixHeader(iHeader.getPrefix("X-PREFIX-"));
	BOOST_CHECK(prefixHeader.size() == 3);
	BOOST_CHECK(prefixHeader.at(0).getName() == "PH-0" && prefixHeader.at(0).getValue() == "PV-0");
	BOOST_CHECK(prefixHeader.at(1).getName() == "PH-1" && prefixHeader.at(1).getValue() == "PV-1");
	BOOST_CHECK(prefixHeader.at(2).getName() == "PH-2" && prefixHeader.at(2).getValue() == "PV-2");
}

#ifndef WIN32
BOOST_AUTO_TEST_CASE(test_Http)
{
	// Open an internal socket pair for testing
	UI::Util::SocketPair sp;
	Http::Connection server(sp.first());
	Http::Connection client(sp.second());

	// Client writes GET request
	{
		client.write(Http::RequestLine(Http::RequestLine::Get_, "/hotzenplotz"));
		client.write(Http::Header().add("Host", "localhost"));
		client.writeLine();
		client.s().flush();
	}

	// Server reads incoming request
	{
		Http::RequestLine requestLine(server.readLine());
		BOOST_CHECK(requestLine.getMethod() == Http::RequestLine::Get_);
		BOOST_CHECK(requestLine.getMethodStr() == "GET");
		BOOST_CHECK(requestLine.getURI() == "/hotzenplotz");

		Http::Header header(server.readHeader());
		BOOST_CHECK(header.get("Host") == "localhost");
	}

	std::string const body("Test response body.");

	// Server writes response
	{
		server.write(Http::StatusLine(200));
		server.write(Http::Header().
		             add("Content-Length", tos(body.size())).
		             add("Content-Type", "text/plain"));
		server.writeLine();
		server.write(body);
	}

	// Client reads response
	{
		Http::StatusLine status(client.readLine());
		BOOST_CHECK(status.getCode() == 200);
		BOOST_CHECK(status.getReason() == "OK");
		std::cerr << status.getReason() << std::endl;

		Http::Header header(client.readHeader());
		BOOST_CHECK(header.get("Content-Type") == "text/plain");
		BOOST_CHECK(body == client.readBlock(header.get<std::streamsize>("Content-Length")));
	}
}
#endif

BOOST_AUTO_TEST_CASE(test_Http_Get)
{
	#ifdef WIN32
	WSADATA wsaData;
	int iResult = ::WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != NO_ERROR)
	{
		BOOST_CHECK(false);
		return;
	}
	#endif

#ifdef UI_UTILCPP_UNITTESTS_HTTP_LOCALHOST
	// Note: These tests only work if you have a local httpd server running.
	Http::URLGet get1("http://localhost");
	Http::URLGet get2("http://localhost/");
	Http::URLGet get3("http://localhost:80");
	Http::URLGet get4("http://localhost:80/");
	Http::URLGet get5("http://localhost:80?a=b&c=d");
#endif

	#ifdef WIN32
	::WSACleanup();
	#endif
}

BOOST_AUTO_TEST_CASE(test_RealTimeStamp)
{
	RealTimeStamp s0;
	RealTimeStamp s1;

	s0.stamp();
	Sys::sleep(1);
	s1.stamp();

	RealTimeStamp const diff(s1-s0);
	RealTimeStamp const bottomMargin(0,400000);
	RealTimeStamp const topMargin(1,600000);

	BOOST_CHECK(bottomMargin < diff);
	BOOST_CHECK(diff < topMargin);
	std::cout << "RealTimeStamp diff is: " << diff << " (should be: about 1 seconds)." << std::endl;
}

BOOST_AUTO_TEST_CASE(test_FsInfo)
{
	FsInfo fsInfo("C:", ".");
	std::cout << "Total: " << fsInfo.getTotal() << std::endl;
	std::cout << "Used : " << fsInfo.getUsed() << std::endl;
	std::cout << "Avail: " << fsInfo.getAvail() << std::endl;
	std::cout << "Free : " << fsInfo.getFree() << std::endl;
}

BOOST_AUTO_TEST_CASE(test_PosixRegex)
{
	// ---------------------012345678901234567890
	std::string const text("Pumpernickelwurstbrot");

	{
		PosixRegex::Match const match(PosixRegex("nickel").runMatch(text));
		BOOST_CHECK(match.matches);
		BOOST_CHECK(match.begin == 6);
		BOOST_CHECK(match.end == 12);
		std::cout << "Matches/Begin/End: " << match.matches << "/" << match.begin << "/" << match.end << std::endl;
	}
	{
		PosixRegex::Match const match(PosixRegex("nixdrin").runMatch(text));
		BOOST_CHECK(not match.matches);
		std::cout << "Matches/Begin/End: " << match.matches << "/" << match.begin << "/" << match.end << std::endl;
	}
}

BOOST_AUTO_TEST_CASE(test_md5sum)
{
	BOOST_CHECK(md5sum("") == "d41d8cd98f00b204e9800998ecf8427e");
	BOOST_CHECK(md5sum(" ") == "7215ee9c7d9dc229d2921a40e899ec5f");
	BOOST_CHECK(md5sum("\n") == "68b329da9893e34099c7d8ad5cb9c940");
}

}}
