%%% 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) ->
    case (catch mochijson2:decode(Input)) of
        {error, E} ->
            html("add-chain: bad input:", E);
        {struct, [{<<"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]} ->
                            lager:info("adding ~p",
                                           [x509:cert_string(LeafCert)]),
                            success(catlfish:add_chain(Leaf, Chain));
                        {error, Reason} ->
                            lager:info("rejecting ~p: ~p",
                                       [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;

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({R});

request(get, "ct/v1/get-sth-consistency", Query) ->
    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(
                      {[{consistency,
                         [base64:encode(X) ||
                             X <- plop:consistency(First, Second)]}]})
            end;
        _ -> html("get-sth-consistency: bad input:", Query)
    end;

request(get, "ct/v1/get-proof-by-hash", Query) ->
    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 ->
                      case plop:inclusion(Hash, TreeSize) of
                          {ok, Index, Path} ->
                              success({[{leaf_index, Index},
                                        {audit_path,
                                         [base64:encode(X) || X <- Path]}]});
                          {notfound, Msg} ->
                              html("get-proof-by-hash: hash not found", Msg)
                      end
            end;
        _ -> html("get-proof-by-hash: bad input:", Query)
    end;

request(get, "ct/v1/get-entries", Query) ->
    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;

request(get, "ct/v1/get-entry-and-proof", Query) ->
    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;

request(get, "ct/v1/get-roots", _Query) ->
    R = [{certificates,
          [base64:encode(Der) ||
              Der <- catlfish:update_known_roots()]}],
    success({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"}], mochijson2:encode(Data)}.