-module(ts_entry).
-export([create_table/1, new/1, new/2, update/1, update/2, write/1, write/2,
         delete/1, lookup/3, list_asc/3, list_desc/3]).

-include("ts_db_records.hrl").
-include_lib("stdlib/include/qlc.hrl").

create_table(TableOpts) ->
    mnesia:create_table(ts_entry,
        TableOpts ++ [{attributes, record_info(fields, ts_entry)},
                      {type, ordered_set}, {index, [timestamp]}]).

new(ER = #ts_entry{}) ->
    {atomic, NewRow} = mnesia:transaction(fun() -> do_new(ER) end),
    {ok, NewRow}.

new(ER = #ts_entry{}, ExtData) when is_list(ExtData) ->
    {atomic, NewRow} = mnesia:transaction(fun() ->
        NewRow = do_new(ER),
        ts_common:do_set_ext_data(ER, ExtData),
        NewRow end),
     {ok, NewRow}.

do_new(ER = #ts_entry{}) ->
        {Username, TimelineId, _} = ER#ts_entry.ref,
        NextId = id_counter:next_counter(ts_entry_id),
        RowWithRef = ER#ts_entry{ref = {Username, TimelineId, NextId}},
        NewRow = case RowWithRef#ts_entry.uuid of
            undefined -> RowWithRef#ts_entry{uuid = uuid:uuid4()};
            _ -> RowWithRef
        end,
        ok = mnesia:write(NewRow),
        NewRow.

update(ER = #ts_entry{}) -> ts_common:update(ER).

update(ER = #ts_entry{}, ExtData) when is_list(ExtData) ->
    ts_common:update(ER, ExtData).
    
write(ER = #ts_entry{}) -> mnesia:dirty_write(ER).

write(ER = #ts_entry{}, ExtData) ->
    {atomic, Result} = mnesia:transaction(fun() ->
        ok = mnesia:write(ER),
        ok = ts_common:do_set_ext_data(ER, ExtData)
    end),
    Result.

lookup(Username, TimelineId, EntryId) ->
    case mnesia:dirty_read(ts_entry, {Username, TimelineId, EntryId}) of
        [] -> no_record;
        [Entry] -> Entry
    end.

delete(ER = #ts_entry{}) -> mnesia:dirty_delete_object(ER).

list({Username, Timeline}, Start, Length, OrderFun)
when is_integer(Start) and is_integer(Length) ->

    {atomic, Entries} = mnesia:transaction(fun() ->
        % match the username and timeline
        MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, _='_'},

        % select all records that match
        mnesia:select(ts_entry, [{MatchHead, [], ['$_']}])
    end),

    % sort
    SortedEntries = lists:sort(OrderFun, Entries),

    % return only the range selected.
    % TODO: can we do this without selecting all entries?
    case length(SortedEntries) > Start of
        true -> lists:sublist(SortedEntries, Start + 1, Length);
        false -> []
    end;

list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) ->

    % compute the seconds from datetimes
    StartSeconds = calendar:datetime_to_gregorian_seconds(StartDateTime),
    EndSeconds = calendar:datetime_to_gregorian_seconds(EndDateTime),

    % select all entries from the timeline that are within the time range
    {atomic, Entries} = mnesia:transaction(fun() ->

        % match the username and timeline id
        MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, timestamp='$1', _='_'},

        % guards for the time range
        StartGuard = {'>=', '$1', StartSeconds},
        EndGuard = {'<', '$1', EndSeconds},

        mnesia:select(ts_entry, [{MatchHead, [StartGuard, EndGuard], ['$_']}])
    end),

    % sort
    lists:sort(OrderFun, Entries).

list_asc(TimelineRef, Start, Length)
when is_integer(Start) and is_integer(Length) ->
    list(TimelineRef, Start, Length, fun timestamp_asc/2);

list_asc(TimelineRef, StartDateTime, EndDateTime) ->
    list(TimelineRef, StartDateTime, EndDateTime, fun timestamp_asc/2).

list_desc(TimelineRef, Start, Length)
when is_integer(Start) and is_integer(Length) ->
    list(TimelineRef, Start, Length, fun timestamp_desc/2);

list_desc(TimelineRef, StartDateTime, EndDateTime) ->
    list(TimelineRef, StartDateTime, EndDateTime, fun timestamp_desc/2).

timestamp_asc(E1, E2) -> E1#ts_entry.timestamp > E2#ts_entry.timestamp.

timestamp_desc(E1, E2) -> E1#ts_entry.timestamp < E2#ts_entry.timestamp.
