"""
The gametime module handles the global passage of time in the mud.

It also supplies some useful methods to convert between
in-mud time and real-world time as well allows to get the
total runtime of the server and the current uptime.
"""

from time import time
from django.conf import settings
from evennia.scripts.scripts import Script
from evennia.utils.create import create_script

GAMETIME_SCRIPT_NAME = "sys_game_time"

# Speed-up factor of the in-game time compared
# to real time.

TIMEFACTOR = settings.TIME_FACTOR

# Common real-life time measure, in seconds.
# You should not change this.

REAL_MIN = 60.0  # seconds per minute in real world

# Game-time units, in real-life seconds. These are supplied as
# a convenient measure for determining the current in-game time,
# e.g. when defining in-game events. The words month, week and year can
# be used to mean whatever units of time are used in the game.

MIN = settings.TIME_SEC_PER_MIN
HOUR = MIN * settings.TIME_MIN_PER_HOUR
DAY = HOUR * settings.TIME_HOUR_PER_DAY
WEEK = DAY * settings.TIME_DAY_PER_WEEK
MONTH = WEEK * settings.TIME_WEEK_PER_MONTH
YEAR = MONTH * settings.TIME_MONTH_PER_YEAR

# Cached time stamps
SERVER_STARTTIME = time()
SERVER_RUNTIME = 0.0


class GameTime(Script):
    """
    This script repeatedly saves server times so
    it can be retrieved after server downtime.
    """
    def at_script_creation(self):
        """
        Setup the script
        """
        self.key = GAMETIME_SCRIPT_NAME
        self.desc = "Saves uptime/runtime"
        self.interval = 60
        self.persistent = True
        self.start_delay = True
        self.attributes.add("run_time", 0.0)  # OOC time
        self.attributes.add("up_time", 0.0)  # OOC time

    def at_repeat(self):
        """
        Called every minute to update the timers.
        """
        self.attributes.add("run_time", runtime())
        self.attributes.add("up_time", uptime())

    def at_start(self):
        """
        This is called once every server restart.
        We reset the up time and load the relevant
        times.
        """
        global SERVER_RUNTIME
        SERVER_RUNTIME = self.attributes.get("run_time")

def save():
    "Force save of time. This is called by server when shutting down/reloading."
    from evennia.scripts.models import ScriptDB
    try:
        script = ScriptDB.objects.get(db_key=GAMETIME_SCRIPT_NAME)
        script.at_repeat()
    except Exception:
        from evennia.utils import logger
        logger.log_trace()

def _format(seconds, *divisors) :
    """
    Helper function. Creates a tuple of even dividends given
    a range of divisors.

    Inputs
      seconds - number of seconds to format
      *divisors - a number of integer dividends. The number of seconds will be
                  integer-divided by the first number in this sequence, the remainder
                  will be divided with the second and so on.
    Output:
        A tuple of length len(*args)+1, with the last element being the last remaining
        seconds not evenly divided by the supplied dividends.

    """
    results = []
    seconds = int(seconds)
    for divisor in divisors:
        results.append(seconds / divisor)
        seconds %= divisor
    results.append(seconds)
    return tuple(results)


# Access functions

def runtime(format=False):
    "Get the total runtime of the server since first start (minus downtimes)"
    runtime = SERVER_RUNTIME + (time() - SERVER_STARTTIME)
    if format:
        return _format(runtime, 31536000, 2628000, 604800, 86400, 3600, 60)
    return runtime

def uptime(format=False):
    "Get the current uptime of the server since last reload"
    uptime = time() - SERVER_STARTTIME
    if format:
        return _format(uptime, 31536000, 2628000, 604800, 86400, 3600, 60)
    return uptime

def gametime(format=False):
    "Get the total gametime of the server since first start (minus downtimes)"
    gametime = runtime() * TIMEFACTOR
    if format:
        return _format(gametime, YEAR, MONTH, WEEK, DAY, HOUR, MIN)
    return gametime


def gametime_to_realtime(secs=0, mins=0, hrs=0, days=0,
                         weeks=0, months=0, yrs=0, format=False):
    """
    This method helps to figure out the real-world time it will take until an
    in-game time has passed. E.g. if an event should take place a month later
    in-game, you will be able to find the number of real-world seconds this
    corresponds to (hint: Interval events deal with real life seconds).

    Example:
     gametime_to_realtime(days=2) -> number of seconds in real life from
                                now after which 2 in-game days will have passed.
    """
    realtime = (secs + mins * MIN + hrs * HOUR + days * DAY + weeks * WEEK + \
                months * MONTH + yrs * YEAR) / TIMEFACTOR
    if format:
        return _format(realtime, 31536000, 2628000, 604800, 86400, 3600, 60)
    return realtime


def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0,
                         weeks=0, months=0, yrs=0, format=False):
    """
    This method calculates how much in-game time a real-world time
    interval would correspond to. This is usually a lot less interesting
    than the other way around.

     Example:
      realtime_to_gametime(days=2) -> number of game-world seconds
                                      corresponding to 2 real days.
    """
    gametime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
                             weeks * 604800 + months * 2628000 + yrs * 31536000)
    if format:
        return _format(gametime, YEAR, MONTH, WEEK, DAY, HOUR, MIN)
    return gametime


# Time administration routines

def init_gametime():
    """
    This is called once, when the server starts for the very first time.
    """
    # create the GameTime script and start it
    game_time = create_script(GameTime)
    game_time.start()
