from __future__ import absolute_import, unicode_literals

import warnings

from django.template.loader import render_to_string

from debug_toolbar import settings as dt_settings
from debug_toolbar.utils import get_name_from_obj


class Panel(object):
    """
    Base class for panels.
    """
    def __init__(self, toolbar):
        self.toolbar = toolbar

    # Private panel properties

    @property
    def panel_id(self):
        return self.__class__.__name__

    @property
    def enabled(self):
        # Check to see if settings has a default value for it
        disabled_panels = dt_settings.get_config()['DISABLE_PANELS']
        panel_path = get_name_from_obj(self)
        # Some panels such as the SQLPanel and TemplatesPanel exist in a
        # panel module, but can be disabled without panel in the path.
        # For that reason, replace .panel. in the path and check for that
        # value in the disabled panels as well.
        disable_panel = (
            panel_path in disabled_panels or
            panel_path.replace('.panel.', '.') in disabled_panels)
        if disable_panel:
            default = 'off'
        else:
            default = 'on'
        # The user's cookies should override the default value
        return self.toolbar.request.COOKIES.get('djdt' + self.panel_id, default) == 'on'

    # Titles and content

    @property
    def nav_title(self):
        """
        Title shown in the side bar. Defaults to :attr:`title`.
        """
        return self.title

    @property
    def nav_subtitle(self):
        """
        Subtitle shown in the side bar. Defaults to the empty string.
        """
        return ''

    @property
    def has_content(self):
        """
        ``True`` if the panel can be displayed in full screen, ``False`` if
        it's only shown in the side bar. Defaults to ``True``.
        """
        return True

    @property
    def title(self):
        """
        Title shown in the panel when it's displayed in full screen.

        Mandatory, unless the panel sets :attr:`has_content` to ``False``.
        """
        raise NotImplementedError

    @property
    def template(self):
        """
        Template used to render :attr:`content`.

        Mandatory, unless the panel sets :attr:`has_content` to ``False`` or
        overrides `attr`:content`.
        """
        raise NotImplementedError

    @property
    def content(self):
        """
        Content of the panel when it's displayed in full screen.

        By default this renders the template defined by :attr:`template`.
        Statistics stored with :meth:`record_stats` are available in the
        template's context.
        """
        if self.has_content:
            return render_to_string(self.template, self.get_stats())

    # URLs for panel-specific views

    @classmethod
    def get_urls(cls):
        """
        Return URLpatterns, if the panel has its own views.
        """
        return []

    # Enable and disable (expensive) instrumentation, must be idempotent

    def enable_instrumentation(self):
        """
        Enable instrumentation to gather data for this panel.

        This usually means monkey-patching (!) or registering signal
        receivers. Any instrumentation with a non-negligible effect on
        performance should be installed by this method rather than at import
        time.

        Unless the toolbar or this panel is disabled, this method will be
        called early in :class:`DebugToolbarMiddleware.process_request`. It
        should be idempotent.
        """

    def disable_instrumentation(self):
        """
        Disable instrumentation to gather data for this panel.

        This is the opposite of :meth:`enable_instrumentation`.

        Unless the toolbar or this panel is disabled, this method will be
        called late in :class:`DebugToolbarMiddleware.process_response`. It
        should be idempotent.
        """

    # Store and retrieve stats (shared between panels for no good reason)

    def record_stats(self, stats):
        """
        Store data gathered by the panel. ``stats`` is a :class:`dict`.

        Each call to ``record_stats`` updates the statistics dictionary.
        """
        self.toolbar.stats.setdefault(self.panel_id, {}).update(stats)

    def get_stats(self):
        """
        Access data stored by the panel. Returns a :class:`dict`.
        """
        return self.toolbar.stats.get(self.panel_id, {})

    def record_server_timing(self, key, title, value):
        """
        Store data gathered by the panel. ``stats`` is a :class:`dict`.

        Each call to ``record_stats`` updates the statistics dictionary.
        """
        data = {key: dict(title=title, value=value)}
        self.toolbar.server_timing_stats.setdefault(self.panel_id, {}).update(data)

    def get_server_timing_stats(self):
        """
        Access data stored by the panel. Returns a :class:`dict`.
        """
        return self.toolbar.server_timing_stats.get(self.panel_id, {})

    # Standard middleware methods

    def process_request(self, request):
        """
        Like process_request in Django's middleware.

        Write panel logic related to the request there. Save data with
        :meth:`record_stats`.
        """

    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Like process_view in Django's middleware.

        Write panel logic related to the view there. Save data with
        :meth:`record_stats`.
        """

    def process_response(self, request, response):
        """
        Like process_response in Django's middleware. This is similar to
        :meth:`generate_stats <debug_toolbar.panels.Panel.generate_stats>`,
        but will be executed on every request. It should be used when either
        the logic needs to be executed on every request or it needs to change
        the response entirely, such as :class:`RedirectsPanel`.

        Write panel logic related to the response there. Post-process data
        gathered while the view executed. Save data with :meth:`record_stats`.

        Return a response to overwrite the existing response.
        """

    def generate_stats(self, request, response):
        """
        Similar to :meth:`process_response
        <debug_toolbar.panels.Panel.process_response>`,
        but may not be executed on every request. This will only be called if
        the toolbar will be inserted into the request.

        Write panel logic related to the response there. Post-process data
        gathered while the view executed. Save data with :meth:`record_stats`.

        Does not return a value.
        """

    def generate_server_timing(self, request, response):
        """
        Similar to :meth:`generate_stats
        <debug_toolbar.panels.Panel.generate_stats>`,

        Generate stats for Server Timing https://w3c.github.io/server-timing/

        Does not return a value.
        """


# Backward-compatibility for 1.0, remove in 2.0.
class DebugPanel(Panel):

    def __init__(self, *args, **kwargs):
        warnings.warn("DebugPanel was renamed to Panel.", DeprecationWarning)
        super(DebugPanel, self).__init__(*args, **kwargs)
