#!/usr/bin/env python3

# package-tracker - Compare and track package versions in debian repositories
# Copyright (C) 2017 Andreas Kreuzer <andreas.kreuzer@open-infrastructure.net>
#
# This program 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.
#
# 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, see <http://www.gnu.org/licenses/>.

import sys
import os
import os.path
import argparse
import apt
import apt_pkg
import re
import logging
import time

software = u"package-tracker"
program = sys.argv[0]
install_path = "/"
apt_root_path = "var/lib/" + software
config_path = "etc/" + software
log_file = "var/log/" + software + "/" + software + ".log"
template_path = "usr/share/" + software + "/templates"

# --------------------------------
#  Argument handling with argparse
#
def check_args_source(check_arg):
    """
    Check validity of sources file arguments.

    This is the handler for argparse to verfiy the argument passed to the
    program.

    Parameters
    ----------
    check_arg : string
        Given argument from comand line

    Returns
    -------
    string
        Full path to file or given argument if invalid

    """

    file_path = os.path.join(config_path, check_arg)
    if (os.path.exists(os.path.join(install_path, file_path))):
        return file_path
    else:
        msg = file_path + " does not exist"
        raise argparse.ArgumentTypeError(msg)

    return check_arg

parser = argparse.ArgumentParser(
    description="Compare and track package versions in debian repositories"
)
parser.add_argument(
    "READ",
    help="apt sources file to read",
    type=check_args_source
)
parser.add_argument(
    "COMPARE",
    help="apt sources file to compare against",
    type=check_args_source
)
parser.add_argument(
    "-t", "--filter-type",
    help="displays only given action (default: %(default)s)",
    choices=["all", "lower", "obsolete", "ok"],
    default="all"
)
parser.add_argument(
    "-o", "--output-type",
    help="output format type (default: %(default)s)",
    choices=["stdout", "html"],
    default="stdout"
)
parser.add_argument(
    "-f", "--file-path",
    help="file path to output report",
    default="report.html"
)
parser.add_argument(
    "-i", "--ignore-tag",
    help="remove given tag from the end of version string"
)
parser.add_argument(
    "-v", "--verbose",
    help="print summary informations",
    action="store_true"
)
parser.add_argument(
    "-d", "--debug",
    help="log debug information",
    action="store_true"
)
args = parser.parse_args()


# --------------------------------
# Function definitions
#
def exit_critical(message):
    logging.error(message)
    print("Exiting: ", message)
    sys.exit(1)

def bit_isset(number, mask):
    """ Testing a number against a binary mask """
    return ((number & mask) == mask)

def relink_sources_file(filename):
    """
    Creates a symlink of the desired file to sources.list

    Parameters
    ----------
    filename : string
        relative path to sources file
    """
    global apt_root_path, config_path, install_path

    symlink_dir = os.path.join(
                        install_path,
                        apt_root_path,
                        "etc/apt/sources.list"
                  )

    if (os.path.exists(symlink_dir)):
        os.remove(symlink_dir)

    os.symlink(
        os.path.join(install_path, filename),
        symlink_dir
    )

PT_VERSION_COMPARE_ERROR = 0
PT_VERSION_EQUAL = 1
PT_VERSION_LOWER = 2
PT_VERSION_HIGHER = 4
PT_VERSION_IGNORED_TAG = 8
PT_VERSION_TAG_NORMAL = 16
PT_VERSION_TAG_LOWER = 32
PT_VERSION_TAG_HIGHER = 64
PT_VERSION_OBSOLETE = 128
PT_VERSION_BINNMU = 256

def compare_debug(compare_result):
    """ Create a human readable output of the compare result """
    global PT_VERSION_COMPARE_ERROR, PT_VERSION_EQUAL, PT_VERSION_LOWER
    global PT_VERSION_HIGHER, PT_VERSION_IGNORED_TAG
    global PT_VERSION_TAG_NORMAL, PT_VERSION_TAG_LOWER
    global PT_VERSION_TAG_HIGHER

    log_string = "Compare result:"
    if(compare_result == PT_VERSION_COMPARE_ERROR):
        logging.debug(log_string + "PT_VERSION_COMPARE_ERROR");
        return
    if((compare_result & PT_VERSION_EQUAL)
            == PT_VERSION_EQUAL):
        log_string += " PT_VERSION_EQUAL"
    if((compare_result & PT_VERSION_LOWER)
            == PT_VERSION_LOWER):
        log_string += " PT_VERSION_LOWER"
    if((compare_result & PT_VERSION_HIGHER)
            == PT_VERSION_HIGHER):
        log_string += " PT_VERSION_HIGHER"
    if((compare_result & PT_VERSION_IGNORED_TAG)
            == PT_VERSION_IGNORED_TAG):
        log_string += " PT_VERSION_IGNORED_TAG"
    if((compare_result & PT_VERSION_TAG_NORMAL)
            == PT_VERSION_TAG_NORMAL):
        log_string += " PT_VERSION_TAG_NORMAL"
    if((compare_result & PT_VERSION_TAG_LOWER)
            == PT_VERSION_TAG_LOWER):
        log_string += " PT_VERSION_TAG_LOWER"
    if((compare_result & PT_VERSION_TAG_HIGHER)
            == PT_VERSION_TAG_HIGHER):
        log_string += " PT_VERSION_TAG_HIGHER"
    if((compare_result & PT_VERSION_OBSOLETE)
            == PT_VERSION_OBSOLETE):
        log_string += " PT_VERSION_OBSOLETE"
    if((compare_result & PT_VERSION_BINNMU)
            == PT_VERSION_BINNMU):
        log_string += " PT_VERSION_BINNMU"

    logging.debug(log_string)

update_counter = 0
equal_counter = 0
obsolete_counter = 0
def compare_versions(from_version, to_version):
    """
    Compares two given versions with apt_pkg module

    Parameters
    ----------
    from_version : string
        Version string to compare from
    to_version : string
        Version string to compare against

    Returns
    -------
    A binary representation of the compare result. Used to match against binary
    masks.
    """
    global update_counter, equal_counter
    compare_result = 0

    if (args.ignore_tag != None):
        version_re = re.search(
                r'(?P<debrev>-[0-9]+)?(?P<tag>(?P<tagmod>[~+]?)' + args.ignore_tag + r'(?P<tagrev>[0-9]+))$',
                from_version
        )
        if (version_re):
            # We have found a tag from a debian derrivative
            compare_result |= PT_VERSION_IGNORED_TAG

            if (version_re.group('tagmod') == '~'):
                compare_result |= PT_VERSION_TAG_LOWER
            elif (version_re.group('tagmod') == '+'):
                compare_result |= PT_VERSION_TAG_HIGHER
            else:
                compare_result |= PT_VERSION_TAG_NORMAL

            # Trim the version string
            trimpos = version_re.start('tagmod')
            if ((version_re.group('debrev') == '-0')
                    and bit_isset(compare_result, PT_VERSION_TAG_HIGHER)):
                trimpos = version_re.start(0)
            version = from_version[:trimpos]
    else:
        version = from_version

    # Search for a binNMU package version
    if(re.search(r'\+b[0-9]+$', to_version)):
        compare_result |= PT_VERSION_BINNMU

    result = apt_pkg.version_compare(version, to_version)
    if (result > 0):
        compare_result |= PT_VERSION_HIGHER
        equal_counter += 1
    elif (result < 0):
        compare_result |= PT_VERSION_LOWER
        update_counter += 1
    else:
        compare_result |= PT_VERSION_EQUAL
        equal_counter += 1

    return compare_result

def print_package_message(package, compare_result):
    """
    Print information for each version differences

    Parameters
    ----------
    package : string
        Package name (without architecture)
    compare_result : binary
        Result of compared versions from compare_versions()
    """
    global query_cache
    global compare_cache
    global args

    tmp = None

    if ((args.filter_type == "lower") or (args.filter_type == "all")
            and bit_isset(compare_result, PT_VERSION_LOWER)):
        tmp = package + " is lower in version"
        if (bit_isset(compare_result, PT_VERSION_BINNMU)):
            tmp += " [NMU]"
        tmp += "\n"

    elif ((args.filter_type == "ok" or args.filter_type == "all")
            and (bit_isset(compare_result, PT_VERSION_EQUAL)
                or bit_isset(compare_result, PT_VERSION_HIGHER))
            ):
        tmp = package + "\n"

    elif ((args.filter_type == "obsolete") or (args.filter_type == "all")
            and bit_isset(compare_result, PT_VERSION_OBSOLETE)):
        tmp = package + " is obsolete\n"

    if (tmp):
        tmp += "\tparent version: " + compare_cache[package].versions[0].source_version + "\n"
        tmp += "\tcurrent version: " + query_cache[package].versions[0].source_version
        print()
        print(tmp)


# --------------------------------
# Templated output
#
template_rawdata = ''
template_data = {}
template_data['main'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['security'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['updates'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['extras'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['backports'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }
template_data['backports-extras'] = { 'rawspan': '', 'rawelement': '', 'elements': '', 'counter': 0 }

def load_template():
    """ Initiates templated output and loads the template """
    global args
    global install_path, template_path
    global template_rawdata, template_data

    #FIXME: using command parameter to switch to another template
    file_path = os.path.join(install_path, template_path)
    file_name = ''
    if (args.output_type == "html"):
        file_name = "report.html.in"
    f = open(file_path + "/" + file_name, 'r')
    if (not f):
        exit_critical("Cannot read html template: ", file_path + "/" + file_name)
    template_rawdata = f.read()
    f.close()

    m = re.search(r'{%PT_CONTENT_SPAN_MAIN%}(.+?){%/PT_CONTENT_SPAN_MAIN%}',
            template_rawdata, re.DOTALL)
    #FIXME: checks for other template content place holder below
    if (not m):
        exit_critical("Template error: PT_CONTENT_SPAN_MAIN not found.")
    template_data['main']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_MAIN%}(.+?){%/PT_CONTENT_ELEMENTS_MAIN%}',
            template_rawdata, re.DOTALL)
    template_data['main']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_SECURITY%}(.+?){%/PT_CONTENT_SPAN_SECURITY%}',
            template_rawdata, re.DOTALL)
    template_data['security']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_SECURITY%}(.+?){%/PT_CONTENT_ELEMENTS_SECURITY%}',
            template_rawdata, re.DOTALL)
    template_data['security']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_UPDATES%}(.+?){%/PT_CONTENT_SPAN_UPDATES%}',
            template_rawdata, re.DOTALL)
    template_data['updates']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_UPDATES%}(.+?){%/PT_CONTENT_ELEMENTS_UPDATES%}',
            template_rawdata, re.DOTALL)
    template_data['updates']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_EXTRAS%}(.+?){%/PT_CONTENT_SPAN_EXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['extras']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_EXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_EXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['extras']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_BACKPORTS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTS%}',
            template_rawdata, re.DOTALL)
    template_data['backports']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_BACKPORTS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTS%}',
            template_rawdata, re.DOTALL)
    template_data['backports']['rawelement'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_SPAN_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTSEXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['backports-extras']['rawspan'] = m.group(1)

    m = re.search(r'{%PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}',
            template_rawdata, re.DOTALL)
    template_data['backports-extras']['rawelement'] = m.group(1)

#FIXME: Parameters for all attributes to output into the table. As such
#       lookup via cache will not be nessary and templating engine can
#       be moved to a module.
def add_template_element(suite, package, compare_result):
    """
    Build result data for template engine

    Parameters
    ----------
    suite : string
        Archive suite name
    package : string
        Package name (without architecture)
    compare_result : binary
        Result of compared versions from compare_versions()
    """
    global template_data

    action = ""
    if ((bit_isset(compare_result, PT_VERSION_EQUAL)
        or bit_isset(compare_result, PT_VERSION_HIGHER))
            and (args.filter_type == "ok" or args.filter_type == "all")):
        action = "ok"
    elif (bit_isset(compare_result, PT_VERSION_LOWER)
            and (args.filter_type == "lower" or args.filter_type == "all")):
        action = "lower"
        if (bit_isset(compare_result, PT_VERSION_BINNMU)):
            action += " [NMU]"
    elif (bit_isset(compare_result, PT_VERSION_OBSOLETE)
            and (args.filter_type == "obsolete" or args.filter_type == "all")):
        action = "obsolete"
    else:
        return

    template_data[suite]['counter'] += 1

    tmp = template_data[suite]['rawelement'].replace('{%PT_ITEM_INDEX/%}',
            str(template_data[suite]['counter']))
    tmp = tmp.replace('{%PT_SOURCE_PACKAGE/%}',
            query_cache[package].versions[0].source_name)
    tmp = tmp.replace('{%PT_PARENT_VERSION/%}',
            compare_cache[package].versions[0].source_version)
    tmp = tmp.replace('{%PT_CURRENT_VERSION/%}',
            query_cache[package].versions[0].source_version)
    tmp = tmp.replace('{%PT_STATUS/%}',
            action)

    template_data[suite]['elements'] += tmp

def output_template():
    """ Outputs a file with the generated results """
    global args
    global template_rawdata, template_data
    global install_path, config_path


    #FIXME: usage of apt functions to query archives in use
    f = open(os.path.join(install_path, args.READ), 'r')
    configured_archives_query = f.read()
    f.close()
    configured_archives_query = re.sub(r"^\W?#.+?$\n?", "",
                        configured_archives_query, flags=re.MULTILINE)
    configured_archives_query = re.sub(r"^\W?$\n?", "",
                        configured_archives_query, flags=re.MULTILINE)

    f = open(os.path.join(install_path, args.COMPARE), 'r')
    configured_archives_compare = f.read()
    f.close()
    configured_archives_compare = re.sub(r"^\W?#.+?$\n?", "",
                        configured_archives_compare, flags=re.MULTILINE)
    configured_archives_compare = re.sub(r"^\W?$\n?", "",
                        configured_archives_compare, flags=re.MULTILINE)

    title = "Package-Tracker Report"
    intro = "<h1>" + title + "</h1>\n"
    intro += "<p>This Report was generated at: " + time.ctime() + "</p>\n"
    intro += "<p>"
    if ((args.filter_type == "lower") or (args.filter_type == "all")):
        intro += "Total of source packages wich are outdated: "
        intro += str(update_counter) + "<br/>\n"
    if ((args.filter_type == "obsolete") or (args.filter_type == "all")):
        intro += "Total of source packages wich are obsolete: "
        intro += str(obsolete_counter) + "<br/>\n"
    if ((args.filter_type == "ok") or (args.filter_type == "all")):
        intro += "Total of source packages with no action requested: "
        intro += str(equal_counter) + "<br/>\n"
    intro += "</p>"

    summary = "Archives were read from:\n<pre>\n"
    summary += configured_archives_query + "</pre>\n"
    summary += "Archives wich have been compared against:\n<pre>\n"
    summary += configured_archives_compare + "</pre>\n"

    if (template_data['main']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_MAIN%}(.+?){%/PT_CONTENT_SPAN_MAIN%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_MAIN%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_MAIN%}(.+?){%/PT_CONTENT_ELEMENTS_MAIN%}'
    template_rawdata = re.sub(re_element, template_data['main']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['security']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_SECURITY%}(.+?){%/PT_CONTENT_SPAN_SECURITY%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_SECURITY%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_SECURITY%}(.+?){%/PT_CONTENT_ELEMENTS_SECURITY%}'
    template_rawdata = re.sub(re_element, template_data['security']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['updates']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_UPDATES%}(.+?){%/PT_CONTENT_SPAN_UPDATES%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_UPDATES%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_UPDATES%}(.+?){%/PT_CONTENT_ELEMENTS_UPDATES%}'
    template_rawdata = re.sub(re_element, template_data['updates']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['extras']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_EXTRAS%}(.+?){%/PT_CONTENT_SPAN_EXTRAS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_EXTRAS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_EXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_EXTRAS%}'
    template_rawdata = re.sub(re_element, template_data['extras']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['backports']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_BACKPORTS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_BACKPORTS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_BACKPORTS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTS%}'
    template_rawdata = re.sub(re_element, template_data['backports']['elements'],
                            template_rawdata, flags=re.DOTALL)

    if (template_data['backports-extras']['counter'] == 0):
        re_element = r'{%PT_CONTENT_SPAN_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_SPAN_BACKPORTSEXTRAS%}'
    else:
        template_rawdata = re.sub(r'{%[/]*PT_CONTENT_SPAN_BACKPORTSEXTRAS%}', '',
                                template_rawdata, flags=re.DOTALL)
        re_element = r'{%PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}(.+?){%/PT_CONTENT_ELEMENTS_BACKPORTSEXTRAS%}'
    template_rawdata = re.sub(re_element, template_data['backports-extras']['elements'],
                            template_rawdata, flags=re.DOTALL)

    #FIXME: Cleanup of unmatched tags from template


    # replace single items
    template_rawdata = template_rawdata.replace('{%PT_TITLE/%}', title)
    template_rawdata = template_rawdata.replace('{%PT_INTRO/%}', intro)
    template_rawdata = template_rawdata.replace('{%PT_SUMMARY/%}', summary)

    file_name = args.file_path
    f = open(file_name, "w")
    f.write(template_rawdata)
    f.close()

# --------------------------------
# Apt cache packages processing
#
compare_data = {}
query_data = {}
suites = [
            'main',
            'security',
            'updates',
            'extras',
            'backports',
            'backports-extras'
        ]

# FIXME: debug
def debug_data():
    for source_package, binary_packages in compare_data.items():
        print("source: ", source_package)
        for binary_package in binary_packages:
            print("  binary: ", binary_package)

def process_packages():
    """ Read all packages in compare cache to detect source/binary packages """
    global query_data
    global compare_data

    for package in compare_cache:
        archive = package.versions[0].origins[0].archive
        if (not archive in compare_data):
            compare_data[archive] = {}
        if (not package.versions[0].source_name in compare_data[archive]):
            compare_data[archive][package.versions[0].source_name] = []

        compare_data[archive][package.versions[0].source_name].append(package.shortname)

    for package in query_cache:
        archive = package.versions[0].origins[0].archive
        if (not archive in query_data):
            query_data[archive] = {}
        if (not package.versions[0].source_name in query_data[archive]):
            query_data[archive][package.versions[0].source_name] = []

        query_data[archive][package.versions[0].source_name].append(package.shortname)

def process_compared_data():
    """
    Iterate over all source packages and process
    the compare result data from process_packages()
    """
    global equal_conter, update_counter, obsolete_counter

    # check if we have to compare against
    # or from a testing archive
    compare_archive = "stable"
    if (not compare_archive in compare_data):
        compare_archive = "testing"
    if (not compare_archive in compare_data):
        logging.error("There is no testing or stable suite in parent distribution.")
        sys.exit(1)
    query_archive = "stable"
    if (not query_archive in query_data):
        query_archive = "testing"
    if (not query_archive in query_data):
        logging.error("There is no testing or stable suite in current distribution.")
        sys.exit(1)

    for suite in suites:
        if (suite != 'main'):
            if (suite == 'backports' or suite == 'backports-extras'):
                compare_suite = 'unstable'
            else:
                compare_suite = compare_archive + "-" + suite
            if (not compare_suite in compare_data):
                logging.info("skipping check for '" + suite + "'. It does not exist in parent archives.")
                continue
            query_suite = query_archive + "-" + suite
            if (not query_suite in query_data):
                logging.info("skipping check for '" + suite + "'. It does not exist in current archives.")
                continue
        else:
            compare_suite = compare_archive
            query_suite = query_archive

        #FIXME: stdout output
        if (args.output_type == "stdout"):
            print()
            print("comparing '" + query_suite + "' against '" + compare_suite + "' suite")

        for source_package in compare_data[compare_suite]:
            if (source_package in query_data[query_suite]):
                for binary_package in compare_data[compare_suite][source_package]:
                    if (binary_package in query_data[query_suite]):
                        result = compare_versions(
                                    query_cache[binary_package].versions[0].version,
                                    compare_cache[binary_package].versions[0].version)

                        # FIXME: testing
                        # Check if compare_result has errors
                        if (result == PT_VERSION_COMPARE_ERROR):
                            logging.error(
                                    "compare_result for " + binary_package.shortname + " has not succeeded."
                        )
                        if (args.debug):
                            logging.debug(
                                "source package: "
                                    + query_cache[source_package].shortname
                                    + " "
                                    + query_cache[source_package].versions[0].source_version
                                    + " (current) "
                                    + compare_cache[source_package].versions[0].source_version
                                    + " (parent)"
                            )
                            logging.debug(
                                "binary package: "
                                    + query_cache[binary_package].shortname
                            )
                            logging.debug(
                                "current binary version: "
                                    + query_cache[binary_package].versions[0].version
                            )
                            logging.debug(
                                "parent binary version: "
                                    + compare_cache[binary_package].versions[0].version
                            )
                            compare_debug(result)

                        if (args.output_type == "stdout"):
                            print_package_message(source_package, result)
                        else:
                            add_template_element(suite, source_package, result)

    #Check for extras and backports-extras source packages
    #wich have a package in parent archive
    for suite in [ 'extras', 'backports-extras' ]:
        #FIXME: stdout output
        if (args.output_type == "stdout"):
            print()
            print("obsolete check for '" + query_archive + "-" + suite + "'")

        if query_archive + "-" + suite in query_data:
            for source_package in query_data[query_archive + "-" + suite]:
                if (source_package in compare_cache):
                    obsolete_counter += 1

                    if (args.debug):
                        logging.debug(
                                "obsolete-check: source package: "
                               + query_cache[source_package].shortname
                               + " "
                               + query_cache[source_package].versions[0].version
                               + " (current) is obsolete"
                        )
                        logging.debug(
                                "obsolete-check: parent source version: "
                                + compare_cache[source_package].versions[0].source_version
                        )

                    if (args.output_type == "stdout"):
                        print_package_message(source_package, PT_VERSION_OBSOLETE)
                    else:
                        add_template_element(suite, source_package, PT_VERSION_OBSOLETE)


# --------------------------------
# Main:
#
def main():
    global query_cache
    global compare_cache
    global install_path
    global apt_root_path
    global args

    # Configuring logging
    if (args.verbose == True):
        loglevel=logging.DEBUG
    else:
        loglevel=logging.INFO

    logging.basicConfig(
            filename=os.path.join(install_path, log_file),
            level=loglevel,
            filemode='w',
            format="%(asctime)s: "+software+": %(levelname)s: %(message)s"
    )

    if (args.verbose == True):
        print("Reading apt archives to compare from..")

    relink_sources_file(args.READ)

    query_cache = apt.Cache(rootdir=os.path.join(install_path, apt_root_path))
    query_cache.update(None)
    query_cache.open()

    if (args.verbose == True):
        print("Reading apt archives to compare against..")

    relink_sources_file(args.COMPARE)

    compare_cache = apt.Cache(rootdir=os.path.join(install_path, apt_root_path))
    compare_cache.update(None)
    compare_cache.open()

    if (args.verbose == True):
        print()
        print(
            "Number of packages in archives to compare from: ",
            len(query_cache)
        )
        print(
            "Number of packages to compare against: ",
            len(compare_cache)
        )
        print()
        print("Comparing packages..")

    process_packages()

    if (args.verbose == True):
        print(
            "Source packages to compare: ",
            len(compare_data)
        )

    if (args.output_type == "html"):
        load_template()

    process_compared_data()

    if (args.output_type == "html"):
        output_template()

    if (args.verbose == True):
        print()
        print(
                "Total of source packages to update: ",
                update_counter
        )
        print(
                "Total of source packages wich are obsolete: ",
                obsolete_counter
        )
        print(
                "Total of source packages with no action required: ",
                equal_counter
        )


if (__name__ == "__main__"):
    main()

sys.exit(0)
