anchor.diff:  -module(ranger).
  -
  -%% Test App.
  --export([start/0]).
   
   %% API.
   -export([upgrade/4]).
  
positive.code: type_from_proto(Proto) ->
    Proto.
  
  forwarded_header_prefix(Req, State = #state{req_headers = ReqHeaders}) ->
    case call(Req, State, forwarded_header_prefix) of
      no_call ->
        next(Req, State, fun request_id/2);
      {Prefix, Req2, HandlerState} ->
        ForwardedHeaders = format_forwarded_headers(Req, Prefix),
        State2 = State#state{handler_state = HandlerState,
                             req_headers = ForwardedHeaders ++ ReqHeaders},
        next(Req2, State2, fun request_id/2)
    end.
  
  request_id(Req, State = #state{req_headers = ReqHeaders, res_headers = ResHeaders}) ->
    case call(Req, State, request_id) of
      no_call ->
        next(Req, State, fun req_headers/2);
      {{Name, ID}, Req2, HandlerState} ->
        State2 = State#state{handler_state = HandlerState,
                             req_headers = [{Name, ID}|ReqHeaders],
                             res_headers = [{Name, ID}|ResHeaders]},
        next(Req2, State2, fun req_headers/2);
      {ID, Req2, HandlerState} ->
        State2 = State#state{handler_state = HandlerState,
                             req_headers = [{<<"x-request-id">>, ID}|ReqHeaders],
                             res_headers = [{<<"x-request-id">>, ID}|ResHeaders]},
        next(Req2, State2, fun req_headers/2)
    end.
  
  req_headers(Req, State = #state{req_headers = ReqHeaders}) ->
    %% TODO filter headers - connection, host, content-length
    ReqHeaders2 = filter_headers(ReqHeaders, []),
    case call(Req, State, req_headers, ReqHeaders2) of
      no_call ->
        next(Req, State#state{req_headers = ReqHeaders2}, fun init_request/2);
      {ReqHeaders3, Req2, HandlerState} ->
        State2 = State#state{handler_state = HandlerState,
                             req_headers = ReqHeaders3},
        next(Req2, State2, fun init_request/2)
    end.
  
  filter_headers([], Acc) ->
    Acc;
  filter_headers([{<<"host">>, _}|Headers], Acc) ->
    filter_headers(Headers, Acc);
  filter_headers([{<<"connection">>, _}|Headers], Acc) ->
    filter_headers(Headers, Acc);
  filter_headers([{<<"content-length">>, _}|Headers], Acc) ->
    filter_headers(Headers, Acc);
  filter_headers([Header|Headers], Acc) ->
    filter_headers(Headers, [Header|Acc]).
  
  init_request(Req, State = #state{conn = Conn, method = Method, backend = {_, _, _, BasePath}, req_headers = Headers}) ->
    {Parts, Req2} = cowboy_req:path_info(Req),
    Path = path_join(BasePath, Parts),
    %% TODO add the cowboy_req:qs
    io:format("~n~p ~p~n~p~n", [Method, Path, Headers]),
    Ref = gun:request(Conn, Method, Path, Headers),
    next(Req2, State#state{ref = Ref}, fun req_body/2).
  
  path_join(<<>>, Parts) ->
    [<<"/">>, io_join(lists:reverse(Parts), <<"/">>, [])];
  path_join(<<"/">>, Parts) ->
    [<<"/">>, io_join(lists:reverse(Parts), <<"/">>, [])];
  path_join(BasePath, Parts) ->
    io_join(lists:reverse([BasePath|Parts]), <<"/">>, []).
  
  io_join([], _Sep, Acc) ->
    Acc;
  io_join([Item], _Sep, Acc) ->
    [Item, Acc];
  io_join([Item|Items], Sep, Acc) ->
    io_join(Items, Sep, [Sep, Item, Acc]).
  
  req_body(Req, State = #state{conn = Conn, ref = Ref}) ->
    case cowboy_req:has_body(Req) of
      true ->
        case exported(Req, State, req_body, 3) of
          true ->
            %% TODO should we catch the errors?
            %% TODO expose max body size variable
            {ok, Body, Req2} = cowboy_req:body(Req),
            {Body2, Req3, State2} = call(Req2, State, req_body, Body),
            ok = gun:data(Conn, Ref, fin, Body2),
            next(Req3, State2, fun res_status/2);
          _ ->
            next(Req, State, fun chunk_req_body/2)
        end;
      _ ->
        next(Req, State, fun res_status/2)
    end.
  
  chunk_req_body(Req, State = #state{conn = Conn, ref = Ref}) ->
    case cowboy_req:stream_body(Req) of
      {ok, Body, Req2} ->
        ok = gun:data(Conn, Ref, nofin, Body),
        chunk_req_body(Req2, State);
      {done, Req2} ->
        ok = gun:date(Conn, Ref, fin, <<>>),
        next(Req2, State, fun res_status/2);
      {error, _Reason} ->
        %% TODO send back a better error message
        next(Req, State, 400)
    end.
  
  res_status(Req, State = #state{conn = Conn, ref = Ref, timeout = _Timeout, res_headers = ResHeaders}) ->
    receive
      {gun_response, Conn, Ref, _Fin, Status, Headers} ->
        %% TODO handle if fin
        next(Req, State#state{res_status = Status, res_headers = Headers ++ ResHeaders}, fun res_headers/2)
    %% after Timeout TODO
    end.
  
  res_headers(Req, State = #state{res_status = ResStatus, res_headers = ResHeaders}) ->
    %% TODO filter the response headers - connection, content-length, date?
  
    {ManipulatedHeaders, Req2, State2} = case call(Req, State, res_headers, ResHeaders) of
      no_call ->
        {ResHeaders, Req, State};
      Result ->
        Result
    end,
    {ok, Req3} = cowboy_req:chunked_reply(ResStatus, ManipulatedHeaders, Req2),
    next(Req3, State2, fun get_res_body/2).
  
  get_res_body(Req, State) ->
    case exported(Req, State, res_body, 3) of
      true ->
        %% TODO
        ok;
      _ ->
        stream_body(Req, State)
    end.
  
  stream_body(Req, State = #state{conn = Conn, ref = Ref, timeout = _Timeout}) ->
    receive
      {gun_data, Conn, Ref, nofin, Data} ->
        ok = cowboy_req:chunk(Data, Req),
        stream_body(Req, State);
      {gun_data, Conn, Ref, fin, Data} ->
        ok = cowboy_req:chunk(Data, Req),
        terminate(Req, State)
    %% after Timeout TODO
    end.
  
  %% Formatting
  format_forwarded_headers(Req, Prefix) ->
    %% TODO this is wrong. fix this to use the values from the upstream request
    [Host, Port, Path] = cowboy_req:get([host, port, path], Req),
    [
      {<<Prefix/binary, "-proto">>, <<"http">>}, %% TODO
      {<<Prefix/binary, "-host">>, Host},
      {<<Prefix/binary, "-port">>, integer_to_binary(Port)},
      {<<Prefix/binary, "-path">>, Path}
    ].
  
  %% Protocol
  
  call(Req, State=#state{handler=Handler, handler_state=HandlerState}, Callback) ->
    case erlang:function_exported(Handler, Callback, 2) of
      true ->
        try
  
negative.code:     {state, HandlerState}
    ]).
  
  proxy_terminate(Req, #state{handler = Handler, handler_state = HandlerState}) ->
    case erlang:function_exported(Handler, proxy_terminate, 2) of
      true -> ok = Handler:proxy_terminate(
        cowboy_req:lock(Req), HandlerState);
  