-module(ts_common).
-export([new/1, new/2, update/1, update/2, update_record/2, do_set_ext_data/2,
         list/3, order_datetimes/2]).

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

new(Record) ->
    {atomic, Result} = mnesia:transaction(fun() -> do_new(Record) end),
    Result.

new(Record, ExtData) when is_list(ExtData) ->
    {atomic, Result} = mnesia:transaction(fun() ->
        case do_new(Record) of
            {error, Err} -> mnesia:abort({"Cannot create new record.", Err});
            NewRecord -> case do_set_ext_data(Record, ExtData) of
                    ok -> NewRecord;
                    Error -> mnesia:abort(Error)
            end
        end
    end),
    Result.

% required to be wrapped in a transaction
do_new(Record) ->
    Table = element(1, Record),

    % check for existing record
    case mnesia:read(Table, element(2, Record)) of
        % record exists
        [ExistingRecord] -> {error, {record_exists, ExistingRecord}};
        
        [] -> mnesia:write(Record)
    end.

update(Record) ->
    {atomic, Result} = mnesia:transaction(fun() -> do_update(Record) end),
    Result.

update(Record, ExtData) when is_list(ExtData) ->
    {atomic, Result} = mnesia:transaction(fun() ->
        case do_update(Record) of
            {error, Err} -> mnesia:abort({"Cannot update record.", Err});
            UpdatedRecord -> case do_set_ext_data(Record, ExtData) of
                ok -> UpdatedRecord;
                Error -> mnesia:abort({"Cannot update record.", Error})
            end
        end
    end),
    Result.

do_update(Record) ->
    Table = element(1, Record),
    Key = element(2, Record),

    % look for existing record
    case mnesia:read(Table, Key) of
        % record does not exist, cannot update
        [] -> no_record;
        % record does exist, update
        [ExistingRecord] ->
            mnesia:write(update_record(ExistingRecord, Record))
    end.

update_record(Record, UpdateData) ->
    update_record(tuple_to_list(Record), tuple_to_list(UpdateData), []).
    
update_record([], [], Updated) -> list_to_tuple(lists:reverse(Updated));
update_record([Field|RecordFields], [FieldReplacement|UpdateData], Acc) ->
    UpdatedField = case FieldReplacement of
        undefined -> Field;
        NewValue -> NewValue
    end,

    update_record(RecordFields, UpdateData, [UpdatedField|Acc]).

%% list <Length> number of records, skipping the first <Start>
list(Table, Start, Length)
when is_atom(Table) and is_integer(Start) and is_integer(Length) ->
    list(qlc:q([A || A <- mnesia:table(Table)]), Start, Length);

list(Query, Start, Length) ->
    {atomic, Result} = mnesia:transaction(fun() ->
        % create a cursor for the query
        C = qlc:cursor(Query),

        % skip the first Start records
        if Start > 0 -> qlc:next_answers(C, Start);
            true -> ok
        end,

        % return Length records
        List = qlc:next_answers(C, Length),

        % free cursor
        ok = qlc:delete_cursor(C),

        List
    end),
    Result.

% should be wrapped in a transaction
do_set_ext_data(Record, ExtData) when is_list(ExtData) ->
    lists:foreach(
        fun({Key, Val}) ->
            ok = ts_ext_data:set_property(Record, Key, Val)
        end,
        ExtData),
    ok.

% This is somewhat ridiculous.
order_datetimes({{Y1, Mon1, D1}, {H1, Min1, S1}},
                {{Y2, Mon2, D2}, {H2, Min2, S2}}) ->

    % this is actually a deep-nested set of case statements, but it seems
    % cleaner to keep the indentation level and follow a strict pattern

    % compare field.If /=, return that value, else drop through to next field
    case Y1   = Y2   of false -> Y1   > Y2;   true ->
    case Mon1 = Mon2 of false -> Mon1 > Mon2; true ->
    case D1   = D2   of false -> D1   > D2;   true ->
    case H1   = H2   of flase -> H1   > H2;   true ->
    case Min1 = Min2 of false -> Min1 > Min2; true ->
    case S1   = S2   of false -> S1   > S2;   true -> true

  % sec min hr  day mon year
    end end end end end end.
