use strict;
use warnings;
use Test;

BEGIN { plan(tests => 120) }

use Net::Gopher;
use Net::Gopher::Request;
use Net::Gopher::Constants qw(:item_types);

require './t/serverfunctions.pl';







################################################################################
#
# These tests check the integrity of the Net::Gopher object itself, making
# sure initialization via the constructor and later modification via the
# accessor methods all work properly:
#

{
	my $ng = new Net::Gopher;

	ok($ng->buffer_size, 4096);                             # 1
	ok($ng->timeout, 30);                                   # 2
	ok($ng->upward_compatible, 1);                          # 3
	ok($ng->warn_handler ==
		$Net::Gopher::Exception::DEFAULT_WARN_HANDLER); # 4
	ok($ng->die_handler == 
		$Net::Gopher::Exception::DEFAULT_DIE_HANDLER);  # 5
	ok($ng->silent, 0);                                     # 6
	ok($ng->debug, 0);                                      # 7
	ok(!defined $ng->log_file);                             # 8
	ok(!defined $ng->_buffer);                              # 9
	ok(!defined $ng->_socket);                              # 10
	ok(!defined $ng->_select);                              # 11
}

{
	my $warn = sub { print "a warning" };
	my $die  = sub { print "a fatal error" };

	my $ng = new Net::Gopher (
		BufferSize       => 777777,
		TIMEOUT          => 60,
		upwardcompatible => 'true',
		WaRNHaNDleR      => $warn,
		DieHandler       => $die,
		SILENT           => undef,
		debug            => 'also true',
		LoGFiLe          => 'a_filename.txt'
	);

	ok($ng->buffer_size, 777777);         # 12
	ok($ng->timeout, 60);                 # 13
	ok($ng->upward_compatible, 1);        # 14
	ok($ng->warn_handler == $warn);       # 15
	ok($ng->die_handler  == $die);        # 16
	ok($ng->silent, 0);                   # 17
	ok($ng->debug, 1);                    # 18
	ok($ng->log_file, 'a_filename.txt');  # 19
	ok(!defined $ng->_buffer);            # 20
	ok(!defined $ng->_socket);            # 21
	ok(!defined $ng->_select);            # 22
}

{
	my $ng = new Net::Gopher;

	$ng->buffer_size(1234567);
	ok($ng->buffer_size, 1234567); # 23

	$ng->timeout(100);
	ok($ng->timeout, 100);         # 24

	$ng->upward_compatible('true');
	ok($ng->upward_compatible, 1); # 25

	$ng->upward_compatible(0);
	ok($ng->upward_compatible, 0); # 26

	$ng->debug(100);
	ok($ng->debug, 1);             # 27

	$ng->debug(0);
	ok($ng->debug, 0);             # 28
}







{
	# this runs testserver.pl with -e to echo back each request:
	my $port = launch_echo_server();
	ok($port); # 29

	my $ng = new Net::Gopher;



	#######################################################################
	#
	# These tests are used to make sure that the Net::Gopher request()
	# method properly sends the request strings generated by the
	# Net::Gopher::Request as_string() method to the server:
	#

	{
		my $request = new Net::Gopher::Request (
			Gopher => {
				Host        => 'localhost',
				Port        => $port,
				Selector    => '/something',
				SearchWords => ['red', 'green', 'blue']
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 30
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 31
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain'
			}
		);

		my $response = $ng->request($request);
		if ($response->is_success)
		{
			ok(1);                                    # 32
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 33
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain',
				DataBlock      => 'This is a single-line block'
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 34
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 35
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain',
				DataBlock      => 'This is a big single-line block ' x 2000
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 36
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 37
	}

	{ 
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain',
				DataBlock      =>
				"This\015\012is\012a\015\012multi-line\012block"
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 38
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 39
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain',
				DataBlock      =>
				"This\015\012is\012a\015\012multi-line\012block " x 2000,
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 40
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 41
	}

	{
		my $request = new Net::Gopher::Request (
			ItemAttribute => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/some_item',
				Attributes     => '+ATTR'
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 42
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 43
	}

	{
		my $request = new Net::Gopher::Request (
			DirectoryAttribute => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/some_dir',
				Attributes     => '+ATTR'
			}
		);

		my $response = $ng->request($request);

		if ($response->is_success)
		{
			ok(1);                                    # 44
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->raw_response, $request->as_string); # 45
	}







	########################################################################
	# 
	# These tests are used to make sure that the named request methods
	# create the proper request objects:
	#

	{
		my $request = new Net::Gopher::Request (
			Gopher => {
				Host        => 'localhost',
				Port        => $port,
				Selector    => '/something',
				SearchWords => ['red', 'green', 'blue']
			}
		);

		my $response = $ng->gopher(
			Host        => 'localhost',
			Port        => $port,
			Selector    => '/something',
			SearchWords => ['red', 'green', 'blue']
		);

		if ($response->is_success)
		{
			ok(1);                   # 46
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->request->request_type,
			$request->request_type); # 47
		ok($response->request->as_string,
			$request->as_string);    # 48
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/something_else',
				Representation => 'text/plain'
			}
		);

		my $response = $ng->gopher_plus(
			Host           => 'localhost',
			Port           => $port,
			Selector       => '/something_else',
			Representation => 'text/plain'
		);

		if ($response->is_success)
		{
			ok(1);                   # 49
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->request->request_type,
			$request->request_type); # 50
		ok($response->request->as_string,
			$request->as_string);    # 51
	}

	{
		my $request = new Net::Gopher::Request (
			ItemAttribute => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/some_dir',
				Attributes     => '+ATTR'
			}
		);

		my $response = $ng->item_attribute(
			Host           => 'localhost',
			Port           => $port,
			Selector       => '/some_dir',
			Attributes     => '+ATTR'
		);

		if ($response->is_success)
		{
			ok(1);                   # 52
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->request->request_type,
			$request->request_type); # 53
		ok($response->request->as_string,
			$request->as_string);    # 54
	}

	{
		my $request = new Net::Gopher::Request (
			DirectoryAttribute => {
				Host           => 'localhost',
				Port           => $port,
				Selector       => '/some_dir',
				Attributes     => '+ATTR'
			}
		);

		my $response = $ng->directory_attribute(
			Host           => 'localhost',
			Port           => $port,
			Selector       => '/some_dir',
			Attributes     => '+ATTR'
		);

		if ($response->is_success)
		{
			ok(1);                   # 55
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($response->request->request_type,
			$request->request_type); # 56
		ok($response->request->as_string,
			$request->as_string);    # 57
	}
}







{
	# this launches a copy of the test server to serve up the items in
	# ./t/items:
	my $port = launch_item_server();
	ok($port); # 58

	my $ng = new Net::Gopher;



	########################################################################
	# 
	# These tests are used to make sure that the request method handles
	# response handlers correctly:
	#

	{
		my $request = new Net::Gopher::Request (
			Gopher => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/index'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 59
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 60
		ok($request  == $last_request_obj);  # 61
		ok($response == $last_response_obj); # 62
		ok($content_matches);                # 63
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_period_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 64
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 65
		ok($request  == $last_request_obj);  # 66
		ok($response == $last_response_obj); # 67
		ok($content_matches);                # 68
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_no_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 69
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 70
		ok($request  == $last_request_obj);  # 71
		ok($response == $last_response_obj); # 72
		ok($content_matches);                # 73
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_byte_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 74
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 75
		ok($request  == $last_request_obj);  # 76
		ok($response == $last_response_obj); # 77
		ok($content_matches);                # 78
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_s_period_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 79
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 80
		ok($request  == $last_request_obj);  # 81
		ok($response == $last_response_obj); # 82
		ok($content_matches);                # 83
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_s_no_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 84
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 85
		ok($request  == $last_request_obj);  # 86
		ok($response == $last_response_obj); # 87
		ok($content_matches);                # 88
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_s_byte_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 89
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 90
		ok($request  == $last_request_obj);  # 91
		ok($response == $last_response_obj); # 92
		ok($content_matches);                # 93
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/index'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 94
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 95
		ok($request  == $last_request_obj);  # 96
		ok($response == $last_response_obj); # 97
		ok($content_matches);                # 98
	}

	# these tests test handlers with negative return values:
	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_period_term'
			}
		);

		my $content;
		my $last_request_obj;
		my $last_response_obj;
		my $content_matches = 1;
		my $invocations     = 0;
		my $response = $ng->request($request,
			Handler => sub {
				my $buffer = shift;
				($last_request_obj, $last_response_obj) = @_;

				$content .= $buffer;

				# only execute once:
				return 0 if ($invocations);

				$content_matches = 0
					if ($content ne $last_response_obj->content);

				$invocations++;

				return 1;
			}
		);

		if ($response->is_success)
		{
			ok(1);                       # 99
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok($invocations);                    # 100
		ok($request  == $last_request_obj);  # 101
		ok($response == $last_response_obj); # 102
		ok($content_matches);                # 103
	}






	{
		my $request = new Net::Gopher::Request (
			Gopher => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/index'
			}
		);

		my $response = $ng->request($request, File => 'test.txt');

		if ($response->is_success)
		{
			ok(1);                            # 104
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok(open(TEST, 'test.txt'));               # 105
		ok(join('', <TEST>), $response->content); # 106
		close TEST;
		ok(unlink('test.txt'));                   # 107
		ok(!-e 'test.txt');                       # 108
	}

	{
		my $request = new Net::Gopher::Request (
			GopherPlus => {
				Host     => 'localhost',
				Port     => $port,
				Selector => '/gp_index'
			}
		);

		my $response = $ng->request($request, File => 'test2.txt');


		if ($response->is_success)
		{
			ok(1);                             # 109
		}
		else
		{
			ok(0);
			warn $response->error;
		}
		ok(open(TEST2, 'test2.txt'));              # 110
		ok(join('', <TEST2>), $response->content); # 111
		close TEST2;
		ok(unlink('test2.txt'));                   # 112
		ok(!-e 'test2.txt');                       # 113
	}







	########################################################################
	# 
	# These tests make sure Net::Gopher raises exceptions in the proper
	# places:
	#

	{
		my (@warnings, @fatal_errors);

		my $ng = new Net::Gopher(
			WarnHandler => sub { push(@warnings, @_) },
			DieHandler  => sub { push(@fatal_errors, @_) }
		);

		$ng->request();

		ok(scalar @warnings, 0);        # 114
		ok(scalar @fatal_errors, 1);    # 115
		ok($fatal_errors[0],
			'A Net::Gopher::Request object was not supplied as ' .
			'the first argument.'); # 116
	}

	{
		my (@warnings, @fatal_errors);

		my $ng = new Net::Gopher(
			WarnHandler => sub { push(@warnings, @_) },
			DieHandler  => sub { push(@fatal_errors, @_) }
		);

		$ng->request(new Net::Gopher::Request('Gopher') );

		ok(@warnings, 0);     # 117
		ok(@fatal_errors, 1); # 118
		ok($fatal_errors[0],
			join(' ',
				"You never specified a host; it's impossible",
				"to send your request. Specify one during",
				"request object creation or later on with the",
				"host() method."
			));           # 119
	}



	ok(kill_servers()); # 120
}
