#!/usr/bin/env python

# This script has subcommands which operate against your bitbake layers, either
# displaying useful information, or acting against them.
# Currently, it only provides a show_appends command, which shows you what
# bbappends are in effect, and warns you if you have appends which are not being
# utilized.

import cmd
import logging
import os.path
import sys

bindir = os.path.dirname(__file__)
topdir = os.path.dirname(bindir)
sys.path[0:0] = [os.path.join(topdir, 'lib')]

import bb.cache
import bb.cooker
import bb.providers
import bb.utils
from bb.cooker import state


logger = logging.getLogger('BitBake')
default_cmd = 'show_appends'


def main(args):
    logging.basicConfig(format='%(levelname)s: %(message)s')
    bb.utils.clean_environment()

    cmds = Commands()
    if args:
        cmds.onecmd(' '.join(args))
    else:
        cmds.onecmd(default_cmd)
    return cmds.returncode


class Commands(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)

        self.returncode = 0
        self.config = Config(parse_only=True)
        self.cooker = bb.cooker.BBCooker(self.config,
                                         self.register_idle_function)
        self.config_data = self.cooker.configuration.data
        bb.providers.logger.setLevel(logging.ERROR)
        self.prepare_cooker()

    def register_idle_function(self, function, data):
        pass

    def prepare_cooker(self):
        sys.stderr.write("Parsing recipes..")
        logger.setLevel(logging.WARNING)

        try:
            while self.cooker.state in (state.initial, state.parsing):
                self.cooker.updateCache()
        except KeyboardInterrupt:
            self.cooker.shutdown()
            self.cooker.updateCache()
            sys.exit(2)

        logger.setLevel(logging.INFO)
        sys.stderr.write("done.\n")

        self.cooker_data = self.cooker.status
        self.cooker_data.appends = self.cooker.appendlist

    def do_show_layers(self, args):
        logger.info(str(self.config_data.getVar('BBLAYERS', True)))

    def do_show_overlayed(self, args):
        if self.cooker.overlayed:
            logger.info('Overlayed recipes:')
            for f in self.cooker.overlayed.iterkeys():
                logger.info('%s' % f)
                for of in self.cooker.overlayed[f]:
                    logger.info('  %s' % of)
        else:
            logger.info('No overlayed recipes found')

    def do_flatten(self, args):
        arglist = args.split()
        if len(arglist) != 1:
            logger.error('syntax: flatten <outputdir>')
            return

        if os.path.exists(arglist[0]) and os.listdir(arglist[0]):
            logger.error('Directory %s exists and is non-empty, please clear it out first' % arglist[0])
            return

        layers = (self.config_data.getVar('BBLAYERS', True) or "").split()
        for layer in layers:
            overlayed = []
            for f in self.cooker.overlayed.iterkeys():
                for of in self.cooker.overlayed[f]:
                    if of.startswith(layer):
                        overlayed.append(of)

            logger.info('Copying files from %s...' % layer )
            for root, dirs, files in os.walk(layer):
                for f1 in files:
                    f1full = os.sep.join([root, f1])
                    if f1full in overlayed:
                        logger.info('  Skipping overlayed file %s' % f1full )
                    else:
                        ext = os.path.splitext(f1)[1]
                        if ext != '.bbappend':
                            fdest = f1full[len(layer):]
                            fdest = os.path.normpath(os.sep.join([arglist[0],fdest]))
                            bb.utils.mkdirhier(os.path.dirname(fdest))
                            if os.path.exists(fdest):
                                if f1 == 'layer.conf' and root.endswith('/conf'):
                                    logger.info('  Skipping layer config file %s' % f1full )
                                    continue
                                else:
                                    logger.warn('Overwriting file %s', fdest)
                            bb.utils.copyfile(f1full, fdest)
                            if ext == '.bb':
                                if f1 in self.cooker_data.appends:
                                    appends = self.cooker_data.appends[f1]
                                    if appends:
                                        logger.info('  Applying appends to %s' % fdest )
                                        for appendname in appends:
                                            self.apply_append(appendname, fdest)

    def get_append_layer(self, appendname):
        for layer, _, regex, _ in self.cooker.status.bbfile_config_priorities:
            if regex.match(appendname):
                return layer
        return "?"

    def apply_append(self, appendname, recipename):
        appendfile = open(appendname, 'r')
        recipefile = open(recipename, 'a')
        recipefile.write('\n')
        recipefile.write('##### bbappended from %s #####\n' % self.get_append_layer(appendname))
        recipefile.writelines(appendfile.readlines())

    def do_show_appends(self, args):
        if not self.cooker_data.appends:
            logger.info('No append files found')
            return

        logger.info('State of append files:')

        pnlist = list(self.cooker_data.pkg_pn.keys())
        pnlist.sort()
        for pn in pnlist:
            self.show_appends_for_pn(pn)

        self.show_appends_for_skipped()

    def show_appends_for_pn(self, pn):
        filenames = self.cooker_data.pkg_pn[pn]

        best = bb.providers.findBestProvider(pn,
                                             self.cooker.configuration.data,
                                             self.cooker_data,
                                             self.cooker_data.pkg_pn)
        best_filename = os.path.basename(best[3])

        self.show_appends_output(filenames, best_filename)

    def show_appends_for_skipped(self):
        filenames = [os.path.basename(f)
                    for f in self.cooker.skiplist.iterkeys()]
        self.show_appends_output(filenames, None, " (skipped)")

    def show_appends_output(self, filenames, best_filename, name_suffix = ''):
        appended, missing = self.get_appends_for_files(filenames)
        if appended:
            for basename, appends in appended:
                logger.info('%s%s:', basename, name_suffix)
                for append in appends:
                    logger.info('  %s', append)

            if best_filename:
                if best_filename in missing:
                    logger.warn('%s: missing append for preferred version',
                                best_filename)
                    self.returncode |= 1


    def get_appends_for_files(self, filenames):
        appended, notappended = [], []
        for filename in filenames:
            _, cls = bb.cache.Cache.virtualfn2realfn(filename)
            if cls:
                continue

            basename = os.path.basename(filename)
            appends = self.cooker_data.appends.get(basename)
            if appends:
                appended.append((basename, list(appends)))
            else:
                notappended.append(basename)
        return appended, notappended

    def do_EOF(self, line):
        return True


class Config(object):
    def __init__(self, **options):
        self.pkgs_to_build = []
        self.debug_domains = []
        self.extra_assume_provided = []
        self.prefile = []
        self.postfile = []
        self.debug = 0
        self.__dict__.update(options)

    def __getattr__(self, attribute):
        try:
            return super(Config, self).__getattribute__(attribute)
        except AttributeError:
            return None


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]) or 0)
