#!/usr/bin/python

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os
import sys
import re
import argparse
from datetime import datetime
from datetime import time
from datetime import timedelta

from timeparse import AppendDateTimeOrTime
from timeparse import ParseDateTimeOrTime
from timeparse import ParseTimeDelta
from timeparse import ParseDate
from logscanlib import add_timecodes
from logscanlib import RotatedLogs
from logscanlib import TimeCodeError


FILES = (
    'logscan.conf',
    '/usr/local/etc/logscan.conf',
    '/usr/etc/logscan.conf',
    )

USAGE = """usage: 
  logscan -h
  logscan [OPTIONS] [LOGFILE]
"""

HELP = """usage: 
  logscan -h
  logscan [OPTIONS] [LOGFILE]

description:
  Get time-specific access to logfiles.

  logscan is able to handle rotated and/or gzipped logfiles or to read stdin.
  It supports several formats for time-information in the log, and checks the
  logfiles automatically for them. You can also specify further formats in
  logscan.conf.


positional argument:
  LOGFILE                   If LOGFILE is missing or '-' stdin is read instead.

optional arguments:
  -h, --help                print this help message and exit
  -i, --info                print the number of files, the start-time and end-time
  -c, --timecode TIMECODE   use TIMECODE to parse the log-times (e.g. "%Y-%m-%d %H:%M:%S")
  -g, --grep PATTERN        print only lines where PATTERN was found
  -d, --date DATE           print all log-entries of DATE

argument for times:
  -t, --time [DATE] TIME    specify a point of time

argruments for durations:
  -W, --weeks WEEKS [DAYS [HOURS [MINUTES [SECONDS]]]]
  -D, --days DAYS [HOURS [MINUTES [SECONDS]]]
  -H, --hours HOURS [MINUTES [SECONDS]]
  -M, --minutes MINUTES [SECONDS]
  -S, --seconds SECONDS


times (DATE and TIME):
  If Date is omitted the date of the last log-entry will be taken. The generic
  format for the daytime is HHMMSS. You could also use an arbitrary seperator
  (e.g. HH:MM:SS). Doing so you don't need two- digit-values. Seconds or seconds
  and minutes are not obliging. 0322 will be 03:22:00h or just 3 will be
  03:00:00h. The generic format for the date is DDMM[YY]YY. As well you can use
  a seperator and one- digit-values. Also the year or the year and the month
  could be missing. In that case the date will be completed by the actual year
  and month. Use this option twice to specify two points of time.


durations:
  durations are specified with all upper-case-letter-options (-W, -D, -H,-M, -S),
  while all specifications are taken together, so that '-D 3 0 25' is the same
  as '-D 3 -M 25'. To specify a retrograde duration use a minus for all values
  (e.g. -D -4 -3 -2).


Which period to print is defined as follows:
  no times and no duration:         from start to eof
  one time and no duration:         from time to eof
  two times and no duration:        from time-one to time-two
  no times and positive duration:   form start to end of duration
  no times and negative duration:   from begin of duration to eof
  one time and positive duration:   from time to end of duration
  one time and negative duration:   from begin of duration to time


time-code-formats:
  logscan checks a log-file automatically for these formats:
  %Y-%m-%d %H:%M:%S
  %b %d %X %Y
  %b %d %X
  Also a timestamp as total amount of seconds since epoche (1970-01-01 00:00:00 UTC)
  is supported. logscan expects for it a ten-digit decimal with three decimal-places.
  To specify further format-codes use logscan.conf. logscan uses either the first
  format that fits or an explicitly with --timecode given format.
  A list of all available format-codes can be recieved from 'date --help'.


Please report bugs at https://github.com/thomst/logscan/issues"""

#TODO: arg for not-rotating
class Logscan:
    def __init__(self):
        self.parser = argparse.ArgumentParser(
            prog='logscan',
            usage=USAGE,
            conflict_handler='resolve',
            )
        self.parser.add_argument(
            '-h',
            '--help',
            action='store_true',
            default=False,
            )
        self.parser.add_argument(
            'logfile',
            type=argparse.FileType('rb', 1),
            nargs='?',
            default='-',
            )
        self.parser.add_argument(
            '-i',
            '--info',
            action='store_true',
            default=False,
            )
        self.parser.add_argument(
            '-c',
            '--timecode',
            type=str,
            )
        self.parser.add_argument(
            '-g',
            '--grep',
            type=str,
            )
        self.parser.add_argument(
            '-d',
            '--date',
            action=ParseDate,
            )
        self.parser.add_argument(
            '-t',
            '--time',
            action=AppendDateTimeOrTime,
            nargs='+',
            )
        self.parser.add_argument(
            '-W',
            '--weeks',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            )
        self.parser.add_argument(
            '-D',
            '--days',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            )
        self.parser.add_argument(
            '-H',
            '--hours',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            )
        self.parser.add_argument(
            '-M',
            '--minutes',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            )
        self.parser.add_argument(
            '-S',
            '--seconds',
            action=ParseTimeDelta,
            default=timedelta(),
            )

    def get_timecodes(self):
        timecodes = list()
        parse = lambda f: [l.rstrip('\n') for l in f if not re.match('[#\n ]+', l)]
        for file in [f for f in FILES if os.path.isfile(f)]:
            with open(file) as f: timecodes += parse(f)
        return timecodes

    def prepare_times(self):
        for t in self.args.time:
            if isinstance(t, time):
                index = self.args.time.index(t)
                self.args.time[index] = datetime.combine(self.log.end.date(), t)

    def run(self):
        self.args = self.parser.parse_args()
        if self.args.help: print HELP
        else: self.process_log()

    def process_log(self):
        self.log = RotatedLogs(self.args.logfile, self.args.timecode)
        self.args.weeks += self.args.days + self.args.hours + self.args.minutes + self.args.seconds

        if not self.args.timecode: add_timecodes(self.get_timecodes())
        if self.args.info: self.info()
        elif self.args.date: self.date()
        elif self.args.time and self.args.weeks: self.time_duration()
        elif self.args.time: self.time()
        elif self.args.weeks: self.duration()
        else: self.print_section()

    def info(self):
        print os.path.basename(self.log.name) + ':'
        print '  {0} files'.format(self.log.quantity)
        print '  start: {0}'.format(self.log.start.strftime('%d.%m.%Y %H:%M:%S'))
        print '  end: {0}'.format(self.log.end.strftime('%d.%m.%Y %H:%M:%S'))

    def date(self):
        start = datetime.combine(self.args.date, time())
        end = datetime.combine(self.args.date + timedelta(days=1), time())
        self.print_section(start, end)

    def time_duration(self):
        self.prepare_times()
        if self.args.weeks > timedelta(0):
            start = self.args.time[0]
            end = self.args.time[0] + self.args.weeks
        else:
            start = self.args.time[0] + self.args.weeks
            end = self.args.time[0]
        self.print_section(start, end)

    def time(self):
        self.prepare_times()
        if len(self.args.time) is 1:
            start = self.args.time[0]
            end = None
        else:
            start = self.args.time[0]
            end = self.args.time[1]
        self.print_section(start, end)

    def duration(self):
        if self.args.weeks > timedelta(0):
            start = None
            end = self.log.start + self.args.weeks
        else:
            start = self.log.end + self.args.weeks
            end = None
        self.print_section(start, end)

    def print_section(self, start=None, end=None):
        lines = self.log.get_section(start, end)
        if lines:
            for line in lines:
                if self.args.grep and not self.args.grep in line: continue
                print line,
        else:
            print 'no entries between {0} and {1}'.format(
                start or 'log-start',
                end or 'log-end'
                )


if __name__ == "__main__":
    logscan = Logscan()
    try:
        logscan.run()
    except (KeyboardInterrupt, TimeCodeError) as err:
        print err
    except IOError:
        pass
    finally:
        if hasattr(logscan, 'log'): logscan.log.close()



