# ***** BEGIN GPL LICENSE BLOCK *****
#
# 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 2
# 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, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2018, Blender Foundation - Sybren A. Stüvel
"""List dependencies of a blend file."""
import functools
import hashlib
import json
import logging
import pathlib
import sys
import time
import typing

from blender_asset_tracer import trace, bpathlib
from . import common

log = logging.getLogger(__name__)


def add_parser(subparsers):
    """Add argparser for this subcommand."""

    parser = subparsers.add_parser("list", help=__doc__)
    parser.set_defaults(func=cli_list)
    parser.add_argument("blendfile", type=pathlib.Path)
    common.add_flag(
        parser, "json", help="Output as JSON instead of human-readable text"
    )
    common.add_flag(
        parser,
        "sha256",
        help="Include SHA256sums in the output. Note that those may differ from the "
        "SHA256sums in a BAT-pack when paths are rewritten.",
    )
    common.add_flag(parser, "timing", help="Include timing information in the output")


def cli_list(args):
    bpath = args.blendfile
    if not bpath.exists():
        log.fatal("File %s does not exist", args.blendfile)
        return 3

    if args.json:
        if args.sha256:
            log.fatal("--sha256 can currently not be used in combination with --json")
        if args.timing:
            log.fatal("--timing can currently not be used in combination with --json")
        report_json(bpath)
    else:
        report_text(bpath, include_sha256=args.sha256, show_timing=args.timing)


def calc_sha_sum(filepath: pathlib.Path) -> typing.Tuple[str, float]:
    start = time.time()

    if filepath.is_dir():
        for subfile in filepath.rglob("*"):
            calc_sha_sum(subfile)
        duration = time.time() - start
        return "-multiple-", duration

    summer = hashlib.sha256()
    with filepath.open("rb") as infile:
        while True:
            block = infile.read(32 * 1024)
            if not block:
                break
            summer.update(block)

    digest = summer.hexdigest()
    duration = time.time() - start

    return digest, duration


def report_text(bpath, *, include_sha256: bool, show_timing: bool):
    reported_assets = set()  # type: typing.Set[pathlib.Path]
    last_reported_bfile = None
    shorten = functools.partial(common.shorten, pathlib.Path.cwd())

    time_spent_on_shasums = 0.0
    start_time = time.time()

    for usage in trace.deps(bpath):
        filepath = usage.block.bfile.filepath.absolute()
        if filepath != last_reported_bfile:
            if include_sha256:
                shasum, time_spent = calc_sha_sum(filepath)
                time_spent_on_shasums += time_spent
                print(shorten(filepath), shasum)
            else:
                print(shorten(filepath))

        last_reported_bfile = filepath

        for assetpath in usage.files():
            assetpath = bpathlib.make_absolute(assetpath)
            if assetpath in reported_assets:
                log.debug("Already reported %s", assetpath)
                continue

            if include_sha256:
                shasum, time_spent = calc_sha_sum(assetpath)
                time_spent_on_shasums += time_spent
                print("   ", shorten(assetpath), shasum)
            else:
                print("   ", shorten(assetpath))
            reported_assets.add(assetpath)

    if show_timing:
        duration = time.time() - start_time
        print("Spent %.2f seconds on producing this listing" % duration)
        if include_sha256:
            print("Spent %.2f seconds on calculating SHA sums" % time_spent_on_shasums)
            percentage = time_spent_on_shasums / duration * 100
            print("  (that is %d%% of the total time" % percentage)


class JSONSerialiser(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, pathlib.Path):
            return str(o)
        if isinstance(o, set):
            return sorted(o)
        return super().default(o)


def report_json(bpath):
    import collections

    # Mapping from blend file to its dependencies.
    report = collections.defaultdict(set)

    for usage in trace.deps(bpath):
        filepath = usage.block.bfile.filepath.absolute()
        for assetpath in usage.files():
            assetpath = assetpath.resolve()
            report[str(filepath)].add(assetpath)

    json.dump(report, sys.stdout, cls=JSONSerialiser, indent=4)
