#!/usr/bin/python

import os
import sys
import apt
import logging

from optparse import OptionParser
from tempfile import TemporaryFile

from checkbox.lib.log import set_logging
from checkbox.lib.redirect import RedirectEcho, RedirectTee
from checkbox.lib.template_i18n import TemplateI18n

from checkbox.resource import ResourceMap


DEFAULT_LOG_LEVEL = "critical"
DEFAULT_OUTPUT = "-"


def get_redirect_file(input, output):
    temp = TemporaryFile()
    tee = RedirectTee(output, temp)
    echo = RedirectEcho(input, tee)
    echo.read()

    temp.seek(0)
    return temp

def get_package_names(file):
    package_names = set()
    class PackageObject(object):

        def __init__(self, compare):
            self._compare = compare

        def __eq__(self, other):
            package_names.add(other)
            return self._compare

    # In order to read the package names from the requires field
    # in messages, it is necessary to trick eval into thinking that
    # package.name exists using the PackageResource. The problem is
    # that since we don't have access to the expression tree, an 'or'
    # operator will only evaluate the left hand side if it is true
    # and an 'and' operator will only evaluate the left hand side if
    # it is false. The solution is to compare against both zero and
    # non-zero comparison results. Caveat, this doesn't work when
    # both operators are used: foo or (bar and baz)
    resource_0 = ResourceMap()
    resource_0["package"] = [{"name": PackageObject(compare=True)}]
    resource_1 = ResourceMap()
    resource_1["package"] = [{"name": PackageObject(compare=True)}]

    template = TemplateI18n()
    messages = template.load_file(file)
    for message in messages:
        if "requires_extended" in message:
            requires = message["requires_extended"].split("\n")

        elif "requires" in message:
            requires = [message["requires"]]

        else:
            requires = []

        for require in requires:
            resource_0.eval(require)
            resource_1.eval(require)

    return list(package_names)

def install_package_names(names):
    class CapturedInstallProgress(apt.InstallProgress):

        def fork(self):
            self.stdout = TemporaryFile()
            self.stderr = TemporaryFile()
            p = os.fork()
            if p == 0:
                os.dup2(self.stdout.fileno(), sys.stdout.fileno())
                os.dup2(self.stderr.fileno(), sys.stderr.fileno())
            return p

    cache = apt.Cache()
    for name in names:
        if name in cache:
            cache[name].markInstall()

    os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
    install_progress = CapturedInstallProgress()

    try:
        cache.commit(None, install_progress)

        # Process stdout
        install_progress.stdout.seek(0)
        stdout = install_progress.stdout.read()
        install_progress.stdout.close()
        if stdout:
            logging.debug(stdout)

        # Process stderr
        install_progress.stderr.seek(0)
        stderr = install_progress.stderr.read()
        install_progress.stderr.close()
        if stderr:
            logging.error(stderr)

    except apt.cache.FetchCancelledException, e:
        return False

    except (apt.cache.LockFailedException, apt.cache.FetchFailedException), e:
        logging.warning('Package fetching failed: %s', str(e))
        raise SystemError, str(e)

    return True

def main(args):
    usage = "Usage: %prog [OPTIONS] [FILE]"
    parser = OptionParser(usage=usage)
    parser.add_option("--dry-run",
        action="store_true",
        help="do not modify system")
    parser.add_option("-l", "--log", metavar="FILE",
        help="log file where to send output")
    parser.add_option("--log-level",
        default=DEFAULT_LOG_LEVEL,
        help="one of debug, info, warning, error or critical")
    parser.add_option("-o", "--output",
        default=DEFAULT_OUTPUT,
        help="output file, - for stdout")
    (options, args) = parser.parse_args(args)

    # Set logging early
    set_logging(options.log_level, options.log)

    # Parse options
    if not options.dry_run and os.getuid():
        parser.error("Must be run as root to modify the system")

    if options.output == "-":
        output_file = sys.stdout

    else:
        try:
            output_file = open(options.output, "w")
        except IOError, e:
            parser.error("%s: %s" % (options.output, e.strerror))

    # Parse args
    if len(args) > 1:
        parser.error("Can only specify zero or one file")

    if args:
        filename = args[0]
        try:
            input_file = open(filename, "r")
        except IOError, e:
            parser.error("%s: %s" % (filename, e.strerror))

    else:
        input_file = sys.stdin

    # Get packages
    file = get_redirect_file(input_file, output_file)
    package_names = get_package_names(file)

    # Install packages
    if not options.dry_run:
        if not install_package_names(package_names):
            parser.error("Failed to fetch packages")

    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
