# Copyright (C) 2012, 2015, 2016 David Maxwell
#
# This file is part of PISM.
#
# PISM 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 3 of the License, or (at your option) any later
# version.
#
# PISM 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 PISM; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

"""Implements a rudimentary logging system.  Messages are sent in client code via :func:`logError`, :func:`logMessage`,
etc.  These messages are then forwarded to any loggers that have been previously registered with :func:`add_logger`.

A logger is either a function with signature::

  def myLogger(message, verbosity)

or a class that implements::

  def __call__(self, message, verbosity)

The string message is passed as ``message`` and verbosity is a standard PISM verbosity (an integer between 1-5).
The following aliases are available

  * ``PISM.logging.kError``
  * ``PISM.logging.kWarning``
  * ``PISM.logging.kMessage``
  * ``PISM.logging.kDebug``
  * ``PISM.logging.kPrattle``

which are listed in increasing verbosity.  Note that ``kError`` need not signify an error message, only a message with
verbosity 1 that is ensured to be printed.  Conversely, ``kPrattle`` signifies a verbosity level 5 with messages that
rarely need to be displayed.

The default logger, :func:`print_logger`, simply passes the message along as a call to :cpp:func:`verbPrintf`.
See also the  :class:`CaptureLogger`, which saves logged messages into an attribute of an :file:`.nc` file.

The logging system does not log calls to verbPrintf directly.  In particular, calls to verbPrintf from within PISM's C++
code do not pass through the python-based logging system.
"""

import PISM
import time

kError = 1
kWarning = 2
kMessage = 2
kDebug = 4
kPrattle = 5

_loggers = []

def clear_loggers():
    """Removes all members from the global list of loggers."""
    global _loggers
    _loggers = []


def add_logger(logger):
    """Appends a new logger to the global list of loggers."""
    global _loggers
    _loggers.append(logger)


def log(message, verbosity):
    """Logs a message with the specified verbosity"""
    for l in _loggers:
        l(message, verbosity)


def logError(message):
    """Convenience function for logging a message at the level of ``kError``"""
    log(message, kError)


def logWarning(message):
    """Convenience function for logging a message at the level of ``kWarning``"""
    log(message, kWarning)


def logMessage(message):
    """Convenience function for logging a message at the level of ``kMessage``"""
    log(message, kMessage)


def logDebug(message):
    """Convenience function for logging a message at the level of ``kDebug``"""
    log(message, kDebug)


def logPrattle(message):
    """Convenience function for logging a message at the level of ``kPrattle``"""
    log(message, kPrattle)


def print_logger(message, verbosity):
    """Implements a logger that forwards messages to :cpp:func:`verbPrintf`."""
    com = PISM.Context().com
    msg = str(message)
    PISM.verbPrintf(verbosity, com, msg)

# The global list of loggers.
_loggers = [print_logger]


class CaptureLogger(object):

    """Implements a logger that appends log messages as they occur
    to an attribute of an :file:`.nc` file."""

    def __init__(self, filename, attribute='pism_log'):
        """:param filename: Name of :file:`.nc` file to save the log to.
        :param attribute: Attribute name to save the log as."""
        self.com = PISM.Context().com
        self.rank = PISM.Context().rank
        self.log = ""
        self.filename = filename
        self.attr = attribute

    def __call__(self, message, verbosity):
        """Saves the message to our internal log string and writes the string out to the file."""
        if self.rank == 0 and verbosity <= 2: # FIXME: fixed verbosity threshold
            timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
            self.log = "%s%s: %s" % (self.log, timestamp, message)
            d = PISM.netCDF.Dataset(self.filename, 'a')
            d.__setattr__(self.attr, self.log)
            d.close()
        self.com.barrier()

    def readOldLog(self):
        """If the :file:`.nc` file we are logging to already has a log,
        read it in to the log we are about to make so that we append to it rather
        than overwriting it."""
        if PISM.Context().rank == 0:
            d = PISM.netCDF.Dataset(self.filename, 'a')
            if self.attr in d.ncattrs():
                self.log += d.__getattr__(self.attr)
            d.close()
        self.com.barrier()

    def write(self, filename=None, attribute=None):
        """Save a copy of our log to the specified file and attribute."""
        if filename is None:
            filename = self.filename
        if attribute is None:
            attribute = self.attr
        if PISM.Context().rank == 0:
            d = PISM.netCDF.Dataset(filename, 'a')
            d.__setattr__(attribute, self.log)
            d.close()
        self.com.barrier()


import termios
import sys
import os
TERMIOS = termios


def getkey():
    """Helper function for grabbing a single key press"""
    fd = sys.stdin.fileno()
    c = None
    if os.isatty(fd):
        old = termios.tcgetattr(fd)
        new = termios.tcgetattr(fd)
        new[3] = new[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO
        new[6][TERMIOS.VMIN] = 1
        new[6][TERMIOS.VTIME] = 0
        termios.tcsetattr(fd, TERMIOS.TCSANOW, new)
        try:
            c = os.read(fd, 1)
        finally:
            termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old)
    else:
        # FIXME: The following is here for multi-processor runs.
        # Termios is not available and I don't know a better solution.
        c = sys.stdin.read(1)
    return c


def pause(message_in=None, message_out=None):
    """Prints a message and waits for a key press.

    :param message_in: Message to display before waiting.
    :param message_out: Message to display after waiting."""
    com = PISM.Context().com
    if not message_in is None:
        PISM.verbPrintf(1, com, message_in + "\n")
    _ = getkey()
    if not message_out is None:
        PISM.verbPrintf(1, com, message_out + "\n")
