# -*- coding: utf-8 -*-

# Bob build tool
# Copyright (C) 2016  TechniSat Digital GmbH
#
# SPDX-License-Identifier: GPL-3.0-or-later

from ..input import RecipeSet
from ..errors import BuildError
from ..utils import processDefines
import argparse
import codecs
import sys

try:
    # test if stdout can handle box drawing characters
    codecs.encode("└├│─", sys.stdout.encoding)
    LS_SEP_1 = u"└── "
    LS_SEP_2 = u"├── "
    LS_SEP_3 = u"    "
    LS_SEP_4 = u"│   "
except UnicodeEncodeError:
    # fall back to ASCII
    LS_SEP_1 = "\\-- "
    LS_SEP_2 = "|-- "
    LS_SEP_3 = "    "
    LS_SEP_4 = "|   "


class PackagePrinter:
    def __init__(self, showAll, showOrigin, recurse, unsorted):
        self.showAll = showAll
        self.showOrigin = showOrigin
        self.recurse = recurse
        if unsorted:
            self.sort = lambda x: x
        else:
            self.sort = sorted

    def __getChilds(self, package):
        return [
            (name, child.node, " ({})".format(child.origin) if (self.showOrigin and child.origin) else "")
            for (name, child) in self.sort(package.items())
            if (self.showAll or child.direct)
        ]

    def showTree(self, package, prefix=""):
        i = 0
        packages = self.__getChilds(package)
        for (n, p, o) in packages:
            last = (i >= len(packages)-1)
            print("{}{}{}{}".format(prefix, LS_SEP_1 if last else LS_SEP_2, n, o))
            self.showTree(p, prefix + (LS_SEP_3 if last else LS_SEP_4))
            i += 1

    def showPrefixed(self, package, showAliases, stack=[], level=0):
        for p in showAliases: print(p)
        for (n, p, o) in self.__getChilds(package):
            newStack = stack[:]
            newStack.append(n)
            print("{}{}".format("/".join(newStack), o))
            if self.recurse:
                self.showPrefixed(p, [], newStack, level+1)


def doLS(argv, bobRoot):
    parser = argparse.ArgumentParser(prog="bob ls", description='List packages.')
    parser.add_argument('package', type=str, nargs='?', default="",
                        help="Sub-package to start listing from")
    parser.add_argument('-a', '--all', default=False, action='store_true',
                        help="Show indirect dependencies too")
    parser.add_argument('-o', '--origin', default=False, action='store_true',
                        help="Show origin of indirect dependencies")
    parser.add_argument('-r', '--recursive', default=False, action='store_true',
                        help="Recursively display dependencies")
    parser.add_argument('-u', '--unsorted', default=False, action='store_true',
                        help="Show packages in recipe order (unsorted)")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-p', '--prefixed', default=False, action='store_true',
                       help="Prints the full path prefix for each package")
    group.add_argument('-d', '--direct', default=False, action='store_true',
                       help="List packages themselves, not their contents")
    parser.add_argument('-D', default=[], action='append', dest="defines",
        help="Override default environment variable")
    parser.add_argument('-c', dest="configFile", default=[], action='append',
        help="Use config File")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('--sandbox', action='store_true', default=False,
        help="Enable sandboxing")
    group.add_argument('--no-sandbox', action='store_false', dest='sandbox',
        help="Disable sandboxing")
    args = parser.parse_args(argv)

    defines = processDefines(args.defines)

    recipes = RecipeSet()
    recipes.setConfigFiles(args.configFile)
    recipes.parse()

    packages = recipes.generatePackages(lambda s,m: "unused", defines, args.sandbox)
    showAliases = packages.getAliases() if args.package == "" else []

    printer = PackagePrinter(args.all, args.origin, args.recursive, args.unsorted)
    for (stack, root) in packages.queryTreePath(args.package):
        if args.prefixed:
            printer.showPrefixed(root, showAliases, stack)
        elif args.direct:
            print("/".join(stack) if stack else "/")
        elif args.recursive:
            print("/".join(stack) if stack else "/")
            printer.showTree(root)
        else:
            printer.showPrefixed(root, showAliases)

class Default(dict):
    def __init__(self, default, *args, **kwargs):
        self.__default = default
        super().__init__(*args, **kwargs)

    def __missing__(self, key):
        return self.__default

def doQueryMeta(argv, bobRoot):
    parser = argparse.ArgumentParser(prog="bob query-meta",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            description="""Query meta information of packages.""")
    parser.add_argument('packages', nargs='+', help="(Sub-)packages to query")
    parser.add_argument('-D', default=[], action='append', dest="defines",
        help="Override default environment variable")
    parser.add_argument('-c', dest="configFile", default=[], action='append',
        help="Use config File")
    parser.add_argument('-r', '--recursive', default=False, action='store_true',
                        help="Recursively display dependencies")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('--sandbox', action='store_true', default=False,
        help="Enable sandboxing")
    group.add_argument('--no-sandbox', action='store_false', dest='sandbox',
        help="Disable sandboxing")
    args = parser.parse_args(argv)

    defines = processDefines(args.defines)

    recipes = RecipeSet()
    recipes.setConfigFiles(args.configFile)
    recipes.parse()
    packages = recipes.generatePackages(lambda s,m: "unused", defines, args.sandbox)

    def showPackage(package, recurse, done):
        # Show each package only once. Meta variables are fixed and not variant
        # dependent.
        key = package.getName()
        if key not in done:
            for (var, val) in package.getMetaEnv().items():
                print(package.getName() + " " + var + "=" + val)
            done.add(key)

        # recurse package tree if requested
        if recurse:
            for ps in package.getDirectDepSteps():
                showPackage(ps.getPackage(), recurse, done)

    done = set()
    for p in args.packages:
        for package in packages.queryPackagePath(p):
            showPackage(package, args.recursive, done)

def doQuerySCM(argv, bobRoot):
    parser = argparse.ArgumentParser(prog="bob query-scm",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="""Query SCM configuration of packages.

By default this command will print one line for each SCM in the given package.
The output format may be overridded by '-f'. By default the following formats
are used:

 * git="git {package} {dir} {url} {branch}"
 * svn="svn {package} {dir} {url} {revision}"
 * cvs="cvs {package} {dir} {cvsroot} {module}"
 * url="url {package} {dir}/{fileName} {url}"
""")
    parser.add_argument('packages', nargs='+', help="(Sub-)packages to query")

    parser.add_argument('-D', default=[], action='append', dest="defines",
        help="Override default environment variable")
    parser.add_argument('-c', dest="configFile", default=[], action='append',
        help="Use config File")
    parser.add_argument('-f', default=[], action='append', dest="formats",
        help="Output format for scm (syntax: scm=format). Can be specified multiple times.")
    parser.add_argument('--default', default="", help='Default for missing attributes (default: "")')
    parser.add_argument('-r', '--recursive', default=False, action='store_true',
                        help="Recursively display dependencies")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('--sandbox', action='store_true', default=False,
        help="Enable sandboxing")
    group.add_argument('--no-sandbox', action='store_false', dest='sandbox',
        help="Disable sandboxing")

    formats = {
        'git' : "git {package} {dir} {url} {branch}",
        'svn' : "svn {package} {dir} {url} {revision}",
        'cvs' : "cvs {package} {dir} {cvsroot} {module}",
        'url' : "url {package} {dir}/{fileName} {url}",
    }

    args = parser.parse_args(argv)

    defines = processDefines(args.defines)

    recipes = RecipeSet()
    recipes.setConfigFiles(args.configFile)
    recipes.parse()
    packages = recipes.generatePackages(lambda s,m: "unused", defines, args.sandbox)

    # update formats
    for fmt in args.formats:
        f = fmt.split("=")
        if len(f) != 2: parser.error("Malformed format: "+fmt)
        formats[f[0]] = f[1]

    def showPackage(package, recurse, done, donePackages):
        if package._getId() in donePackages:
            return
        donePackages.add(package._getId())

        # show recipes only once for each checkout variant
        key = (package.getRecipe().getName(), package.getCheckoutStep().getVariantId())
        if key not in done:
            for scm in package.getCheckoutStep().getScmList():
                p = { k:v for (k,v) in scm.getProperties().items() if v is not None }
                p['package'] = "/".join(package.getStack())
                fmt = formats.get(p['scm'], "{scm} {dir}")
                print(fmt.format_map(Default(args.default, p)))
            done.add(key)

        # recurse package tree if requested
        if recurse:
            for ps in package.getDirectDepSteps():
                showPackage(ps.getPackage(), recurse, done, donePackages)

    done = set()
    donePackages = set()
    for p in args.packages:
        for package in packages.queryPackagePath(p):
            showPackage(package, args.recursive, done, donePackages)

def doQueryRecipe(argv, bobRoot):
    parser = argparse.ArgumentParser(prog="bob query-recipe",
        description="Query recipe and class files of package.")
    parser.add_argument('package', help="(Sub-)package to query")
    parser.add_argument('-D', default=[], action='append', dest="defines",
        help="Override default environment variable")
    parser.add_argument('-c', dest="configFile", default=[], action='append',
        help="Use config File")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('--sandbox', action='store_true', default=False,
        help="Enable sandboxing")
    group.add_argument('--no-sandbox', action='store_false', dest='sandbox',
        help="Disable sandboxing")

    args = parser.parse_args(argv)

    defines = processDefines(args.defines)

    recipes = RecipeSet()
    recipes.setConfigFiles(args.configFile)
    recipes.parse()
    package = recipes.generatePackages(lambda s,m: "unused", defines, args.sandbox).walkPackagePath(args.package)

    for fn in package.getRecipe().getSources():
        print(fn)
