%%% Copyright (c) 2014, NORDUnet A/S.
%%% See LICENSE for licensing information.

%%% @doc Certificate Transparency (RFC 6962)

-module(v1).
%% API (URL)
-export([request/3]).

%% Public functions, i.e. part of URL.
request(post, "ct/v1/add-chain", Input) ->
    R = case (catch jiffy:decode(Input)) of
            {error, E} ->
                html("add-chain: bad input:", E);
            {[{<<"chain">>, ChainBase64}]} ->
                case (catch [base64:decode(X) || X <- ChainBase64]) of
                    {'EXIT', _} ->
                        html("add-chain: invalid base64-encoded chain: ",
                             [ChainBase64]);
                    [LeafCert | CertChain] ->
                        Roots = catlfish:known_roots(),
                        case x509:normalise_chain(Roots, [LeafCert|CertChain]) of
                            {ok, [Leaf | Chain]} ->
                                io:format("[info] adding ~p~n",
                                          [x509:cert_string(LeafCert)]),
                                success(catlfish:add_chain(Leaf, Chain));
                            {error, Reason} ->
                                io:format("[info] rejecting ~p: ~p~n",
                                          [x509:cert_string(LeafCert), Reason]),
                                html("add-chain: invalid chain", Reason)
                        end;
                    Invalid ->
                        html("add-chain: chain is not a list: ", [Invalid])
                end;
            _ -> html("add-chain: missing input: chain", Input)
        end,
    R;

request(post, "ct/v1/add-pre-chain", _Input) ->
    niy();

request(get, "ct/v1/get-sth", _Query) ->
    { Treesize,
       Timestamp,
       Roothash,
       Signature} = plop:sth(),
    R = [{tree_size, Treesize},
         {timestamp, Timestamp},
         {sha256_root_hash, base64:encode(Roothash)},
         {tree_head_signature, base64:encode(
                                 plop:serialise(Signature))}],
    success(jiffy:encode({R}));

request(get, "ct/v1/get-sth-consistency", Query) ->
    R = case lists:sort(Query) of
            [{"first", FirstInput}, {"second", SecondInput}] ->
                {First, _} = string:to_integer(FirstInput),
                {Second, _} = string:to_integer(SecondInput),
                case lists:member(error, [First, Second]) of
                    true ->
                        html("get-sth-consistency: bad input:",
                             [FirstInput, SecondInput]);
                    false ->
                        success(
                          jiffy:encode(
                            {[{consistency,
                               [base64:encode(X) ||
                                   X <- plop:consistency(First, Second)]}]}))
                end;
            _ -> html("get-sth-consistency: bad input:", Query)
        end,
    R;

request(get, "ct/v1/get-proof-by-hash", Query) ->
    R = case lists:sort(Query) of
            [{"hash", HashInput}, {"tree_size", TreeSizeInput}] ->
                Hash = case (catch base64:decode(HashInput)) of
                           {'EXIT', _} -> error;
                           H -> H
                       end,
                {TreeSize, _} = string:to_integer(TreeSizeInput),
                case lists:member(error, [Hash, TreeSize]) of
                    true ->
                        html("get-proof-by-hash: bad input:",
                             [HashInput, TreeSizeInput]);
                    false ->
                        success(
                          jiffy:encode(
                            case plop:inclusion(Hash, TreeSize) of
                                {ok, Index, Path} ->
                                    {[{leaf_index, Index},
                                      {audit_path,
                                       [base64:encode(X) || X <- Path]}]};
                                {notfound, Msg} ->
                                    %% FIXME: http status 400
                                    {[{success, false},
                                      {error_message, list_to_binary(Msg)}]}
                            end))
                end;
            _ -> html("get-proof-by-hash: bad input:", Query)
        end,
    R;

request(get, "ct/v1/get-entries", Query) ->
    R = case lists:sort(Query) of
            [{"end", EndInput}, {"start", StartInput}] ->
                {Start, _} = string:to_integer(StartInput),
                {End, _} = string:to_integer(EndInput),
                case lists:member(error, [Start, End]) of
                    true -> html("get-entries: bad input:", [Start, End]);
                    false -> success(catlfish:entries(Start, min(End, Start + 999)))
                end;
            _ -> html("get-entries: bad input:", Query)
        end,
    R;

request(get, "ct/v1/get-entry-and-proof", Query) ->
    R = case lists:sort(Query) of
            [{"leaf_index", IndexInput}, {"tree_size", TreeSizeInput}] ->
                {Index, _} = string:to_integer(IndexInput),
                {TreeSize, _} = string:to_integer(TreeSizeInput),
                case lists:member(error, [Index, TreeSize]) of
                    true ->
                        html("get-entry-and-proof: not integers: ",
                             [IndexInput, TreeSizeInput]);
                    false -> success(catlfish:entry_and_proof(Index, TreeSize))
                end;
            _ -> html("get-entry-and-proof: bad input:", Query)
        end,
    R;

request(get, "ct/v1/get-roots", _Query) ->
    R = [{certificates,
          [base64:encode(Der) ||
              Der <- catlfish:known_roots()]}],
    success(jiffy:encode({R}));

request(_Method, _Path, _) ->
    none.

%% Private functions.
html(Text, Input) ->
    {400, [{"Content-Type", "text/html"}],
     io_lib:format(
       "<html><body><p>~n" ++
           "~s~n" ++
           "~p~n" ++
           "</body></html>~n", [Text, Input])}.

niy() ->
    html("NIY - Not Implemented Yet|", []).

success(Data) ->
    {200, [{"Content-Type", "text/json"}], Data}.