# Copyright © 2021 Collabora, Ltd.
# Author: Antonio Caggiano <antonio.caggiano@collabora.com>

# 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 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.

import argparse
import textwrap
import os

import xml.etree.ElementTree as et


class SourceFile:
   def __init__(self, filename):
      self.file = open(filename, 'w')
      self._indent = 0

   def write(self, *args):
      code = ' '.join(map(str,args))
      for line in code.splitlines():
         text = ''.rjust(self._indent) + line
         self.file.write(text.rstrip() + "\n")

   def indent(self, n):
      self._indent += n

   def outdent(self, n):
      self._indent -= n


class Counter:
   # category Category owning the counter
   # xml XML representation of itself
   def __init__(self, category, xml):
      self.category = category
      self.xml = xml
      self.name = self.xml.get("name")
      self.desc = self.xml.get("description")
      self.units = self.xml.get("units")
      self.offset = int(self.xml.get("offset"))
      self.underscore_name = self.xml.get("counter").lower()


class Category:
   # product Product owning the gategory
   # xml XML representation of itself
   def __init__(self, product, xml):
      self.product = product
      self.xml = xml
      self.name = self.xml.get("name")
      self.underscore_name = self.name.lower().replace(' ', '_')

      xml_counters = self.xml.findall("event")
      self.counters = []
      for xml_counter in xml_counters:
         self.counters.append(Counter(self, xml_counter))


# Wraps an entire *.xml file.
class Product:
   def __init__(self, filename):
      self.filename = filename
      self.xml = et.parse(self.filename)
      self.id = self.xml.getroot().get('id').lower()
      self.categories = []

      for xml_cat in self.xml.findall(".//category"):
         self.categories.append(Category(self, xml_cat))


def main():
   parser = argparse.ArgumentParser()
   parser.add_argument("--header", help="Header file to write", required=True)
   parser.add_argument("--code", help="C file to write", required=True)
   parser.add_argument("xml_files", nargs='+', help="List of xml metrics files to process")

   args = parser.parse_args()

   c = SourceFile(args.code)
   h = SourceFile(args.header)

   prods = []
   for xml_file in args.xml_files:
      prods.append(Product(xml_file))

   tab_size = 3

   copyright = textwrap.dedent("""\
      /* Autogenerated file, DO NOT EDIT manually! generated by {}
       *
       * Copyright © 2021 Arm Limited
       * Copyright © 2021 Collabora Ltd.
       *
       * 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.
       */

      """).format(os.path.basename(__file__))

   h.write(copyright)
   h.write(textwrap.dedent("""\
      #ifndef PAN_PERF_METRICS_H
      #define PAN_PERF_METRICS_H

      #include "perf/pan_perf.h"

      """))

   c.write(copyright)
   c.write("#include \"" + os.path.basename(args.header) + "\"")
   c.write(textwrap.dedent("""\

      #include <util/macros.h>
      """))

   for prod in prods:
      h.write("extern const struct panfrost_perf_config panfrost_perf_config_%s;\n" % prod.id)

   for prod in prods:
      c.write(textwrap.dedent("""
      static void UNUSED
      static_asserts_%s(void)
      {
      """ % prod.id))
      c.indent(tab_size)

      n_categories = len(prod.categories)
      c.write("STATIC_ASSERT(%u <= PAN_PERF_MAX_CATEGORIES);" % n_categories)
      n_counters = 0
      for category in prod.categories:
         category_counters_count = len(category.counters)
         c.write("STATIC_ASSERT(%u <= PAN_PERF_MAX_COUNTERS);" % category_counters_count)
         n_counters += category_counters_count
      
      c.outdent(tab_size)
      c.write("}\n")


      current_struct_name = "panfrost_perf_config_%s" % prod.id
      c.write("\nconst struct panfrost_perf_config %s = {" % current_struct_name)
      c.indent(tab_size)
      
      c.write(".n_categories = %u," % len(prod.categories))

      c.write(".categories = {")
      c.indent(tab_size)

      counter_id = 0

      for i in range(0, len(prod.categories)):
         category = prod.categories[i]

         c.write("{")
         c.indent(tab_size)
         c.write(".name = \"%s\"," % (category.name))
         c.write(".n_counters = %u," % (len(category.counters)))
         c.write(".counters = {")
         c.indent(tab_size)

         for j in range(0, len(category.counters)):
            counter = category.counters[j]

            assert counter_id < n_counters
            c.write("{")
            c.indent(tab_size)

            c.write(".name = \"%s\"," % (counter.name))
            c.write(".desc = \"%s\"," % (counter.desc.replace("\\", "\\\\")))
            c.write(".symbol_name = \"%s\"," % (counter.underscore_name))
            c.write(".units = PAN_PERF_COUNTER_UNITS_%s," % (counter.units.upper()))
            c.write(".offset = %u," % (counter.offset))
            c.write(".category = &%s.categories[%u]," % (current_struct_name, i))

            c.outdent(tab_size)
            c.write("}, // counter")

            counter_id += 1

         c.outdent(tab_size)
         c.write("}, // counters")

         c.outdent(tab_size)
         c.write("}, // category")

      c.outdent(tab_size)
      c.write("}, // categories")

      c.outdent(tab_size)
      c.write("}; // %s\n" % current_struct_name)

   h.write("\n#endif // PAN_PERF_METRICS_H")


if __name__ == '__main__':
   main()
