#!/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',
    )
METAVAR = 'VALUE'
USAGE = """
  logscan -h
  logscan [OPTIONS] [LOGFILE]"""
DESCRIPTION ="""
------------------------------------------------------------------------
description:
  logscan is a command-line-tool to get time-specific access to logfiles.

  It can handle rotated and gzipped logfiles or reads from stdin. The log
  is automatically checked for different timecodes.
------------------------------------------------------------------------"""
EPILOG = """
------------------------------------------------------------------------
The calculation of a period works on basis of times and durations:
  no times; no duration:        from start to eof
  one time; no duration:        from time to eof
  two times; no duration:       from time-one to time-two
  no times; positive duration:  form start to end of duration
  no times; negative duration:  from begin of duration to eof
  one time; positive duration:  from time to end of duration
  one time; negative duration:  from begin of duration to time
------------------------------------------------------------------------

To specify different time-formats logscan should automatically check the
logfiles for use the file logscan.conf (probably installed under
/usr/local/etc/).
"""
OPTIONS = {
    '-i' : 'print the number of logfiles, the start- and the end-time',
    '-d' : 'Print the log of a specified day.',
    '-c' : 'use TIMECODE to parse the date ("%%Y-%%m-%%d %%H:%%M:%%S")',
    '-g' : 'Only lines where PATTERN was found will be printed.',
    '-t' : """The first VALUE as date, second as daytime. The Date could be omitted. In that case logscan takes the date of the last log-entry.
The generic format for the daytime is HHMMSS. You could also use an arbitrary seperator: 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.""",
    '-W' : """Specifies a duration of weeks, days, hours, minutes and seconds for the given VALUEs in this order. Starting with weeks. Following VALUEs are optional. To specify a 'negative' duration put a '-' in front of all VALUEs.
Durations can be speciefied with all upper-case-options. All given durations are taken together, so that '-D 3 0 25' is the same as '-D 3 -M 25'.""",
    '-D' : 'duration with first VALUE as days.',
    '-H' : 'duration with first VALUE as hours.',
    '-M' : 'duration with first VALUE as minutes.',
    '-S' : 'duration with first VALUE as seconds.',
    }


#TODO: arg for not-rotating
class Logscan:
    def __init__(self):
        self.parser = argparse.ArgumentParser(
            prog='logscan',
            usage=USAGE,
            description=DESCRIPTION,
            epilog=EPILOG,
            formatter_class=argparse.RawDescriptionHelpFormatter,
            )
        self.parser.add_argument(
            'logfile',
            type=argparse.FileType('rb', 1),
            nargs='?',
            default='-',
            metavar='LOGFILE',
            help="If LOGFILE is missing or '-' stdin is read instead."
            )
        self.parser.add_argument(
            '-i',
            '--info',
            action='store_true',
            default=False,
            help=OPTIONS['-i'],
            )
        self.parser.add_argument(
            '-c',
            '--timecode',
            type=str,
            metavar='TIMECODE',
            help=OPTIONS['-c']
            )
        self.parser.add_argument(
            '-g',
            '--grep',
            type=str,
            metavar='PATTERN',
            help=OPTIONS['-g']
            )
        self.parser.add_argument(
            '-d',
            '--date',
            action=ParseDate,
            metavar=METAVAR,
            help=OPTIONS['-d']
            )
        self.parser.add_argument(
            '-t',
            '--time',
            action=AppendDateTimeOrTime,
            nargs='+',
            metavar=METAVAR,
            help=OPTIONS['-t']
            )
        self.parser.add_argument(
            '-W',
            '--weeks',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            metavar=METAVAR,
            help=OPTIONS['-W']
            )
        self.parser.add_argument(
            '-D',
            '--days',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            metavar=METAVAR,
            help=OPTIONS['-D']
            )
        self.parser.add_argument(
            '-H',
            '--hours',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            metavar=METAVAR,
            help=OPTIONS['-H']
            )
        self.parser.add_argument(
            '-M',
            '--minutes',
            action=ParseTimeDelta,
            nargs='+',
            default=timedelta(),
            metavar=METAVAR,
            help=OPTIONS['-M']
            )
        self.parser.add_argument(
            '-S',
            '--seconds',
            action=ParseTimeDelta,
            default=timedelta(),
            metavar=METAVAR,
            help=OPTIONS['-S']
            )

    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()
        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()



