#!/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
import os
sys.path = [os.path.join(os.path.dirname(sys.path[0]), 'lib', 'python%d.%d' % \
                     (sys.version_info[0], sys.version_info[1]), 'site-packages')] \
                     + sys.path

import vamos
from vamos.tools import setup_logging, get_online_processors
from vamos.model import get_model_for_arch, parse_model

from vamos.golem.inference import Inferencer
from vamos.golem.inference_atoms import *
from vamos.golem.kbuild import determine_buildsystem_variables, \
    file_in_current_configuration, files_for_current_configuration, \
    determine_buildsystem_variables_in_directory, \
    get_linux_version, get_busybox_version, get_coreboot_version, \
    NotALinuxTree, TreeNotConfigured, \
    guess_subarch_from_arch, NotABusyboxTree, NotACorebootTree
from vamos.vampyr.BuildFrameworks import select_framework
from vamos.vampyr.utils import ExpansionError


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)

    atoms = None
    if os.path.exists("src/Modules.ia32"):
        atoms = FiascoInferenceAtoms()

    if not atoms:
        try:
            get_linux_version()
            atoms = LinuxInferenceAtoms(arch, subarch, path)
        except NotALinuxTree:
            pass

    if not atoms:
        try:
            get_busybox_version()
            atoms = BusyboxInferenceAtoms(path)
        except NotABusyboxTree:
            pass

    if not atoms:
        try:
            get_coreboot_version()
            atoms = CorebootInferenceAtoms(path)
        except NotACorebootTree:
            pass


    if not atoms:
        raise RuntimeError("No primitive operations found (not in a supported source tree?)")


    inferencer = Inferencer(atoms)
    inferencer.calculate()

def find_variables_in_directories(arch, args):
    modelfile = get_model_for_arch(arch)
    if not modelfile:
        logging.error("%s not found, please generate models using undertaker-kconfigdump",
                      modelfile)
        sys.exit(1)
    logging.info("loading model %s", modelfile)
    if len(args) is 0:
        args.append(".")
    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, arch)
        logging.info("Detected %d Kconfig variables in subdir %s", len(variables), d)
        model = parse_model(modelfile)
        s = [x for x in variables if x in model]
        s += [x + "_MODULE" for x in s if (x+"_MODULE") in model]
        logging.debug("found %d items in check_subdir result: %s", len(s), s)
        for v in sorted(variables):
            print 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 not arch:
        try:
            logging.info("detected Linux version %s", get_linux_version())

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

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

            if not arch:
                arch = vamos.default_architecture
                subarch = guess_subarch_from_arch(arch)
                logging.warning("Environment variable $ARCH not set, defaulting to '%s/%s'",
                                arch, subarch)
            if not subarch:
                subarch = guess_subarch_from_arch(arch)
        except NotALinuxTree:
            pass

    if not arch:
        try:
            logging.info("detected Busybox version %s", get_busybox_version())
            arch = 'busybox'
        except NotABusyboxTree:
            pass

    if not arch:
        try:
            logging.info("detected Coreboot version %s", get_coreboot_version())
            arch = 'coreboot'
            if os.environ.has_key('SUBARCH'):
                subarch = os.environ['SUBARCH']
        except NotACorebootTree:
            pass

    if not arch:
        logging.error("No supported software project found")
        sys.exit(1)
    if opts.inference:
        try:
            do_inference(args, arch=arch, subarch=subarch)
        except RuntimeError as e:
            logging.error("Calculating inferences failed: %s", e)
            sys.exit(1)
        sys.exit(0)
    if opts.do_opt:
        variables = determine_buildsystem_variables(arch)
        logging.info("Detected %d Kconfig variables in Makefiles", len(variables))
        for v in sorted(variables):
            print 'CONFIG_' + v
        sys.exit(0)

    if opts.do_directory:
        find_variables_in_directories(arch, 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)

        options = {
            'arch' : arch,
            'subarch': subarch,
            'threads': get_online_processors(),
            'loglevel': logging.getLogger().getEffectiveLevel(),
            'expansion_strategy': opts.strategy,
            }

        framework = select_framework(None, options)
        config_exp = framework.make_partial_configuration(opts.partialconfig)

        if not get_model_for_arch(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, arch, subarch)
                print filename, status
        except TreeNotConfigured:
            logging.error("Your Linux tree is not configured, please configure it first")
            sys.exit(1)
        sys.exit(0)
    elif not opts.partialconfig:
        print "No option given"
        parser.print_help()
        sys.exit(1)

if __name__ == "__main__":
    main()
