from datetime import datetime, timedelta

units = ("year", "day", "hour", "minute", "second", "millisecond", "microsecond")


def get_delta_from_subject(subject):
    """Convert the subject to a timedelta and return it."""
    if isinstance(subject, timedelta):
        return subject

    if isinstance(subject, datetime):
        dt = subject
    else:
        # if it's not a datetime, assume it's a unix timestamp
        try:
            subject = float(subject)
        except ValueError:
            # some unknown type that couldn't be converted to a float
            raise TypeError("Unsupported subject type")

        dt = datetime.fromtimestamp(subject)

    # return a timedelta from the datetime, using its timezone (if it has one)
    return datetime.now(tz=dt.tzinfo) - dt


def delta2dict(delta):
    """Accepts a delta, returns a dictionary of units"""
    delta = abs(delta)
    return {
        "year": int(delta.days / 365),
        "day": int(delta.days % 365),
        "hour": int(delta.seconds / 3600),
        "minute": int(delta.seconds / 60) % 60,
        "second": int(delta.seconds % 60),
        "millisecond": int(delta.microseconds / 1000),
        "microsecond": int(delta.microseconds % 1000),
    }


def human(
    subject, precision=2, past_tense="{} ago", future_tense="in {}", abbreviate=False
):
    """
    Accept a subject, return a human readable timedelta string.

    :param subject: a datetime, timedelta, or timestamp (integer / float) object
    :param precision: the desired amount of unit precision (default: 2)
    :param past_tense: the format string used for a past timedelta (default: '{} ago')
    :param future_tense: the format string used for a future timedelta (default: 'in {}')
    :param abbreviate: boolean to abbreviate units (default: False)

    :returns: Human readable timedelta string (Str)
    """
    delta = get_delta_from_subject(subject)
    the_tense = future_tense if delta < timedelta(0) else past_tense

    d = delta2dict(delta)

    hlist = []
    count = 0

    # start building up the output in the hlist.
    for unit in units:
        if count >= precision:
            break  # met precision
        if d[unit] == 0:
            continue  # skip 0's
        if abbreviate:
            if unit == "millisecond":
                abr = "ms"
            elif unit == "microsecond":
                abr = "um"
            else:
                abr = unit[0]
            hlist.append("{}{}".format(d[unit], abr))
        else:
            s = "" if d[unit] == 1 else "s"  # handle plurals
            hlist.append("{} {}{}".format(d[unit], unit, s))
        count += 1

    return the_tense.format(", ".join(hlist))
