%% -------------------------------------------------------------------
%%
%% cuttlefish_duration: complexity for parsing durations
%%
%% Copyright (c) 2013 Basho Technologies, Inc.  All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License.  You may obtain
%% a copy of the License at
%%
%%   http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied.  See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(cuttlefish_duration).

-opaque time_unit() :: f | w | d | h | m | s | ms.
-export_type([time_unit/0]).

-include("cuttlefish_duration.hrl").

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.

-export([parse/1, parse/2, to_string/2]).

-spec to_string(integer(), time_unit()) -> string().
to_string(Fortnights, f) -> milliseconds(Fortnights * ?FORTNIGHT);
to_string(Weeks, w)      -> milliseconds(Weeks * ?WEEK);
to_string(Days, d)       -> milliseconds(Days * ?DAY);
to_string(Hours, h)      -> milliseconds(Hours * ?HOUR);
to_string(Minutes, m)    -> milliseconds(Minutes * ?MINUTE);
to_string(Seconds, s)    -> milliseconds(Seconds * ?SECOND);
to_string(Millis, ms)    -> milliseconds(Millis).

milliseconds(Millis) ->
    Units = lists:filter(fun({N, _Unit}) -> N =/= 0 end, [
        { Millis div ?WEEK,                "w"},
        {(Millis rem ?WEEK)   div ?DAY,    "d"},
        {(Millis rem ?DAY)    div ?HOUR,   "h"},
        {(Millis rem ?HOUR)   div ?MINUTE, "m"},
        {(Millis rem ?MINUTE) div ?SECOND, "s"},
        { Millis rem ?SECOND,              "ms"}
    ]),
    lists:flatten([
        integer_to_list(N) ++ Unit
    || {N, Unit} <- Units]).

-spec parse(string(), time_unit()) -> integer().
parse(DurationString, ms) -> parse(DurationString);
parse(DurationString, Unit) ->
    case parse(DurationString) of
        {error, _}=Err ->
            Err;
        Num when is_number(Num) ->
            {Unit, Multiplier} = lists:keyfind(Unit, 1, ?MULTIPLIERS),
            cuttlefish_util:ceiling(Num / Multiplier)
    end.


-spec parse(string()) -> integer() | cuttlefish_error:error().
parse(InputDurationString) ->
    DurationString = string:to_lower(InputDurationString),
    case cuttlefish_duration_parse:parse(DurationString) of
        Float when is_float(Float) ->
            cuttlefish_util:ceiling(Float);
        Int when is_integer(Int) ->
            Int;
        _ ->
            {error, {duration, InputDurationString}}
    end.

-ifdef(TEST).

milliseconds_test() ->
    ?assertEqual("500ms", milliseconds(500)),
    ?assertEqual("1s500ms", milliseconds(1500)),
    ?assertEqual("30m", milliseconds(1800000)),
    ?assertEqual("1w1d1h1m1s1ms", milliseconds(694861001)),
    ok.

seconds_test() ->
    ?assertEqual("50s", to_string(50, s)),
    ?assertEqual("1m1s", to_string(61, s)),
    ?assertEqual("30m", to_string(1800, s)),
    ?assertEqual("1w1d1h1m1s", to_string(694861, s)),
    ok.

parse_test() ->
    test_parse(500,  "500ms"),
    test_parse(500,  ".5s"),
    test_parse(1001, "1s1ms"),
    test_parse(1599, "1s599ms"),
    test_parse(1599, "1.599s"),
    test_parse(1600, "1.5999s"),
    test_parse(60000, "1m"),
    test_parse(60000, "60s"),
    test_parse(60000, "60000ms"),
    test_parse(90000, "1.5m"),
    test_parse(90000, "1m30s"),
    test_parse(90000, "1m29s1000ms"),
    test_parse(1800000, ".5h"),
    test_parse(3600000, "1h"),
    test_parse(3601000, "1h1s"),
    test_parse(3660000, "1h1m"),
    test_parse(3661000, "1h1m1s"),
    test_parse(3661001, "1h1m1s1ms"),
    test_parse(3600000, "60m"),
    test_parse(5400000, "90m"),
    test_parse(5401000, "90m1s"),
    test_parse(5401001, "90m1s1ms"),
    test_parse(5400000, "1h30m"),
    test_parse(5401000, "1h30m1s"),
    test_parse(3660000, "1h1m"),
    test_parse(86400000, "1d"),
    test_parse(86401000, "1d1s"),
    test_parse(86401001, "1d1s1ms"),
    test_parse(604800000, "1w"),
    test_parse(691200000, "1w1d"),
    test_parse(694800000, "1w1d1h"),
    test_parse(694860000, "1w1d1h1m"),
    test_parse(694861000, "1w1d1h1m1s"),
    test_parse(694861001, "1w1d1h1m1s1ms"),

    %% Weird but ok?
    test_parse(121001, "1m1s1ms1m"),

    %% Easter Egg
    test_parse(1904461001, "1f1w1d1h1m1s1ms"),
    ok.

test_parse(ExpectedMillis, StringToParse) ->
    ?assertEqual(ExpectedMillis, parse(StringToParse)).

parse_2_test() ->
    ?assertEqual(1, parse("1ms", ms)),
    ?assertEqual(1, parse("1ms", s)),
    ?assertEqual(1, parse("1ms", m)),
    ?assertEqual(1, parse("1ms", h)),
    ?assertEqual(1, parse("1ms", d)),
    ?assertEqual(1, parse("1ms", w)),
    ?assertEqual(1, parse("1ms", f)),
    ok.

to_string_test() ->
    ?assertEqual("2w", to_string(1, f)),
    ?assertEqual("2w", to_string(2, w)),
    ?assertEqual("1w", to_string(1, w)),
    ?assertEqual("1d", to_string(1, d)),
    ?assertEqual("1w", to_string(7, d)),
    ?assertEqual("1h", to_string(1, h)),
    ?assertEqual("1m", to_string(1, m)),
    ok.

parse_error_test() ->
    ?assertMatch({error, _}, parse("1q")),
    %% This previously raised badarith because it did not check the
    %% return value of parse/1.
    ?assertMatch({error, _}, catch parse("1q", h)),
    ok.

-endif.
