#!/usr/bin/python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2010-2012 Bryce Harrington <bryce@canonical.com>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import absolute_import, print_function, unicode_literals

import re
import os
import sys
import datetime

# Add project root directory (enable symlink, and trunk execution).
PROJECT_ROOT_DIRECTORY = os.path.abspath(
    os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))

if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'xdiagnose'))
    and PROJECT_ROOT_DIRECTORY not in sys.path):
    sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
    os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses

def loadfile(filename):
    fp = open(filename)
    text = fp.read()
    fp.close()
    return text

def parse_dpkg_dates(dpkg_text):
    dates = {}
    re_dpkg = re.compile(r'^(\d+-\d+-\d+) (\d\d:\d\d:\d\d) (\w+) (.*)$')
    for line in dpkg_text.split("\n"):
        m = re_dpkg.match(line)
        if not m:
            continue
        event_date = datetime.datetime.strptime(m.group(1), "%Y-%m-%d")
        dates[event_date] = True
    return sorted(dates)

def parse_dpkg(dpkg_text, start_date=None, end_date=None):
    re_dpkg = re.compile(r'^(\d+-\d+-\d+) (\d\d:\d\d:\d\d) (\w+) (.*)$')

    events = { }
    for line in dpkg_text.split("\n"):
        m = re_dpkg.match(line)
        if not m:
            continue

        event_date = datetime.datetime.strptime(m.group(1), "%Y-%m-%d")
        event_time = m.group(2)
        event_name = m.group(3)
        event_detail = m.group(4)

        if start_date and event_date < start_date:
            continue
        if end_date and event_date > end_date:
            continue
        if event_name not in ['install', 'upgrade', 'remove']:
            continue

        [pkg, old_ver, new_ver] = event_detail.split(' ')

        result = os.popen("apt-cache show %s 2>/dev/null | grep ^Source" % (pkg)).read().split(': ')
        if len(result) > 1:
            binary_pkg = pkg
            value = result[1].strip()
            source_pkg = value.split("\n")[0]
        else:
            source_pkg = pkg

        if source_pkg in events:
            source_event_found = False
            for source_pkg_event in events[source_pkg]:
                if source_pkg_event['new_version'] == new_ver:
                    source_event_found = True
                    source_pkg_event['binaries'].append(binary_pkg)
                    break
            if source_event_found:
                continue
        else:
            events[source_pkg] = [ ]

        # TODO: Create PackageEvent class
        source_pkg_event = {
            'action': event_name,
            'date': event_date.strftime("%Y-%m-%d"),
            'time': event_time,
            'source_package': source_pkg,
            'old_version': old_ver,
            'new_version': new_ver,
            'binaries': [ ],
            }
        events[source_pkg].append(source_pkg_event)

        #print "%-8s %-30s %-12s %-12s %-30s %-30s" %(
        #    event_name, source_pkg, source_pkg_event['date'], event_time, old_ver, new_ver)
    return events

if __name__ == "__main__":
    import gettext
    from gettext import gettext as _
    gettext.textdomain('xdiagnose')

    from xdiagnose import info
    from xdiagnose.utils.option_handler import OptionHandler
    from xdiagnose.utils import debug

    opt_hand = OptionHandler(info)
    opt_hand.add('-v', '--verbose', dest='verbose',
                 help=_('Show debug messages'),
                 action='store_true', default=False,
                 desc='Turns on verbose debugging output.')
    opt_hand.add('-D', '--show-dates', dest='show_dates',
                 help=_("List dates on which updates were performed"),
                 action='store_true', default=False,
                 desc='List dates on which updates were performed')
    opt_hand.add('-s', '--start-date', dest='start_date',
                 help=_("Only include entries from this date forward (YYYY/MM/DD)"),
                 action='store', default=None,
                 desc='Only include entries from this date forward (YYYY/MM/DD)')
    opt_hand.add('-e', '--end-date', dest='end_date',
                 help=_("Only include entries from this date and earlier (YYYY/MM/DD)"),
                 action='store', default=None,
                 desc='Only include entries from this date and earlier (YYYY/MM/DD)')
    opts, args = opt_hand.parse_args()
    debug.DEBUGGING = opts.verbose

    if len(args) < 1:
        dpkg_log_filename = '/var/log/dpkg.log'
    else:
        dpkg_log_filename = args[0]

    if opts.verbose:
        print("Reading from %s" %(dpkg_log_filename))
    dpkg_text = loadfile(dpkg_log_filename)

    if opts.show_dates:
        event_dates = parse_dpkg_dates(dpkg_text)
        for d in event_dates:
            print(d.strftime("%Y-%m-%d"))
        sys.exit(0)

    start_date = None
    if opts.start_date:
        start_date = datetime.datetime.strptime(opts.start_date, "%Y-%m-%d")
    end_date = None
    if opts.end_date:
        end_date = datetime.datetime.strptime(opts.end_date, "%Y-%m-%d")
    dpkg_events = parse_dpkg(dpkg_text, start_date, end_date)

    for pkg in sorted(dpkg_events):
        indent = ''
        for event in dpkg_events[pkg]:
            print("%s%-2s %-40s %-10s %-10s %-30s %-30s" %(
                indent,
                event['action'][0], event['source_package'],
                event['date'], event['time'],
                event['old_version'], event['new_version']))
            indent += ' '

    sys.exit(0)
