#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ebin -pa ../lager/ebin -pa ../lager/deps/goldrush/ebin

-mode(compile).

-include_lib("kernel/include/file.hrl").

-define(DATA_COMMIT_START_COOKIE, 16#75c2e4b3d5f643a1).
-define(DATA_COMMIT_END_COOKIE, 16#2b05eed61b5af550).
-define(INDEX_COMMIT_END_COOKIE, 16#2fb1778c74a402e4).
-define(DATA_FILE_COOKIE, 16#99c99d588b1e7983).
-define(INDEX_FILE_COOKIE, 16#b7e16b02ba8a6d1b).
-define(DATA_ENTRY_COOKIE, 16#e7c1cdc2ba3dc77c).
-define(INDEX_NODE_COOKIE, 16#2e0f555ad73210d1).

openfile(Filename) ->
    {ok, File} = file:open(Filename, [read, write, binary, raw]),
    File.

%% getroot(File) ->
%%     {ok, RootNodeBinary} = file:read(File, ?NODESIZE),
%%             Nodemagic = ?NODEMAGIC,
%%             <<Nodemagic:2/binary, Node/binary>> = RootNodeBinary,
%%             ets:insert(State#state.cachename, {root, Node}),
%%             Node;
%%         [{root, Node}] ->
%%             Node
%%     end.

printfile(<<?INDEX_FILE_COOKIE:64/big, Rest/binary>>) ->
    io:format("index file header~n", []),
    printcommit(Rest, index, 8);
printfile(<<?DATA_FILE_COOKIE:64, Blocksize:32, Q:32, Keylength:32, Rest/binary>>) ->
    io:format("data file header: blocksize ~p q ~p keylength ~p~n", [Blocksize, Q, Keylength]),
    printcommit(Rest, data, 8+4*3);
printfile(<<Unknown:16/binary, _Rest/binary>>) ->
    io:format("unknown byte: ~p at file start~n", [Unknown]),
    error.

printnode(<<>>) ->
    ok;
printnode(<<Child:64/big, Rest/binary>>) ->
    case <<Child:64/big>> of
        <<0:1, Offset:63>> ->
            io:format("  child ~p~n", [Offset]);
        <<1:1, Offset:63>> ->
            io:format("  offset ~p~n", [Offset])
    end,
    printnode(Rest).

printcommit(Data, FileType, FileOffset) ->
    OneCommit = case FileType of
                    index ->
                        printindex(Data, FileOffset);
                    data ->
                        printdata(Data, FileOffset)
                end,
    case OneCommit of
        {{CommitLength, CommitChecksum, Rest}, FileOffset2, FileOffset3} ->
            io:format("------- commit ~p bytes at ~p-~p -------~n", [CommitLength, FileOffset, FileOffset3]),
            CalculatedChecksum = crypto:hash(sha256, binary:part(Data, 0, CommitLength)),
            if
                CommitChecksum /= CalculatedChecksum ->
                    io:format("incorrect checksum~n", []),
                    io:format("commit: length ~p checksum ~p~n", [CommitLength, CommitChecksum]),
                    io:format("calculated length ~p checksum ~p~n", [FileOffset2 - FileOffset, crypto:hash(sha256, binary:part(Data, 0, CommitLength))]),
                    io:format("checksummed data: ~p~n", [binary:part(Data, 0, CommitLength)]),
                    exit(assert);
                CommitLength /= (FileOffset2 - FileOffset) ->
                    io:format("length and offset mismatch~n", []),
                    io:format("commit: length ~p checksum ~p~n", [CommitLength, CommitChecksum]),
                    io:format("calculated length ~p checksum ~p~n", [FileOffset2 - FileOffset, crypto:hash(sha256, binary:part(Data, 0, CommitLength))]),
                    io:format("checksummed data: ~p~n", [binary:part(Data, 0, CommitLength)]),
                    exit(assert);
                true ->
                    ok
            end,
            printcommit(Rest, FileType, FileOffset3);
        {ok, FileOffset2} ->
            io:format("ending at offset ~p~n", [FileOffset]),
            ok
    end.

printindex(<<>>, FileOffset) ->
    {ok, FileOffset};
printindex(<<?INDEX_NODE_COOKIE:64/big, Rest/binary>>, FileOffset) ->
    FileOffset2 = FileOffset + 8,
    Q = 2,
    NChildren = 4,
    ChildrenLength = NChildren * 8,
    <<Children:ChildrenLength/binary, Rest2/binary>> = Rest,
    io:format("node ~p~n", [FileOffset]),
    printnode(Children),
    printindex(Rest2, FileOffset2 + ChildrenLength);
printindex(<<CommitLength:64/big, CommitChecksum:32/binary, ?INDEX_COMMIT_END_COOKIE:64/big, Rest/binary>>, FileOffset) ->
    {{CommitLength, CommitChecksum, Rest}, FileOffset + 8, FileOffset + 8 + 32 + 8};
printindex(<<Unknown:16/binary, _Rest/binary>>, FileOffset) ->
    io:format("unknown byte: ~p at ~p~n", [Unknown, FileOffset]),
    error.

printdata(<<>>, FileOffset) ->
    io:format("reached end of file~n", []),
    {ok, FileOffset};
printdata(<<?DATA_ENTRY_COOKIE:64, Key:32/binary, 1:16, Length:16, Rest/binary>>, FileOffset) ->
    FileOffset2 = FileOffset + 8 + 32 + 4,
    io:format("data ~p key ~p length ~p~n", [FileOffset, Key, Length]),
    case Rest of
        <<Data:Length/binary, Rest2/binary>> ->
	     printdata(Rest2, FileOffset2 + Length);
	 _ ->
	     io:format("data was truncated, remaining bytes: ~p ~p~n",
                       [size(Rest),
                        binary:part(Rest, 0, min(size(Rest), 16))]),
	     error
    end;
printdata(<<?DATA_COMMIT_START_COOKIE:64, Rest2/binary>> = Rest, FileOffset) ->
    Padding = case FileOffset rem 4 of
        0 ->
            0;
        1 ->
            3;
        2 ->
            2;
        3 ->
            1
    end,
    io:format("printdata: ~p ~p~n", [Padding, FileOffset]),
    case Rest2 of
        <<0:Padding/unit:8,
          CommitLength:64,
          CommitChecksum:32/binary,
          ?DATA_COMMIT_END_COOKIE:64,
          Rest3/binary>> ->
            {{CommitLength, CommitChecksum, Rest3},
             FileOffset + 8 + Padding + 8, FileOffset +
                 8 + Padding + 8 + 32 + 8};
        _ ->
            io:format("commit trailer was not correctly formed at ~p, "
                      ++ "expected padding ~p, remaining bytes: ~p ~w~n",
                      [FileOffset, Padding, size(Rest),
                       binary:part(Rest, 0, min(size(Rest), 64))]),
            error
    end;
printdata(Data, FileOffset) ->
   Unknown = binary:part(Data, 0, min(size(Data), 16)),
   io:format("unknown byte: ~p at ~p~n", [Unknown, FileOffset]),
   error.

read_whole_file(File, Size) ->
    case file:read(File, Size) of
        {ok, Data} ->
            RestData = read_whole_file(File, Size),
            <<Data/binary, RestData/binary>>;
        eof ->
            <<>>
    end.

main([Filename]) ->
    {ok, FileInfo} = file:read_file_info(Filename),
    File = openfile(Filename),
    Data = read_whole_file(File, FileInfo#file_info.size),
    io:format("read ~p bytes, file size ~p~n",
              [size(Data), FileInfo#file_info.size]),
    ok = printfile(Data),
    ok.