%% This module echoes back the value the test is interested in.

-module(resp_h).

%% @todo Probably should have a separate handler for errors,
%% so that we can dialyze all the other correct calls.
-dialyzer({nowarn_function, do/3}).

-export([init/2]).

init(Req, Opts) ->
	do(cowboy_req:binding(key, Req), Req, Opts).

do(<<"set_resp_cookie3">>, Req0, Opts) ->
	Req = case cowboy_req:binding(arg, Req0) of
		undefined ->
			cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0);
		<<"multiple">> ->
			Req1 = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0),
			cowboy_req:set_resp_cookie(<<"yourcookie">>, <<"yourvalue">>, Req1);
		<<"overwrite">> ->
			Req1 = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0),
			cowboy_req:set_resp_cookie(<<"mycookie">>, <<"overwrite">>, Req1)
	end,
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_cookie4">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_cookie(<<"mycookie">>, "myvalue", Req0,
		#{path => cowboy_req:path(Req0)}),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_header">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_header_cookie">>, Req0, Opts) ->
	ct_helper:ignore(cowboy_req, set_resp_header, 3),
	Req = cowboy_req:set_resp_header(<<"set-cookie">>, <<"name=value">>, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_header_server">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_header(<<"server">>, <<"nginx">>, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_headers(#{
		<<"content-type">> => <<"text/plain">>,
		<<"content-encoding">> => <<"compress">>
	}, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers_list">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_headers([
		{<<"content-type">>, <<"text/plain">>},
		{<<"test-header">>, <<"one">>},
		{<<"content-encoding">>, <<"compress">>},
		{<<"test-header">>, <<"two">>}
	], Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers_cookie">>, Req0, Opts) ->
	ct_helper:ignore(cowboy_req, set_resp_headers, 2),
	Req = cowboy_req:set_resp_headers(#{
		<<"set-cookie">> => <<"name=value">>
	}, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers_list_cookie">>, Req0, Opts) ->
	ct_helper:ignore(cowboy_req, set_resp_headers_list, 3),
	Req = cowboy_req:set_resp_headers([
		{<<"set-cookie">>, <<"name=value">>},
		{<<"set-cookie">>, <<"name2=value2">>}
	], Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_headers_http11">>, Req0, Opts) ->
	Req = cowboy_req:set_resp_headers(#{
		<<"connection">> => <<"custom-header, close">>,
		<<"custom-header">> => <<"value">>,
		<<"keep-alive">> => <<"timeout=5, max=1000">>,
		<<"proxy-connection">> => <<"close">>,
		<<"transfer-encoding">> => <<"chunked">>,
		<<"upgrade">> => <<"HTTP/1.1">>
	}, Req0),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"resp_header_defined">>, Req0, Opts) ->
	Req1 = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
	<<"text/plain">> = cowboy_req:resp_header(<<"content-type">>, Req1),
	<<"text/plain">> = cowboy_req:resp_header(<<"content-type">>, Req1, default),
	{ok, cowboy_req:reply(200, #{}, "OK", Req0), Opts};
do(<<"resp_header_default">>, Req, Opts) ->
	undefined = cowboy_req:resp_header(<<"content-type">>, Req),
	default = cowboy_req:resp_header(<<"content-type">>, Req, default),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"resp_headers">>, Req0, Opts) ->
	Req1 = cowboy_req:set_resp_header(<<"server">>, <<"nginx">>, Req0),
	Req = cowboy_req:set_resp_headers(#{
		<<"content-type">> => <<"text/plain">>,
		<<"content-encoding">> => <<"compress">>
	}, Req1),
	Headers = cowboy_req:resp_headers(Req),
	true = maps:is_key(<<"server">>, Headers),
	true = maps:is_key(<<"content-type">>, Headers),
	true = maps:is_key(<<"content-encoding">>, Headers),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"resp_headers_empty">>, Req, Opts) ->
	#{} = cowboy_req:resp_headers(Req),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"set_resp_body">>, Req0, Opts) ->
	Arg = cowboy_req:binding(arg, Req0),
	Req1 = case Arg of
		<<"sendfile0">> ->
			AppFile = code:where_is_file("cowboy.app"),
			cowboy_req:set_resp_body({sendfile, 0, 0, AppFile}, Req0);
		<<"sendfile">> ->
			AppFile = code:where_is_file("cowboy.app"),
			cowboy_req:set_resp_body({sendfile, 0, filelib:file_size(AppFile), AppFile}, Req0);
		_ ->
			cowboy_req:set_resp_body(<<"OK">>, Req0)
	end,
	Req = case Arg of
		<<"override">> ->
			cowboy_req:reply(200, #{}, <<"OVERRIDE">>, Req1);
		_ ->
			cowboy_req:reply(200, Req1)
	end,
	{ok, Req, Opts};
do(<<"has_resp_header">>, Req0, Opts) ->
	false = cowboy_req:has_resp_header(<<"content-type">>, Req0),
	Req = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req0),
	true = cowboy_req:has_resp_header(<<"content-type">>, Req),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"has_resp_body">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"sendfile">> ->
			%% @todo Cases for sendfile. Note that sendfile 0 is unallowed.
			false = cowboy_req:has_resp_body(Req0),
			Req = cowboy_req:set_resp_body({sendfile, 0, 10, code:where_is_file("cowboy.app")}, Req0),
			true = cowboy_req:has_resp_body(Req),
			{ok, cowboy_req:reply(200, #{}, <<"OK">>, Req), Opts};
		undefined ->
			false = cowboy_req:has_resp_body(Req0),
			Req = cowboy_req:set_resp_body(<<"OK">>, Req0),
			true = cowboy_req:has_resp_body(Req),
			{ok, cowboy_req:reply(200, #{}, Req), Opts}
	end;
do(<<"delete_resp_header">>, Req0, Opts) ->
	%% We try to delete first even though it hasn't been set to
	%% make sure this noop is possible.
	Req1 = cowboy_req:delete_resp_header(<<"content-type">>, Req0),
	false = cowboy_req:has_resp_header(<<"content-type">>, Req1),
	Req2 = cowboy_req:set_resp_header(<<"content-type">>, <<"text/plain">>, Req1),
	true = cowboy_req:has_resp_header(<<"content-type">>, Req2),
	Req = cowboy_req:delete_resp_header(<<"content-type">>, Req2),
	false = cowboy_req:has_resp_header(<<"content-type">>, Req),
	{ok, cowboy_req:reply(200, #{}, "OK", Req), Opts};
do(<<"inform2">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"binary">> ->
			cowboy_req:inform(<<"102 On my way">>, Req0);
		<<"error">> ->
			ct_helper:ignore(cowboy_req, inform, 3),
			cowboy_req:inform(ok, Req0);
		<<"twice">> ->
			cowboy_req:inform(102, Req0),
			cowboy_req:inform(102, Req0);
		<<"after_reply">> ->
			ct_helper:ignore(cowboy_req, inform, 3),
			Req1 = cowboy_req:reply(200, Req0),
			cowboy_req:inform(102, Req1);
		Status ->
			cowboy_req:inform(binary_to_integer(Status), Req0)
	end,
	Req = cowboy_req:reply(200, Req0),
	{ok, Req, Opts};
do(<<"inform3">>, Req0, Opts) ->
	Headers = #{<<"ext-header">> => <<"ext-value">>},
	case cowboy_req:binding(arg, Req0) of
		<<"binary">> ->
			cowboy_req:inform(<<"102 On my way">>, Headers, Req0);
		<<"error">> ->
			ct_helper:ignore(cowboy_req, inform, 3),
			cowboy_req:inform(ok, Headers, Req0);
		<<"set_cookie">> ->
			ct_helper:ignore(cowboy_req, inform, 3),
			cowboy_req:inform(102, #{<<"set-cookie">> => <<"name=value">>}, Req0);
		<<"twice">> ->
			cowboy_req:inform(102, Headers, Req0),
			cowboy_req:inform(102, Headers, Req0);
		<<"after_reply">> ->
			ct_helper:ignore(cowboy_req, inform, 3),
			Req1 = cowboy_req:reply(200, Req0),
			cowboy_req:inform(102, Headers, Req1);
		Status ->
			cowboy_req:inform(binary_to_integer(Status), Headers, Req0)
	end,
	Req = cowboy_req:reply(200, Req0),
	{ok, Req, Opts};
do(<<"reply2">>, Req0, Opts) ->
	Req = case cowboy_req:binding(arg, Req0) of
		<<"binary">> ->
			cowboy_req:reply(<<"200 GOOD">>, Req0);
		<<"error">> ->
			ct_helper:ignore(cowboy_req, reply, 4),
			cowboy_req:reply(ok, Req0);
		<<"twice">> ->
			ct_helper:ignore(cowboy_req, reply, 4),
			Req1 = cowboy_req:reply(200, Req0),
			timer:sleep(100),
			cowboy_req:reply(200, Req1);
		Status ->
			cowboy_req:reply(binary_to_integer(Status), Req0)
	end,
	{ok, Req, Opts};
do(<<"reply3">>, Req0, Opts) ->
	Req = case cowboy_req:binding(arg, Req0) of
		<<"error">> ->
			ct_helper:ignore(cowboy_req, reply, 4),
			cowboy_req:reply(200, ok, Req0);
		<<"set_cookie">> ->
			ct_helper:ignore(cowboy_req, reply, 4),
			cowboy_req:reply(200, #{<<"set-cookie">> => <<"name=value">>}, Req0);
		Status ->
			cowboy_req:reply(binary_to_integer(Status),
				#{<<"content-type">> => <<"text/plain">>}, Req0)
	end,
	{ok, Req, Opts};
do(<<"reply4">>, Req0, Opts) ->
	Req = case cowboy_req:binding(arg, Req0) of
		<<"error">> ->
			ct_helper:ignore(erlang, iolist_size, 1),
			cowboy_req:reply(200, #{}, ok, Req0);
		<<"set_cookie">> ->
			ct_helper:ignore(cowboy_req, reply, 4),
			cowboy_req:reply(200, #{<<"set-cookie">> => <<"name=value">>}, <<"OK">>, Req0);
		<<"204body">> ->
			ct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),
			cowboy_req:reply(204, #{}, <<"OK">>, Req0);
		<<"304body">> ->
			ct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),
			cowboy_req:reply(304, #{}, <<"OK">>, Req0);
		Status ->
			cowboy_req:reply(binary_to_integer(Status), #{}, <<"OK">>, Req0)
	end,
	{ok, Req, Opts};
do(<<"stream_reply2">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"binary">> ->
			Req = cowboy_req:stream_reply(<<"200 GOOD">>, Req0),
			stream_body(Req),
			{ok, Req, Opts};
		<<"error">> ->
			ct_helper:ignore(cowboy_req, stream_reply, 3),
			Req = cowboy_req:stream_reply(ok, Req0),
			stream_body(Req),
			{ok, Req, Opts};
		<<"204">> ->
			Req = cowboy_req:stream_reply(204, Req0),
			{ok, Req, Opts};
		<<"204body">> ->
			ct_helper:ignore(cowboy_req, stream_body, 3),
			Req = cowboy_req:stream_reply(204, Req0),
			stream_body(Req),
			{ok, Req, Opts};
		<<"304body">> ->
			ct_helper:ignore(cowboy_req, stream_body, 3),
			Req = cowboy_req:stream_reply(304, Req0),
			stream_body(Req),
			{ok, Req, Opts};
		<<"twice">> ->
			ct_helper:ignore(cowboy_req, stream_reply, 3),
			Req1 = cowboy_req:stream_reply(200, Req0),
			timer:sleep(100),
			%% We will crash here so the body shouldn't be sent.
			Req = cowboy_req:stream_reply(200, Req1),
			stream_body(Req),
			{ok, Req, Opts};
		Status ->
			Req = cowboy_req:stream_reply(binary_to_integer(Status), Req0),
			stream_body(Req),
			{ok, Req, Opts}
	end;
do(<<"stream_reply3">>, Req0, Opts) ->
	Req = case cowboy_req:binding(arg, Req0) of
		<<"error">> ->
			ct_helper:ignore(cowboy_req, stream_reply, 3),
			cowboy_req:stream_reply(200, ok, Req0);
		<<"set_cookie">> ->
			ct_helper:ignore(cowboy_req, stream_reply, 3),
			cowboy_req:stream_reply(200, #{<<"set-cookie">> => <<"name=value">>}, Req0);
		Status ->
			cowboy_req:stream_reply(binary_to_integer(Status),
				#{<<"content-type">> => <<"text/plain">>}, Req0)
	end,
	stream_body(Req),
	{ok, Req, Opts};
do(<<"stream_body">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"fin0">> ->
			Req = cowboy_req:stream_reply(200, Req0),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			cowboy_req:stream_body(<<>>, fin, Req),
			{ok, Req, Opts};
		<<"multiple">> ->
			Req = cowboy_req:stream_reply(200, Req0),
			cowboy_req:stream_body(<<"Hello ">>, nofin, Req),
			cowboy_req:stream_body(<<"world">>, nofin, Req),
			cowboy_req:stream_body(<<"!">>, fin, Req),
			{ok, Req, Opts};
		<<"loop">> ->
			Req = cowboy_req:stream_reply(200, Req0),
			_ = [cowboy_req:stream_body(<<0:1000000/unit:8>>, nofin, Req)
				|| _ <- lists:seq(1, 32)],
			{ok, Req, Opts};
		<<"nofin">> ->
			Req = cowboy_req:stream_reply(200, Req0),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			{ok, Req, Opts};
		<<"sendfile">> ->
			AppFile = code:where_is_file("cowboy.app"),
			AppSize = filelib:file_size(AppFile),
			Req = cowboy_req:stream_reply(200, Req0),
			cowboy_req:stream_body(<<"Hello ">>, nofin, Req),
			cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),
			cowboy_req:stream_body(<<" interspersed ">>, nofin, Req),
			cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),
			cowboy_req:stream_body(<<" world!">>, fin, Req),
			{ok, Req, Opts};
		<<"sendfile_fin">> ->
			AppFile = code:where_is_file("cowboy.app"),
			AppSize = filelib:file_size(AppFile),
			Req = cowboy_req:stream_reply(200, Req0),
			cowboy_req:stream_body(<<"Hello! ">>, nofin, Req),
			cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, fin, Req),
			{ok, Req, Opts};
		<<"spawn">> ->
			Req = cowboy_req:stream_reply(200, Req0),
			Parent = self(),
			Pid = spawn(fun() ->
				cowboy_req:stream_body(<<"Hello ">>, nofin, Req),
				cowboy_req:stream_body(<<"world">>, nofin, Req),
				cowboy_req:stream_body(<<"!">>, fin, Req),
				Parent ! {self(), ok}
			end),
			receive
				{Pid, ok} -> ok
			after 5000 ->
				error(timeout)
			end,
			{ok, Req, Opts};
		_ ->
			%% Call stream_body without initiating streaming.
			cowboy_req:stream_body(<<0:800000>>, fin, Req0),
			{ok, Req0, Opts}
	end;
do(<<"stream_body_content_length">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"fin0">> ->
			Req1 = cowboy_req:set_resp_header(<<"content-length">>, <<"12">>, Req0),
			Req = cowboy_req:stream_reply(200, Req1),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			cowboy_req:stream_body(<<>>, fin, Req),
			{ok, Req, Opts};
		<<"multiple">> ->
			Req1 = cowboy_req:set_resp_header(<<"content-length">>, <<"12">>, Req0),
			Req = cowboy_req:stream_reply(200, Req1),
			cowboy_req:stream_body(<<"Hello ">>, nofin, Req),
			cowboy_req:stream_body(<<"world">>, nofin, Req),
			cowboy_req:stream_body(<<"!">>, fin, Req),
			{ok, Req, Opts};
		<<"nofin">> ->
			Req1 = cowboy_req:set_resp_header(<<"content-length">>, <<"12">>, Req0),
			Req = cowboy_req:stream_reply(200, Req1),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			{ok, Req, Opts};
		<<"nofin-error">> ->
			Req1 = cowboy_req:set_resp_header(<<"content-length">>, <<"12">>, Req0),
			Req = cowboy_req:stream_reply(200, Req1),
			cowboy_req:stream_body(<<"Hello">>, nofin, Req),
			{ok, Req, Opts}
	end;
do(<<"stream_events">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		%%<<"single">>
		%%<<"list">>
		<<"single">> ->
			Req = cowboy_req:stream_reply(200,
				#{<<"content-type">> => <<"text/event-stream">>},
				Req0),
			cowboy_req:stream_events(#{
				event => <<"add_comment">>,
				data => <<"Comment text.\nWith many lines.">>
			}, fin, Req),
			{ok, Req, Opts};
		<<"list">> ->
			Req = cowboy_req:stream_reply(200,
				#{<<"content-type">> => <<"text/event-stream">>},
				Req0),
			cowboy_req:stream_events([
				#{
					event => <<"add_comment">>,
					data => <<"Comment text.\nWith many lines.">>
				},
				#{
					comment => <<"Set retry higher\nwith many lines also.">>,
					retry => 10000
				},
				#{
					id => <<"123">>,
					event => <<"add_comment">>,
					data => <<"Closing!">>
				}
			], fin, Req),
			{ok, Req, Opts};
		<<"multiple">> ->
			Req = cowboy_req:stream_reply(200,
				#{<<"content-type">> => <<"text/event-stream">>},
				Req0),
			cowboy_req:stream_events(#{
				event => <<"add_comment">>,
				data => <<"Comment text.\nWith many lines.">>
			}, nofin, Req),
			cowboy_req:stream_events(#{
				comment => <<"Set retry higher\nwith many lines also.">>,
				retry => 10000
			}, nofin, Req),
			cowboy_req:stream_events(#{
				id => <<"123">>,
				event => <<"add_comment">>,
				data => <<"Closing!">>
			}, fin, Req),
			{ok, Req, Opts}
	end;
do(<<"stream_trailers">>, Req0, Opts) ->
	case cowboy_req:binding(arg, Req0) of
		<<"large">> ->
			Req = cowboy_req:stream_reply(200, #{
				<<"trailer">> => <<"grpc-status">>
			}, Req0),
			%% The size should be larger than StreamSize and ConnSize
			cowboy_req:stream_body(<<0:80000000>>, nofin, Req),
			cowboy_req:stream_trailers(#{
				<<"grpc-status">> => <<"0">>
			}, Req),
			{ok, Req, Opts};
		<<"set_cookie">> ->
			ct_helper:ignore(cowboy_req, stream_trailers, 2),
			Req = cowboy_req:stream_reply(200, #{
				<<"trailer">> => <<"set-cookie">>
			}, Req0),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			cowboy_req:stream_trailers(#{
				<<"set-cookie">> => <<"name=value">>
			}, Req),
			{ok, Req, Opts};
		_ ->
			Req = cowboy_req:stream_reply(200, #{
				<<"trailer">> => <<"grpc-status">>
			}, Req0),
			cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
			cowboy_req:stream_trailers(#{
				<<"grpc-status">> => <<"0">>
			}, Req),
			{ok, Req, Opts}
	end;
do(<<"push">>, Req, Opts) ->
	case cowboy_req:binding(arg, Req) of
		<<"read_body">> ->
			cowboy_req:push("/echo/read_body", #{}, Req, #{});
		<<"method">> ->
			cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
				#{method => <<"HEAD">>});
		<<"origin">> ->
			cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
				#{scheme => <<"ftp">>, host => <<"127.0.0.1">>, port => 21});
		<<"qs">> ->
			cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req,
				#{qs => <<"server=cowboy&version=2.0">>});
		<<"after_reply">> ->
			ct_helper:ignore(cowboy_req, push, 4),
			Req1 = cowboy_req:reply(200, Req),
			%% We will crash here so no need to worry about propagating Req1.
			cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req1);
		_ ->
			cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req),
			%% The text/plain mime is not defined by default, so a 406 will be returned.
			cowboy_req:push("/static/plain.txt", #{<<"accept">> => <<"text/plain">>}, Req)
	end,
	{ok, cowboy_req:reply(200, Req), Opts}.

stream_body(Req) ->
	_ = [cowboy_req:stream_body(<<0:800000>>, nofin, Req) || _ <- lists:seq(1,9)],
	cowboy_req:stream_body(<<0:800000>>, fin, Req).
