import json
import os
from pathlib import Path

from ..stats import normalize_stats
from ..utils import commonpath
from ..utils import safe_dumps
from ..utils import short_filename


class FileStorage(object):
    def __init__(self, path, logger, default_machine_id=None):
        self.path = Path(path)
        self.default_machine_id = default_machine_id
        if not self.path.exists():
            self.path.mkdir(parents=True)
        self.path = self.path.resolve()
        self.logger = logger
        self._cache = {}

    def __str__(self):
        return str(self.path)

    @property
    def location(self):
        return str(self.path.relative_to(os.getcwd()))

    def get(self, name):
        path = self.path.joinpath(self.default_machine_id) if self.default_machine_id else self.path
        if not path.exists():
            path.mkdir(parents=True)
        return path.joinpath(name)

    @property
    def _next_num(self):
        files = self.query("[0-9][0-9][0-9][0-9]_*")
        files.sort(reverse=True)
        if not files:
            return "0001"
        for f in files:
            try:
                return "%04i" % (int(str(f.name).split('_')[0]) + 1)
            except ValueError:
                raise

    def save(self, output_json, save):
        output_file = self.get("%s_%s.json" % (self._next_num, save))
        assert not output_file.exists()
        with output_file.open('wb') as fh:
            fh.write(safe_dumps(output_json, ensure_ascii=True, indent=4).encode())
        self.logger.info("Saved benchmark data in: %s" % output_file)

    def query(self, *globs_or_files):
        files = []
        globs = []
        if not globs_or_files:
            globs_or_files = "*",

        for globish in globs_or_files:
            candidate = Path(globish)
            try:
                is_file = candidate.is_file()
            except OSError:
                is_file = False
            if is_file:
                files.append(candidate)
                continue

            parts = candidate.parts
            if len(parts) > 2:
                raise ValueError("{0!r} isn't an existing file or acceptable glob. "
                                 "Expected 'platform-glob/filename-glob' or 'filename-glob'.".format(globish))
            elif len(parts) == 2:
                platform_glob, filename_glob = parts
            else:
                platform_glob = self.default_machine_id or "*"
                filename_glob, = parts or ['']

            filename_glob = filename_glob.rstrip("*") + "*.json"
            globs.append((platform_glob, filename_glob))

        files.extend((
            file
            for platform_glob, filename_glob in globs
            for path in self.path.glob(platform_glob)
            for file in path.glob(filename_glob)
        ))
        files.extend((
            file
            for _, filename_glob in globs
            for file in self.path.glob(filename_glob)
        ))
        return sorted(files, key=lambda file: (file.name, file.parent))

    def load(self, *globs_or_files):
        if not globs_or_files:
            globs_or_files = '[0-9][0-9][0-9][0-9]_',

        for file in self.query(*globs_or_files):
            if file in self._cache:
                data = self._cache[file]
            else:
                with file.open("rU") as fh:
                    try:
                        data = json.load(fh)
                        for bench in data["benchmarks"]:
                            normalize_stats(bench["stats"])
                    except Exception as exc:
                        self.logger.warn("BENCHMARK-C5",
                                         "Failed to load {0}: {1}".format(file, exc), fslocation=self.location)
                        continue
                self._cache[file] = data
            try:
                relpath = file.relative_to(self.path)
            except ValueError:
                relpath = file
            yield relpath, data

    def load_benchmarks(self, *globs_or_files):
        sources = [
            (short_filename(path), path, data)
            for path, data in self.load(*globs_or_files)
        ]
        common = len(commonpath([src for src, _, _ in sources])) if sources else 0
        for source, path, data in sources:
            source = source[common:].lstrip(r'\/')

            for bench in data["benchmarks"]:
                bench.update(bench.pop("stats"))
                bench['path'] = str(path)
                bench['source'] = source
                yield bench
