# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import errno
import json
import os
import sys
from pathlib import Path

from mozfile import which

from mozperftest.layers import Layer
from mozperftest.utils import run_script, silence

METRICS_FIELDS = (
    "SpeedIndex",
    "FirstVisualChange",
    "LastVisualChange",
    "VisualProgress",
    "videoRecordingStart",
)


class VisualData:
    def open_data(self, data):
        res = {
            "name": "visualmetrics",
            "subtest": data["name"],
            "data": [
                {"file": "visualmetrics", "value": value, "xaxis": xaxis}
                for xaxis, value in enumerate(data["values"])
            ],
        }
        return res

    def transform(self, data):
        return data

    def merge(self, data):
        return data


class VisualMetrics(Layer):
    """Wrapper around Browsertime's visualmetrics.py script"""

    name = "visualmetrics"
    activated = False
    arguments = {}

    def setup(self):
        self.metrics = {}
        self.metrics_fields = []

        # making sure we have ffmpeg and imagemagick available
        for tool in ("ffmpeg", "convert"):
            if sys.platform in ("win32", "msys"):
                tool += ".exe"
            path = which(tool)
            if not path:
                raise OSError(errno.ENOENT, f"Could not find {tool}")

    def run(self, metadata):
        if "VISUALMETRICS_PY" not in os.environ:
            raise OSError(
                "The VISUALMETRICS_PY environment variable is not set."
                "Make sure you run the browsertime layer"
            )
        path = Path(os.environ["VISUALMETRICS_PY"])
        if not path.exists():
            raise FileNotFoundError(str(path))

        self.visualmetrics = path
        treated = 0

        for result in metadata.get_results():
            result_dir = result.get("results")
            if result_dir is None:
                continue
            result_dir = Path(result_dir)
            if not result_dir.is_dir():
                continue
            browsertime_json = Path(result_dir, "browsertime.json")
            if not browsertime_json.exists():
                continue
            treated += self.run_visual_metrics(browsertime_json)

        self.info(f"Treated {treated} videos.")

        if len(self.metrics) > 0:
            metadata.add_result({
                "name": metadata.script["name"] + "-vm",
                "framework": {"name": "mozperftest"},
                "transformer": "mozperftest.metrics.visualmetrics:VisualData",
                "results": list(self.metrics.values()),
            })

            # we also extend --perfherder-metrics and --console-metrics if they
            # are activated
            def add_to_option(name):
                existing = self.get_arg(name, [])
                for field in self.metrics_fields:
                    existing.append({"name": field, "unit": "ms"})
                self.env.set_arg(name, existing)

            if self.get_arg("perfherder"):
                add_to_option("perfherder-metrics")

            if self.get_arg("console"):
                add_to_option("console-metrics")

        else:
            self.warning("No video was treated.")
        return metadata

    def run_visual_metrics(self, browsertime_json):
        verbose = self.get_arg("verbose")
        self.info(f"Looking at {browsertime_json}")
        venv = self.mach_cmd.virtualenv_manager

        class _display:
            def __enter__(self, *args, **kw):
                return self

            __exit__ = __enter__

        may_silence = not verbose and silence or _display

        with browsertime_json.open() as f:
            browsertime_json_data = json.loads(f.read())

        videos = 0
        global_options = [
            str(self.visualmetrics),
            "--orange",
            "--perceptual",
            "--contentful",
            "--force",
            "--renderignore",
            "5",
            "--viewport",
        ]
        if verbose:
            global_options += ["-vvv"]

        for site in browsertime_json_data:
            # collecting metrics from browserScripts
            # because it can be used in splitting
            for index, bs in enumerate(site["browserScripts"]):
                for name, val in bs.items():
                    if not isinstance(val, (str, int)):
                        continue
                    self.append_metrics(index, name, val)

            extra = {"lowerIsBetter": True, "unit": "ms"}

            for index, video in enumerate(site["files"]["video"]):
                videos += 1
                video_path = browsertime_json.parent / video
                output = "[]"
                with may_silence():
                    res, output = run_script(
                        venv.python_path,
                        global_options + ["--video", str(video_path), "--json"],
                        verbose=verbose,
                        label="visual metrics",
                        display=False,
                    )
                    if not res:
                        self.error(f"Failed {res}")
                        continue

                output = output.strip()
                if verbose:
                    self.info(str(output))
                try:
                    output = json.loads(output)
                except json.JSONDecodeError:
                    self.error("Could not read the json output from visualmetrics.py")
                    continue

                for name, value in output.items():
                    if name.endswith(
                        "Progress",
                    ):
                        self._expand_visual_progress(index, name, value, **extra)
                    else:
                        self.append_metrics(index, name, value, **extra)

        return videos

    def _expand_visual_progress(self, index, name, value, **fields):
        def _split_percent(val):
            # value is of the form "567=94%"
            val = val.split("=")
            value, percent = val[0].strip(), val[1].strip()
            if percent.endswith("%"):
                percent = percent[:-1]
            return int(percent), int(value)

        percents = [_split_percent(elmt) for elmt in value.split(",")]

        # we want to keep the first added value for each percent
        # so the trick here is to create a dict() with the reversed list
        percents = dict(reversed(percents))

        # we are keeping the last 5 percents
        percents = list(percents.items())
        percents.sort()
        for percent, progress_value in percents[:5]:
            self.append_metrics(index, f"{name}{percent}", progress_value, **fields)

    def append_metrics(self, index, name, value, **fields):
        if name not in self.metrics_fields:
            self.metrics_fields.append(name)
        if name not in self.metrics:
            self.metrics[name] = {"name": name, "values": []}

        self.metrics[name]["values"].append(value)
        self.metrics[name].update(**fields)
