%%%=============================================================================
%%% @copyright (C) 2013, Erlang Solutions Ltd
%%% @author Diana Corbacho <diana.corbacho@erlang-solutions.com>
%%% @doc
%%%
%%% @end
%%%=============================================================================
-module(fusco_protocol).
-copyright("2013, Erlang Solutions Ltd.").

-include("fusco.hrl").

-define(SIZE(Data, Response), Response#response{size = Response#response.size + byte_size(Data)}).
-define(RECEPTION(Data, Response), Response#response{size = byte_size(Data),
						     in_timestamp = os:timestamp()}).
-define(TOUT, 1000).
%% Latency is here defined as the time from the start of packet transmission to the start of packet reception

%% API
-export([recv/2, recv/3,
	 decode_cookie/1]).

%% TEST
-export([decode_header_value/5, decode_header_value/6,
	 decode_header/3, decode_header/4]).

%% TODO handle partial downloads

recv(Socket, Ssl) ->
    recv(Socket, Ssl, infinity).

recv(Socket, Ssl, Timeout) ->
    case fusco_sock:recv(Socket, Ssl, Timeout) of
	{ok, Data} ->
	    decode_status_line(<< Data/binary >>,
			       ?RECEPTION(Data, #response{socket = Socket, ssl = Ssl}), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end.

decode_status_line(<<"HTTP/1.0\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) ->
    decode_reason_phrase(Rest, <<>>, Response#response{version = {1,0},
						       status_code = <<C1,C2,C3>>}, Timeout);
decode_status_line(<<"HTTP/1.1\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) ->
    decode_reason_phrase(Rest, <<>>, Response#response{version = {1,1},
						       status_code = <<C1,C2,C3>>}, Timeout);
decode_status_line(Bin, Response = #response{size = Size}, Timeout) when Size < 13 ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_status_line(<<Bin/binary, Data/binary>>, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;    
decode_status_line(_, _, _) ->
    {error, status_line}.

decode_reason_phrase(<<>>, Acc, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_reason_phrase(Data, Acc, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_reason_phrase(<<$\r>>, Acc, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_reason_phrase(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_reason_phrase(<<$\n, Rest/bits>>, Acc, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout);
decode_reason_phrase(<<$\r,$\n, Rest/bits>>, Acc, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout);
decode_reason_phrase(<<C, Rest/bits>>, Acc, Response, Timeout) ->
    decode_reason_phrase(Rest, <<Acc/binary, C>>, Response, Timeout).

decode_header(Data, Acc, Response) ->
    decode_header(Data, Acc, Response, infinity).

decode_header(<<>>, Acc, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header(Data, Acc, ?SIZE(Data, Response), Timeout);
	{error, closed} ->
	    case Acc of
		<<>> ->
		    decode_body(<<>>, Response, Timeout);
		_ ->
		    {error, closed}
	    end;
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header(<<$\r>>, Acc, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header(<<$\s, Rest/bits>>, Acc, Response, Timeout) ->
    decode_header(Rest, Acc, Response, Timeout);
decode_header(<<$:, Rest/bits>>, Header, Response, Timeout) ->
    decode_header_value_ws(Rest, Header, Response, Timeout);
decode_header(<<$\n, Rest/bits>>, <<>>, Response, Timeout) ->
    decode_body(Rest, Response, Timeout);
decode_header(<<$\r, $\n, Rest/bits>>, <<>>, Response, Timeout) ->
    decode_body(Rest, Response, Timeout);
decode_header(<<$\r, $\n, _Rest/bits>>, _, _Response, _Timeout) ->
    {error, header};
decode_header(<<$A, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $a>>, Response, Timeout);
decode_header(<<$B, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $b>>, Response, Timeout);
decode_header(<<$C, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $c>>, Response, Timeout);
decode_header(<<$D, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $d>>, Response, Timeout);
decode_header(<<$E, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $e>>, Response, Timeout);
decode_header(<<$F, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $f>>, Response, Timeout);
decode_header(<<$G, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $g>>, Response, Timeout);
decode_header(<<$H, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $h>>, Response, Timeout);
decode_header(<<$I, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $i>>, Response, Timeout);
decode_header(<<$J, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $j>>, Response, Timeout);
decode_header(<<$K, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $k>>, Response, Timeout);
decode_header(<<$L, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $l>>, Response, Timeout);
decode_header(<<$M, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $m>>, Response, Timeout);
decode_header(<<$N, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $n>>, Response, Timeout);
decode_header(<<$O, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $o>>, Response, Timeout);
decode_header(<<$P, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $p>>, Response, Timeout);
decode_header(<<$Q, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $q>>, Response, Timeout);
decode_header(<<$R, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $r>>, Response, Timeout);
decode_header(<<$S, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $s>>, Response, Timeout);
decode_header(<<$T, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $t>>, Response, Timeout);
decode_header(<<$U, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $u>>, Response, Timeout);
decode_header(<<$V, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $v>>, Response, Timeout);
decode_header(<<$W, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $w>>, Response, Timeout);
decode_header(<<$X, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $x>>, Response, Timeout);
decode_header(<<$Y, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $y>>, Response, Timeout);
decode_header(<<$Z, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, $z>>, Response, Timeout);
decode_header(<<C, Rest/bits>>, Header, Response, Timeout) ->
    decode_header(Rest, <<Header/binary, C>>, Response, Timeout).

decode_header_value_ws(<<$\s, Rest/bits>>, H, S, Timeout) ->
    decode_header_value_ws(Rest, H, S, Timeout);
decode_header_value_ws(<<$\t, Rest/bits>>, H, S, Timeout) ->
    decode_header_value_ws(Rest, H, S, Timeout);
decode_header_value_ws(Rest, <<"connection">> = H, S, Timeout) ->
    decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout);
decode_header_value_ws(Rest, <<"transfer-encoding">> = H, S, Timeout) ->
    decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout);
decode_header_value_ws(Rest, H, S, Timeout) ->
    decode_header_value(Rest, H, <<>>, <<>>, S, Timeout).

decode_header_value(Data, H, V, T, Response) ->
    decode_header_value(Data, H, V, T, Response, infinity).

decode_header_value(<<>>, H, V, T, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header_value(Data, H, V, T, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header_value(<<$\r>>, H, V, T, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header_value(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header_value(<<$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
						content_length = binary_to_integer(V)}, Timeout);
decode_header_value(<<$\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V)
							   | Response#response.cookies],
					  headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\r, $\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V)
						     | Response#response.cookies],
					  headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\r,$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
					  content_length = binary_to_integer(V)}, Timeout);
decode_header_value(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value(Rest, H, V, <<T/binary, $\s>>, Response, Timeout);
decode_header_value(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value(Rest, H, V, <<T/binary, $\t>>, Response, Timeout);
decode_header_value(<<C, Rest/bits>>, H, V, <<>>, Response, Timeout) ->
    decode_header_value(Rest, H, <<V/binary, C>>, <<>>, Response, Timeout);
decode_header_value(<<C, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value(Rest, H, <<V/binary, T/binary, C>>, <<>>, Response, Timeout).

decode_header_value_lc(<<>>, H, V, T, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header_value_lc(Data, H, V, T, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header_value_lc(<<$\r>>, H, V, T, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_header_value_lc(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout);
	{error, Reason} ->
	    {error, Reason}
    end;
decode_header_value_lc(<<$\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
						transfer_encoding = V}, Timeout);
decode_header_value_lc(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
						connection = V}, Timeout);
decode_header_value_lc(<<$\r, $\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
						transfer_encoding = V}, Timeout);
decode_header_value_lc(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
    decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
						connection = V}, Timeout);
decode_header_value_lc(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, V, <<T/binary, $\s>>, Response, Timeout);
decode_header_value_lc(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, V, <<T/binary, $\t>>, Response, Timeout);
decode_header_value_lc(<<$A, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $a>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$B, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $b>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$C, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $c>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$D, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $d>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$E, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $e>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$F, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $f>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$G, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $g>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$H, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $h>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$I, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $i>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$J, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $j>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$K, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $k>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$L, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $l>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$M, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $m>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$N, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $n>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$O, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $o>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$P, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $p>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Q, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $q>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$R, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $r>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$S, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $s>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$T, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $t>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$U, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $u>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$V, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $v>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$W, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $w>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$X, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $x>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Y, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $y>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Z, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, $z>>, <<>>, Response, Timeout);
decode_header_value_lc(<<C, Rest/bits>>, H, V, T, Response, Timeout) ->
    decode_header_value_lc(Rest, H, <<V/binary, T/binary, C>>, <<>>, Response, Timeout).

%% RFC 6265
%% TODO decode cookie values, this only accepts 'a=b'
decode_cookie(Cookie) ->
    decode_cookie_name(Cookie, <<>>).

decode_cookie_name(<<$\s, Rest/bits>>, N) ->
    decode_cookie_name(Rest, N);
decode_cookie_name(<<$\t, Rest/bits>>, N) ->
    decode_cookie_name(Rest, N);
decode_cookie_name(<<$=, Rest/bits>>, N) ->
    decode_cookie_value(Rest, N, <<>>);
decode_cookie_name(<<C, Rest/bits>>, N) ->
    decode_cookie_name(Rest, <<N/binary, C>>).

decode_cookie_value(<<$\s, Rest/bits>>, N, V) ->
    decode_cookie_value(Rest, N, V);
decode_cookie_value(<<$\t, Rest/bits>>, N, V) ->
    decode_cookie_value(Rest, N, V);
decode_cookie_value(<<$;, Rest/bits>>, N, V) ->
    decode_cookie_av_ws(Rest, #fusco_cookie{name = N, value = V});
decode_cookie_value(<<C, Rest/bits>>, N, V) ->
    decode_cookie_value(Rest, N, <<V/binary, C>>);
decode_cookie_value(<<>>, N, V) ->
    #fusco_cookie{name = N, value = V}.

decode_cookie_av_ws(<<$\s, Rest/bits>>, C) ->
    decode_cookie_av_ws(Rest, C);
decode_cookie_av_ws(<<$\t, Rest/bits>>, C) ->
    decode_cookie_av_ws(Rest, C);
%% We are only interested on Expires, Max-Age, Path, Domain
decode_cookie_av_ws(<<$e, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$e>>);
decode_cookie_av_ws(<<$E, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$e>>);
decode_cookie_av_ws(<<$m, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$m>>);
decode_cookie_av_ws(<<$M, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$m>>);
decode_cookie_av_ws(<<$p, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$p>>);
decode_cookie_av_ws(<<$P, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$p>>);
decode_cookie_av_ws(<<$d, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$d>>);
decode_cookie_av_ws(<<$D, Rest/bits>>, C) ->
    decode_cookie_av(Rest, C, <<$d>>);
decode_cookie_av_ws(Rest, C) ->
    ignore_cookie_av(Rest, C).

ignore_cookie_av(<<$;, Rest/bits>>, Co) ->
    decode_cookie_av_ws(Rest, Co);
ignore_cookie_av(<<_, Rest/bits>>, Co) ->
    ignore_cookie_av(Rest, Co);
ignore_cookie_av(<<>>, Co) ->
    Co.

%% Match only uppercase chars on Expires, Max-Age, Path, Domain
decode_cookie_av(<<$=, Rest/bits>>, Co, AV) ->
    decode_cookie_av_value(Rest, Co, AV, <<>>);
decode_cookie_av(<<$D, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $d>>);
decode_cookie_av(<<$O, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $o>>);
decode_cookie_av(<<$N, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $n>>);
decode_cookie_av(<<$E, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $e>>);
decode_cookie_av(<<$X, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $x>>);
decode_cookie_av(<<$P, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $p>>);
decode_cookie_av(<<$I, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $i>>);
decode_cookie_av(<<$R, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $r>>);
decode_cookie_av(<<$S, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $s>>);
decode_cookie_av(<<$M, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $m>>);
decode_cookie_av(<<$A, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $a>>);
decode_cookie_av(<<$G, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $g>>);
decode_cookie_av(<<$T, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $t>>);
decode_cookie_av(<<$H, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, $h>>);
decode_cookie_av(<<$;, Rest/bits>>, Co, _AV) ->
    decode_cookie_av_ws(Rest, Co);
decode_cookie_av(<<C, Rest/bits>>, Co, AV) ->
    decode_cookie_av(Rest, Co, <<AV/binary, C>>);
decode_cookie_av(<<>>, Co, _AV) ->
    ignore_cookie_av(<<>>, Co).

decode_cookie_av_value(<<>>, Co, <<"path">>, Value) ->
    Co#fusco_cookie{path_tokens = binary:split(Value, <<"/">>, [global]),
                    path = Value};
decode_cookie_av_value(<<>>, Co, <<"max-age">>, Value) ->
    Co#fusco_cookie{max_age = max_age(Value)};
decode_cookie_av_value(<<>>, Co, <<"expires">>, Value) ->
    Co#fusco_cookie{expires = expires(Value)};
decode_cookie_av_value(<<>>, Co, <<"domain">>, Value) ->
    Co#fusco_cookie{domain = Value};
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"path">>, Value) ->
    Path = binary:split(Value, <<"/">>, [global]),
    decode_cookie_av_ws(Rest, Co#fusco_cookie{path_tokens = Path,
                                              path = Value});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"max-age">>, Value) ->
    decode_cookie_av_ws(Rest, Co#fusco_cookie{
				max_age = max_age(Value)});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"expires">>, Value) ->
    %% TODO parse expires
    decode_cookie_av_ws(Rest, Co#fusco_cookie{expires = expires(Value)});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"domain">>, Value) ->
    decode_cookie_av_ws(Rest, Co#fusco_cookie{domain = Value});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, _, _) ->
    decode_cookie_av_ws(Rest, Co);
decode_cookie_av_value(<<C, Rest/bits>>, Co, AV, Value) ->
    decode_cookie_av_value(Rest, Co, AV, <<Value/binary, C>>).


decode_body(<<>>, Response = #response{status_code = <<$1, _, _>>,
                       transfer_encoding = TE}, _Timeout)
  when TE =/= <<"chunked">> ->
    return(<<>>, Response);
decode_body(<<$\r, $\n, Rest/bits>>, Response, Timeout) ->
    decode_body(Rest, Response, Timeout);
decode_body(Rest, Response = #response{status_code = <<$1, _, _>>,
				       transfer_encoding = TE}, Timeout)
  when TE =/= <<"chunked">> ->
    decode_status_line(Rest, #response{socket = Response#response.socket,
				       ssl = Response#response.ssl,
				       in_timestamp = Response#response.in_timestamp}, Timeout);
decode_body(Rest, Response = #response{transfer_encoding = <<"chunked">>}, Timeout) ->
    decode_chunked_body(Rest, <<>>, <<>>, Response, Timeout);
decode_body(Rest, Response, Timeout) ->
    case byte_size(Rest) >= Response#response.content_length of
	true ->
	    return(Rest, Response);
	false ->
	    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
		{ok, Data} ->
		    decode_body(<<Rest/binary, Data/binary>>, ?SIZE(Data, Response), Timeout);
		_ ->
		    %% NOTE: Return what we have so far
		    return(Rest, Response)
	    end
    end.

download_chunked_body(Rest, Acc, Size, Response, Timeout) ->
    case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
	{ok, Data} ->
	    decode_chunked_body(<<Rest/bits, Data/bits>>, Acc, Size,
				?SIZE(Data, Response), Timeout);
	_ ->
	    return(Acc, Response)
    end.

decode_chunked_body(<<$0,$\r,$\n,$\r,$\n>>, Acc, _, Response, _Timeout) ->
    return(Acc, Response);
decode_chunked_body(<<$0, Rest/bits>> = R, Acc, Size, Response, Timeout)
  when is_binary(Size),	byte_size(Rest) < 4 ->
    download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(<<$\r>> = R, Acc, Size, Response, Timeout) when is_binary(Size) ->
    download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, <<>>, Response, Timeout) ->
    decode_chunked_body(Rest, Acc, <<>>, Response, Timeout);
decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, Size, Response, Timeout) when is_binary(Size) ->
    IntSize = erlang:binary_to_integer(Size, 16),
    decode_chunked_body(Rest, Acc, IntSize, Response, Timeout);
decode_chunked_body(<<C, Rest/bits>>, Acc, Size, Response, Timeout) when is_binary(Size) ->
    decode_chunked_body(Rest, Acc, <<Size/bits, C>>, Response, Timeout);
decode_chunked_body(<<>> = R, Acc, Size, Response, Timeout) when is_binary(Size) ->
    download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(Rest, Acc, Size, Response, Timeout) when is_integer(Size) ->
    case byte_size(Rest) of
	S when S == Size ->
	    decode_chunked_body(<<>>, <<Acc/bits, Rest/bits>>, <<>>, Response, Timeout);
	S when S < Size ->
	    download_chunked_body(Rest, Acc, Size, Response, Timeout);
	S when S > Size ->
	    Current = binary:part(Rest, 0, Size),
	    Next = binary:part(Rest, Size, S - Size),
	    decode_chunked_body(Next, <<Acc/bits, Current/bits>>, <<>>, Response, Timeout)
    end.

return(Body, Response) ->
    Response#response{body = Body}.

max_age(Value) ->
    binary_to_integer(Value) * 1000000.

%% http://tools.ietf.org/html/rfc2616#section-3.3.1
%% Supports some non-standard datetime (Tomcat) Tue, 06-Nov-1994 08:49:37 GMT
expires(<<_,_,_,$,,$\s,D1,D2,$\s,M1,M2,M3,$\s,Y1,Y2,Y3,Y4,$\s,Rest/bits>>) ->
    expires(Rest, {list_to_integer([Y1,Y2,Y3,Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])});
expires(<<_,_,_,$\s,Mo1,Mo2,Mo3,$\s,D1,D2,$\s,H1,H2,$:,M1,M2,$:,S1,S2,$\s,Y1,Y2,Y3,Y4,_Rest/bits>>) ->
    {{list_to_integer([Y1,Y2,Y3,Y4]),month(<<Mo1,Mo2,Mo3>>),list_to_integer([D1,D2])},
     {list_to_integer([H1,H2]), list_to_integer([M1,M2]), list_to_integer([S1,S2])}};
expires(<<_,_,_,$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Monday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Tuesday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Wednesday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Thursday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Friday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Saturday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<"Sunday",$,,$\s,Rest/bits>>) ->
    expires(Rest);
expires(<<D1,D2,$\-,M1,M2,M3,$\-,Y1,Y2,Y3,Y4,$\s,Rest/bits>>) ->
    expires(Rest, {list_to_integer([Y1,Y2,Y3,Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])});
expires(<<D1,D2,$\-,M1,M2,M3,$\-,Y3,Y4,$\s,Rest/bits>>) ->
    %% http://tools.ietf.org/html/rfc2616#section-19.3
    %% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
    %% which appears to be more than 50 years in the future is in fact
    %% in the past (this helps solve the "year 2000" problem).
    expires(Rest, {to_year([Y3, Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])}).

to_year(List) ->
    Int = list_to_integer(List),
    {Y, _, _} = date(),
    case (2000 + Int - Y) > 50 of
        true ->
            1900 + Int;
        false ->
            2000 + Int
    end.

expires(<<H1,H2,$:,M1,M2,$:,S1,S2,_Rest/bits>>, Date) ->
    {Date, {list_to_integer([H1,H2]), list_to_integer([M1,M2]), list_to_integer([S1,S2])}}.

month(<<$J,$a,$n>>) ->
    1;
month(<<$F,$e,$b>>) ->
    2;
month(<<$M,$a,$r>>) ->
    3;
month(<<$A,$p,$r>>) ->
    4;
month(<<$M,$a,$y>>) ->
    5;
month(<<$J,$u,$n>>) ->
    6;
month(<<$J,$u,$l>>) ->
    7;
month(<<$A,$u,$g>>) ->
    8;
month(<<$S,$e,$p>>) ->
    9;
month(<<$O,$c,$t>>) ->
    10;
month(<<$N,$o,$v>>) ->
    11;
month(<<$D,$e,$c>>) ->
    12.
