# -*- Mode: Python; tab-width: 4 -*-

import asynchat
import socket
import string
import time  # these three are for the rotating logger
import os  # |
import stat  # v

#
# three types of log:
# 1) file
#    with optional flushing.  Also, one that rotates the log.
# 2) socket
#    dump output directly to a socket connection. [how do we
#    keep it open?]
# 3) syslog
#    log to syslog via tcp.  this is a per-line protocol.
#

#
# The 'standard' interface to a logging object is simply
# log_object.log (message)
#

# a file-like object that captures output, and
# makes sure to flush it always...  this could
# be connected to:
#  o    stdio file
#  o    low-level file
#  o    socket channel
#  o    syslog output...


class file_logger:

    # pass this either a path or a file object.
    def __init__(self, file, flush=1, mode="a"):
        if type(file) == type(""):
            if file == "-":
                import sys

                self.file = sys.stdout
            else:
                self.file = open(file, mode)
        else:
            self.file = file
        self.do_flush = flush

    def __repr__(self):
        return "<file logger: %s>" % self.file

    def write(self, data):
        self.file.write(data)
        self.maybe_flush()

    def writeline(self, line):
        self.file.writeline(line)
        self.maybe_flush()

    def writelines(self, lines):
        self.file.writelines(lines)
        self.maybe_flush()

    def maybe_flush(self):
        if self.do_flush:
            self.file.flush()

    def flush(self):
        self.file.flush()

    def softspace(self, *args):
        pass

    def log(self, message):
        if message[-1] not in ("\r", "\n"):
            self.write(message + "\n")
        else:
            self.write(message)


# like a file_logger, but it must be attached to a filename.
# When the log gets too full, or a certain time has passed,
# it backs up the log and starts a new one.  Note that backing
# up the log is done via "mv" because anything else (cp, gzip)
# would take time, during which medusa would do nothing else.


class rotating_file_logger(file_logger):

    # If freq is non-None we back up "daily", "weekly", or "monthly".
    # Else if maxsize is non-None we back up whenever the log gets
    # to big.  If both are None we never back up.
    def __init__(self, file, freq=None, maxsize=None, flush=1, mode="a"):
        self.filename = file
        self.mode = mode
        self.file = open(file, mode)
        self.freq = freq
        self.maxsize = maxsize
        self.rotate_when = self.next_backup(self.freq)
        self.do_flush = flush

    def __repr__(self):
        return "<rotating-file logger: %s>" % self.file

    # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
    def next_backup(self, freq):
        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
        if freq == "daily":
            return time.mktime(yr, mo, day + 1, 0, 0, 0, 0, 0, -1)
        elif freq == "weekly":
            return time.mktime(yr, mo, day - wd + 7, 0, 0, 0, 0, 0, -1)  # wd(monday)==0
        elif freq == "monthly":
            return time.mktime(yr, mo + 1, 1, 0, 0, 0, 0, 0, -1)
        else:
            return None  # not a date-based backup

    def maybe_flush(self):  # rotate first if necessary
        self.maybe_rotate()
        if self.do_flush:  # from file_logger()
            self.file.flush()

    def maybe_rotate(self):
        if self.freq and time.time() > self.rotate_when:
            self.rotate()
            self.rotate_when = self.next_backup(self.freq)
        elif self.maxsize:  # rotate when we get too big
            try:
                if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
                    self.rotate()
            except os.error:  # file not found, probably
                self.rotate()  # will create a new file

    def rotate(self):
        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
        try:
            self.file.close()
            newname = "%s.ends%04d%02d%02d" % (self.filename, yr, mo, day)
            try:
                open(newname, "r").close()  # check if file exists
                newname = newname + "-%02d%02d%02d" % (hr, min, sec)
            except:  # YEARMODY is unique
                pass
            os.rename(self.filename, newname)
            self.file = open(self.filename, self.mode)
        except:
            pass


# syslog is a line-oriented log protocol - this class would be
# appropriate for FTP or HTTP logs, but not for dumping stderr to.

# TODO: a simple safety wrapper that will ensure that the line sent
# to syslog is reasonable.

# TODO: async version of syslog_client: now, log entries use blocking
# send()

import m_syslog

syslog_logger = m_syslog.syslog_client


class syslog_logger(m_syslog.syslog_client):
    def __init__(self, address, facility="user"):
        m_syslog.syslog_client.__init__(self, address)
        self.facility = m_syslog.facility_names[facility]
        self.address = address

    def __repr__(self):
        return "<syslog logger address=%s>" % (repr(self.address))

    def log(self, message):
        m_syslog.syslog_client.log(
            self, message, facility=self.facility, priority=m_syslog.LOG_INFO
        )


# log to a stream socket, asynchronously


class socket_logger(asynchat.async_chat):

    def __init__(self, address):

        if type(address) == type(""):
            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
        else:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)

        self.connect(address)
        self.address = address

    def __repr__(self):
        return "<socket logger: address=%s>" % (self.address)

    def log(self, message):
        if message[-2:] != "\r\n":
            self.socket.push(message + "\r\n")
        else:
            self.socket.push(message)


# log to multiple places
class multi_logger:
    def __init__(self, loggers):
        self.loggers = loggers

    def __repr__(self):
        return "<multi logger: %s>" % (repr(self.loggers))

    def log(self, message):
        for logger in self.loggers:
            logger.log(message)


class resolving_logger:
    """Feed (ip, message) combinations into this logger to get a
    resolved hostname in front of the message.  The message will not
    be logged until the PTR request finishes (or fails)."""

    def __init__(self, resolver, logger):
        self.resolver = resolver
        self.logger = logger

    class logger_thunk:
        def __init__(self, message, logger):
            self.message = message
            self.logger = logger

        def __call__(self, host, ttl, answer):
            if not answer:
                answer = host
            self.logger.log("%s:%s" % (answer, self.message))

    def log(self, ip, message):
        self.resolver.resolve_ptr(ip, self.logger_thunk(message, self.logger))


class unresolving_logger:
    "Just in case you don't want to resolve"

    def __init__(self, logger):
        self.logger = logger

    def log(self, ip, message):
        self.logger.log("%s:%s" % (ip, message))


def strip_eol(line):
    while line and line[-1] in "\r\n":
        line = line[:-1]
    return line


class tail_logger:
    "Keep track of the last <size> log messages"

    def __init__(self, logger, size=500):
        self.size = size
        self.logger = logger
        self.messages = []

    def log(self, message):
        self.messages.append(strip_eol(message))
        if len(self.messages) > self.size:
            del self.messages[0]
        self.logger.log(message)
