#
# Copyright (C) 2020 Google, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#

from mako.template import Template
from collections import namedtuple
from enum import IntEnum
import os

TRACEPOINTS = {}

class Tracepoint(object):
    """Class that represents all the information about a tracepoint
    """
    def __init__(self, name, args=[], tp_struct=None, tp_print=None, tp_perfetto=None):
        """Parameters:

        - name: the tracepoint name, a tracepoint function with the given
          name (prefixed by 'trace_') will be generated with the specied
          args (following a u_trace ptr).  Calling this tracepoint will
          emit a trace, if tracing is enabled.
        - args: the tracepoint func args, an array of TracepointArg
        - tp_print: (optional) array of format string followed by expressions
        - tp_perfetto: (optional) driver provided callback which can generate
          perfetto events
        """
        assert isinstance(name, str)
        assert isinstance(args, list)
        assert name not in TRACEPOINTS

        self.name = name
        self.args = args
        if tp_struct is None:
           tp_struct = args
        self.tp_struct = tp_struct
        self.tp_print = tp_print
        self.tp_perfetto = tp_perfetto

        TRACEPOINTS[name] = self

class TracepointArgStruct():
    """Represents struct that is being passed as an argument
    """
    def __init__(self, type, var):
        """Parameters:

        - type: argument's C type.
        - var: name of the argument
        """
        assert isinstance(type, str)
        assert isinstance(var, str)

        self.type = type
        self.var = var

class TracepointArg(object):
    """Class that represents either an argument being passed or a field in a struct
    """
    def __init__(self, type, var, c_format, name=None, to_prim_type=None):
        """Parameters:

        - type: argument's C type.
        - var: either an argument name or a field in the struct
        - c_format: printf format to print the value.
        - name: (optional) name that will be used in intermidiate structs and will
          be displayed in output or perfetto, otherwise var will be used.
        - to_prim_type: (optional) C function to convert from arg's type to a type
          compatible with c_format.
        """
        assert isinstance(type, str)
        assert isinstance(var, str)
        assert isinstance(c_format, str)

        self.type = type
        self.var = var
        self.c_format = c_format
        if name is None:
           name = var
        self.name = name
        self.to_prim_type = to_prim_type


HEADERS = []

class HeaderScope(IntEnum):
   HEADER = (1 << 0)
   SOURCE = (1 << 1)

class Header(object):
    """Class that represents a header file dependency of generated tracepoints
    """
    def __init__(self, hdr, scope=HeaderScope.HEADER|HeaderScope.SOURCE):
        """Parameters:

        - hdr: the required header path
        """
        assert isinstance(hdr, str)
        self.hdr = hdr
        self.scope = scope

        HEADERS.append(self)


FORWARD_DECLS = []

class ForwardDecl(object):
   """Class that represents a forward declaration
   """
   def __init__(self, decl):
        assert isinstance(decl, str)
        self.decl = decl

        FORWARD_DECLS.append(self)


hdr_template = """\
/* Copyright (C) 2020 Google, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

<% guard_name = '_' + hdrname + '_H' %>
#ifndef ${guard_name}
#define ${guard_name}

% for header in HEADERS:
#include "${header.hdr}"
% endfor

#include "util/perf/u_trace.h"

#ifdef __cplusplus
extern "C" {
#endif

% for declaration in FORWARD_DECLS:
${declaration.decl};
% endfor

% for trace_name, trace in TRACEPOINTS.items():
/*
 * ${trace_name}
 */
struct trace_${trace_name} {
%    for arg in trace.tp_struct:
         ${arg.type} ${arg.name};
%    endfor
%    if len(trace.args) == 0:
#ifdef  __cplusplus
     /* avoid warnings about empty struct size mis-match in C vs C++..
      * the size mis-match is harmless because (a) nothing will deref
      * the empty struct, and (b) the code that cares about allocating
      * sizeof(struct trace_${trace_name}) (and wants this to be zero
      * if there is no payload) is C
      */
     uint8_t dummy;
#endif
%    endif
};
%    if trace.tp_perfetto is not None:
#ifdef HAVE_PERFETTO
void ${trace.tp_perfetto}(${ctx_param}, uint64_t ts_ns, const void *flush_data, const struct trace_${trace_name} *payload);
#endif
%    endif
void __trace_${trace_name}(struct u_trace *ut, void *cs
%    for arg in trace.args:
     , ${arg.type} ${arg.var}
%    endfor
);
static inline void trace_${trace_name}(struct u_trace *ut, void *cs
%    for arg in trace.args:
     , ${arg.type} ${arg.var}
%    endfor
) {
%    if trace.tp_perfetto is not None:
   if (!unlikely(ut->enabled || ut_perfetto_enabled))
%    else:
   if (!unlikely(ut->enabled))
%    endif
      return;
   __trace_${trace_name}(ut, cs
%    for arg in trace.args:
        , ${arg.var}
%    endfor
   );
}
% endfor

#ifdef __cplusplus
}
#endif

#endif /* ${guard_name} */
"""

src_template = """\
/* Copyright (C) 2020 Google, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

% for header in HEADERS:
#include "${header.hdr}"
% endfor

#include "${hdr}"

#define __NEEDS_TRACE_PRIV
#include "util/perf/u_trace_priv.h"

% for trace_name, trace in TRACEPOINTS.items():
/*
 * ${trace_name}
 */
%    if trace.args is not None and len(trace.args) > 0:
static void __print_${trace_name}(FILE *out, const void *arg) {
   const struct trace_${trace_name} *__entry =
      (const struct trace_${trace_name} *)arg;
%    if trace.tp_print is not None:
   fprintf(out, "${trace.tp_print[0]}\\n"
%       for arg in trace.tp_print[1:]:
           , ${arg}
%       endfor
% else:
   fprintf(out, ""
%  for arg in trace.tp_struct:
      "${arg.name}=${arg.c_format}, "
%  endfor
         "\\n"
%  for arg in trace.tp_struct:
   % if arg.to_prim_type:
   ,${arg.to_prim_type.format('__entry->' + arg.name)}
   % else:
   ,__entry->${arg.name}
   % endif
%  endfor
%endif
   );
}
%    else:
#define __print_${trace_name} NULL
%    endif
static const struct u_tracepoint __tp_${trace_name} = {
    ALIGN_POT(sizeof(struct trace_${trace_name}), 8),   /* keep size 64b aligned */
    "${trace_name}",
    __print_${trace_name},
%    if trace.tp_perfetto is not None:
#ifdef HAVE_PERFETTO
    (void (*)(void *pctx, uint64_t, const void *, const void *))${trace.tp_perfetto},
#endif
%    endif
};
void __trace_${trace_name}(struct u_trace *ut, void *cs
%    for arg in trace.args:
     , ${arg.type} ${arg.var}
%    endfor
) {
   struct trace_${trace_name} *__entry =
      (struct trace_${trace_name} *)u_trace_append(ut, cs, &__tp_${trace_name});
   (void)__entry;
%    for arg in trace.tp_struct:
        __entry->${arg.name} = ${arg.var};
%    endfor
}

% endfor
"""

def utrace_generate(cpath, hpath, ctx_param):
    if cpath is not None:
        hdr = os.path.basename(cpath).rsplit('.', 1)[0] + '.h'
        with open(cpath, 'w') as f:
            f.write(Template(src_template).render(
                hdr=hdr,
                ctx_param=ctx_param,
                HEADERS=[h for h in HEADERS if h.scope & HeaderScope.SOURCE],
                TRACEPOINTS=TRACEPOINTS))

    if hpath is not None:
        hdr = os.path.basename(hpath)
        with open(hpath, 'w') as f:
            f.write(Template(hdr_template).render(
                hdrname=hdr.rstrip('.h').upper(),
                ctx_param=ctx_param,
                HEADERS=[h for h in HEADERS if h.scope & HeaderScope.HEADER],
                FORWARD_DECLS=FORWARD_DECLS,
                TRACEPOINTS=TRACEPOINTS))


perfetto_utils_hdr_template = """\
/*
 * Copyright © 2021 Igalia S.L.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

<% guard_name = '_' + hdrname + '_H' %>
#ifndef ${guard_name}
#define ${guard_name}

#include <perfetto.h>

% for trace_name, trace in TRACEPOINTS.items():
static void UNUSED
trace_payload_as_extra_${trace_name}(perfetto::protos::pbzero::GpuRenderStageEvent *event,
                                     const struct trace_${trace_name} *payload)
{
%  if all([trace.tp_perfetto, trace.tp_struct]) and len(trace.tp_struct) > 0:
   char buf[128];

%  for arg in trace.tp_struct:
   {
      auto data = event->add_extra_data();
      data->set_name("${arg.name}");

%     if arg.to_prim_type:
      sprintf(buf, "${arg.c_format}", ${arg.to_prim_type.format('payload->' + arg.name)});
%     else:
      sprintf(buf, "${arg.c_format}", payload->${arg.name});
%     endif

      data->set_value(buf);
   }
%  endfor

%  endif
}
% endfor

#endif /* ${guard_name} */
"""

def utrace_generate_perfetto_utils(hpath):
    if hpath is not None:
        hdr = os.path.basename(hpath)
        with open(hpath, 'wb') as f:
            f.write(Template(perfetto_utils_hdr_template, output_encoding='utf-8').render(
                hdrname=hdr.rstrip('.h').upper(),
                TRACEPOINTS=TRACEPOINTS))
