#!/usr/bin/env python
#
#   golem - analyze feature dependencies in Linux makefiles
#
# Copyright (C) 2011-2012 Reinhard Tartler <tartler@informatik.uni-erlangen.de>
# Copyright (C) 2011-2012 Christian Dietrich <christian.dietrich@informatik.uni-erlangen.de>
# Copyright (C) 2012 Valentin Rothberg <valentinrothberg@googlemail.com>
#
# 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 logging
import sys

from optparse import OptionParser
from os.path import join, dirname

sys.path = [join(dirname(sys.path[0]), 'lib', 'python%d.%d' % \
                     (sys.version_info[0], sys.version_info[1]), 'site-packages')] \
                     + sys.path


from vamos.tools import setup_logging
from vamos.model import Model

from vamos.golem.inference import FileSymbolInferencer
from vamos.golem.kbuild import determine_buildsystem_variables, \
    file_in_current_configuration, files_for_current_configuration, \
    determine_buildsystem_variables_in_directory, \
    get_linux_version, NotALinuxTree, TreeNotConfigured
from vamos.vampyr.Configuration import LinuxPartialConfiguration
from vamos.vampyr.BuildFrameworks import KbuildBuildFramework
from vamos.vampyr.utils import ExpansionError

import os.path

def do_inference(args, arch, subarch):
    path = ""
    if len(args) > 0 and os.path.isdir(args[0]):
        path = os.path.normpath(args[0])
        logging.info("Limiting the constraints interferencing to subdirectory '%s'", path)
    modelfile = 'models/%s.model' % arch
    logging.info("loading model %s", modelfile)
    if not os.path.exists(modelfile):
        logging.error("%s not found, please generate models using undertaker-kconfigdump",
                      modelfile)
    inferencer = FileSymbolInferencer(Model(modelfile), arch=arch, subarch=subarch, subdir=path)
    inferencer.calculate()

def find_variables_in_directories(arch, subarch, args):
    modelfile = 'models/%s.model' % arch
    logging.info("loading model %s", modelfile)
    if not os.path.exists(modelfile):
        logging.error("%s not found, please generate models using undertaker-kconfigdump",
                      modelfile)

    for d in args:
        if not os.path.isdir(d):
            logging.warning("Skipping %s, not a directory", d)
            continue
        variables = determine_buildsystem_variables_in_directory(d)
        logging.info("Detected %d Kconfig variables in subdir %s", len(variables), d)
        inferencer = FileSymbolInferencer(Model(modelfile), arch=arch, subarch=subarch)
        s = inferencer.check_subdir(d, set())
        logging.debug("found %d items in check_subdir result: %s", len(s), s)
        for v in sorted(variables):
            print 'CONFIG_' + v


def main():
    # this method has too many branches and statements
    # pylint: disable=R0912
    # pylint: disable=R0915
    parser = OptionParser(usage="%prog [options]\n\n"
                          "This tool is meant to be run in a Linux source tree.\n"
                          "It is sensitive to the environment variables $ARCH and $SUBARCH.\n"
                          "Change them to scan on specific architectures.")

    parser.add_option('-v', '--verbose', dest='verbose', action='count',
                      help="increase verbosity (specify multiple times for more)")
    parser.add_option('-l', '--list', dest='do_list', action='store_true',
                      help="list all object files that would be built"\
                          +" in the current configuration")
    parser.add_option('-e', '--expand', dest='partialconfig', action='store',
                      default=None,
                      help="expand given partial configuration"\
                          +" in the current configuration")
    parser.add_option('-s', '--strategy', dest='strategy', action='store',
                      default='alldefconfig',
                      help="select how partial configurations get expanded")
    parser.add_option('-o', '--options', dest='do_opt', action='store_true', default=False,
                      help="list configuration options mentioned in Linux makefiles.")
    parser.add_option('-c', '--compiled', dest='compiled', action='append',
                      help="check if a given file is compiled"\
                      +" in the current configuration. This option can"\
                      +" be given multiple times.")
    parser.add_option('-i', '--inference', dest='inference', action='store_true',
                      help="inference makefile configurability for"\
                      +" symbols given as arguments")
    parser.add_option('-d', '--directory', dest='do_directory', action='store_true',
                      help="print variables in a subdirectory, uses '.' if none given")
    parser.add_option('-b', '--batch', dest='batch_mode', action='store_true',
                      help="operate in batch mode, read filenames from given worklists")

    (opts, args) = parser.parse_args()

    setup_logging(opts.verbose)

    (arch, subarch) = (None, None)

    if os.environ.has_key('ARCH'):
        arch = os.environ['ARCH']

    if os.environ.has_key('SUBARCH'):
        subarch = os.environ['SUBARCH']

    try:
        logging.info("using Linux version %s", get_linux_version())
    except NotALinuxTree as e:
        logging.error("Failed to determine Linux version: %s", e)
        sys.exit(1)

    if opts.do_opt:
        variables = determine_buildsystem_variables(arch)
        logging.info("Detected %d Kconfig variables in Makefiles", len(variables))
        for v in variables:
            print 'CONFIG_' + v
        sys.exit(0)

    # do_opt and do_directory work fine without ARCH being set. The other modes do not
    if not arch:
        logging.warning("Environment variable $ARCH not set, defaulting to 'x86'")
        arch = 'x86'
        subarch = 'i386'

    if opts.do_directory:
        find_variables_in_directories(arch, subarch, args)
        sys.exit(0)
    if opts.partialconfig:
        if not os.path.exists(opts.partialconfig):
            logging.error("Partial config %s does not exist", opts.partialconfig)
            sys.exit(1)
        framework_exp = KbuildBuildFramework()
        config_exp = LinuxPartialConfiguration(framework_exp,
                                               opts.partialconfig,
                                               arch, subarch,
                                               opts.strategy)
        if not os.path.exists("models/%s.model" % arch):
            logging.info("Model for arch %s absent, skipping verification", arch)
            config_exp.expand(verify=False)
        else:
            try:
                config_exp.expand(verify=True)
            except ExpansionError as e:
                logging.warning(str(e))
    if opts.do_list:
        how = opts.verbose > 0
        try:
            files = files_for_current_configuration(arch=arch, subarch=subarch, how=how)
            logging.info("Detected %d files with current configuration", len(files))
            for f in sorted(files):
                print f
        except TreeNotConfigured:
            logging.error("Your Linux tree is not configured, please configure it first")
            sys.exit(1)
        sys.exit(0)
    elif opts.compiled:
        try:
            for filename in opts.compiled:
                status = file_in_current_configuration(filename)
                print filename, status
        except TreeNotConfigured:
            logging.error("Your Linux tree is not configured, please configure it first")
            sys.exit(1)
        sys.exit(0)
    elif opts.inference:
        do_inference(args, arch=arch, subarch=subarch)
        sys.exit(0)
    elif not opts.partialconfig:
        print "No option given"
        parser.print_help()
        sys.exit(1)

if __name__ == "__main__":
    main()
