# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import pandas as pd
import io
import contextlib
import asyncio
import signal
from jinja2 import Template
from contextlib import ExitStack
import tempfile

from . import command_line


class TraceFile:
    """A class to represent and query a recorded trace file."""

    def __init__(self, trace_file):
        """Initializes the TraceFile object with the path to the trace file.

        Args:
            trace_file: The path to the recorded trace file.
        """
        self.trace_file = trace_file

    async def query(self, query):
        """Queries the trace file using the trace_processor tool.

        Args:
            query: The SQL query string to execute on the trace.

        Returns:
            The result of the query as a pandas dataframe.
        """
        result = await command_line.run(
            "third_party/perfetto/tools/trace_processor",
            self.trace_file,
            "-Q",
            query,
        )
        # Use io.StringIO to treat the string as a file and read it with pandas.
        return pd.read_csv(io.StringIO(result.stdout), na_values=['[NULL]'])

    @contextlib.asynccontextmanager
    async def record(self, config_file):
        """Records a trace from the configured device using the specified
        config.

        Args:
            config_file: The config file to use for recording.
        """
        recording_task = asyncio.create_task(
            command_line.run(
                "third_party/perfetto/tools/record_android_trace",
                "-o",
                self.trace_file,
                "-c",
                config_file,
                "-n",
                interruption_signal=signal.SIGINT,
            )
        )
        if recording_task.done():
            Exception(f"Recording failed: {recording_task.result()}")

        try:
            yield
        finally:
            recording_task.cancel()
            try:
                await recording_task
            except asyncio.CancelledError:
                pass


@contextlib.contextmanager
def histograms_trace_config(histograms, duration=10000):
    """
    Create a temporary trace configuration file for recording the histogram(s)
    listed in `histograms`.

    Args:
        histograms: A string or a list of strings with the name of the
                    histogram(s) to record.
        duration: The duration of the trace.

    Returns:
        The path to the temporary trace configuration file.
    """
    if isinstance(histograms, str):
        histograms = [histograms]
    elif not isinstance(histograms, list):
        raise TypeError("histograms must be a string or a list of strings")

    # This assumes the current working directory is the chromium src root.
    jinja_file = os.path.join(
        os.getcwd(),
        "tools",
        "android",
        "colabutils",
        "res",
        "histogram_trace_cfg.pbtxt.j2",
    )
    with open(jinja_file, 'r') as f:
        template_content = f.read()
    template = Template(template_content)
    rendered_config = template.render(
        histogram_names=histograms, duration=duration
    )

    with tempfile.NamedTemporaryFile(mode='w') as temporary_config_file:
        temporary_config_file.write(rendered_config)
        temporary_config_file.flush()
        yield temporary_config_file.name


def histogram_values_query(histogram):
    """
    Create a perfetto trace query to obtain all recorded values of a histogram.

    Args:
        histogram: The name of the histogram to query.

    Returns:
        A string containing the query.
    """
    jinja_file = os.path.join(
        os.getcwd(),
        "tools",
        "android",
        "colabutils",
        "res",
        "histogram_query.j2",
    )
    with open(jinja_file, 'r') as f:
        template_content = f.read()
    template = Template(template_content)
    rendered_config = template.render(histogram_name=histogram)
    return rendered_config
