# Copyright 2011 Google Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Timestamp handling routines."""

__author__ = "jaq@google.com (Jamie Wilkinson)"

import logging
import os.path
import tempfile
import time
import stat
import datetime


def ReadTimestamp(filename):
    """Return a timestamp from a file.

    The timestamp file format is a single line, containing a string in the
    ISO-8601 format YYYY-MM-DDThh:mm:ssZ (i.e. UTC time).  We do not support
    all ISO-8601 formats for reasons of convenience in the code.

    Timestamps internal to nss_cache deliberately do not carry milliseconds.

    Args:
      filename:  A String naming the file to read from.

    Returns:
      A time.struct_time, or None if the timestamp file doesn't
      exist or has errors.
    """
    if not os.path.exists(filename):
        return None

    try:
        timestamp_file = open(filename, "r")
        timestamp_string = timestamp_file.read().strip()
    except IOError as e:
        logging.warning("error opening timestamp file: %s", e)
        timestamp_string = None
    else:
        timestamp_file.close()

    logging.debug("read timestamp %s from file %r", timestamp_string, filename)

    if timestamp_string is not None:
        try:
            # Append UTC to force the timezone to parse the string in.
            timestamp = time.strptime(
                timestamp_string + " UTC", "%Y-%m-%dT%H:%M:%SZ %Z"
            )
        except ValueError as e:
            logging.error("cannot parse timestamp file %r: %s", filename, e)
            timestamp = None
    else:
        timestamp = None

    logging.debug("Timestamp is: %r", timestamp)
    now = time.gmtime()
    logging.debug("      Now is: %r", now)
    if timestamp > now:
        logging.warning(
            "timestamp %r (%r) from %r is in the future, now is %r",
            timestamp_string,
            time.mktime(timestamp),
            filename,
            time.mktime(now),
        )
        if time.mktime(timestamp) - time.mktime(now) >= 60 * 60:
            logging.info("Resetting timestamp to now.")
            timestamp = now

    return timestamp


def WriteTimestamp(timestamp, filename):
    """Write a given timestamp out to a file, converting to the ISO-8601
    format.

    We convert internal timestamp format (epoch) to ISO-8601 format, i.e.
    YYYY-MM-DDThh:mm:ssZ which is basically UTC time, then write it out to a
    file.

    Args:
      timestamp: A struct time.struct_time or time tuple.
      filename: A String naming the file to write to.

    Returns:
       A boolean indicating success of write.
    """
    # TODO(jaq): hack
    if timestamp is None:
        return True

    timestamp_dir = os.path.dirname(filename)

    (filedesc, temp_filename) = tempfile.mkstemp(
        prefix="nsscache-update-", dir=timestamp_dir
    )

    time_string = time.strftime("%Y-%m-%dT%H:%M:%SZ", timestamp)

    try:
        os.write(filedesc, b"%s\n" % time_string.encode())
        os.fsync(filedesc)
        os.close(filedesc)
    except OSError:
        os.unlink(temp_filename)
        logging.warning("writing timestamp failed!")
        return False

    os.chmod(temp_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
    os.rename(temp_filename, filename)
    logging.debug("wrote timestamp %s to file %r", time_string, filename)
    return True


def FromTimestampToDateTime(ts):
    """Converts internal nss_cache timestamp to datetime object.

    Args:
      ts: number of seconds since epoch
    Returns:
      datetime object
    """
    return datetime.datetime.utcfromtimestamp(ts)


def FromDateTimeToTimestamp(datetime_obj):
    """Converts datetime object to internal nss_cache timestamp.

    Args:
      datetime object
    Returns:
      number of seconds since epoch
    """
    dt = datetime_obj.replace(tzinfo=None)
    return int((dt - datetime.datetime(1970, 1, 1)).total_seconds())
