text: # -*- coding: utf-8 -*-
  #
  # QAPI code generation
  #
  # Copyright (c) 2015-2019 Red Hat Inc.
  #
  # Authors:
  #  Markus Armbruster <armbru@redhat.com>
  #  Marc-André Lureau <marcandre.lureau@redhat.com>
  #
  # This work is licensed under the terms of the GNU GPL, version 2.
  # See the COPYING file in the top-level directory.
  
  from contextlib import contextmanager
  import os
  import re
  from typing import (
      Dict,
      Iterator,
      Optional,
      Sequence,
      Tuple,
  )
  
  from .common import (
      c_fname,
      c_name,
      guardend,
      guardstart,
      mcgen,
  )
  from .schema import (
      QAPISchemaFeature,
      QAPISchemaIfCond,
      QAPISchemaModule,
      QAPISchemaObjectType,
      QAPISchemaVisitor,
  )
  from .source import QAPISourceInfo
  
  
  def gen_special_features(features: Sequence[QAPISchemaFeature]) -> str:
      special_features = [f"1u << QAPI_{feat.name.upper()}"
                          for feat in features if feat.is_special()]
      return ' | '.join(special_features) or '0'
  
  
  class QAPIGen:
      def __init__(self, fname: str):
          self.fname = fname
          self._preamble = ''
          self._body = ''
  
      def preamble_add(self, text: str) -> None:
          self._preamble += text
  
      def add(self, text: str) -> None:
          self._body += text
  
      def get_content(self) -> str:
          return self._top() + self._preamble + self._body + self._bottom()
  
      def _top(self) -> str:
          # pylint: disable=no-self-use
          return ''
  
      def _bottom(self) -> str:
          # pylint: disable=no-self-use
          return ''
  
      def write(self, output_dir: str) -> None:
          # Include paths starting with ../ are used to reuse modules of the main
          # schema in specialised schemas. Don't overwrite the files that are
          # already generated for the main schema.
          if self.fname.startswith('../'):
              return
          pathname = os.path.join(output_dir, self.fname)
          odir = os.path.dirname(pathname)
  
          if odir:
              os.makedirs(odir, exist_ok=True)
  
          # use os.open for O_CREAT to create and read a non-existant file
          fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
          with os.fdopen(fd, 'r+', encoding='utf-8') as fp:
              text = self.get_content()
              oldtext = fp.read(len(text) + 1)
              if text != oldtext:
                  fp.seek(0)
                  fp.truncate(0)
                  fp.write(text)
  
  
  def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
      if before == after:
          return after   # suppress empty #if ... #endif
  
      assert after.startswith(before)
      out = before
      added = after[len(before):]
      if added[0] == '\n':
          out += '\n'
          added = added[1:]
      out += ifcond.gen_if()
      out += added
      out += ifcond.gen_endif()
      return out
  
  
  def build_params(arg_type: Optional[QAPISchemaObjectType],
                   boxed: bool,
                   extra: Optional[str] = None) -> str:
      ret = ''
      sep = ''
      if boxed:
          assert arg_type
          ret += '%s arg' % arg_type.c_param_type()
          sep = ', '
      elif arg_type:
          assert not arg_type.variants
          for memb in arg_type.members:
              ret += sep
              sep = ', '
              if memb.optional:
                  ret += 'bool has_%s, ' % c_name(memb.name)
              ret += '%s %s' % (memb.type.c_param_type(),
                                c_name(memb.name))
      if extra:
          ret += sep + extra
      return ret if ret else 'void'
  
  
  class QAPIGenCCode(QAPIGen):
      def __init__(self, fname: str):
          super().__init__(fname)
          self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
  
      def start_if(self, ifcond: QAPISchemaIfCond) -> None:
          assert self._start_if is None
          self._start_if = (ifcond, self._body, self._preamble)
  
      def end_if(self) -> None:
          assert self._start_if is not None
          self._body = _wrap_ifcond(self._start_if[0],
                                    self._start_if[1], self._body)
          self._preamble = _wrap_ifcond(self._start_if[0],
                                        self._start_if[2], self._preamble)
          self._start_if = None
  
      def get_content(self) -> str:
          assert self._start_if is None
          return super().get_content()
  
  
  class QAPIGenC(QAPIGenCCode):
      def __init__(self, fname: str, blurb: str, pydoc: str):
          super().__init__(fname)
          self._blurb = blurb
          self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
                                                    re.MULTILINE))
  
      def _top(self) -> str:
          return mcgen('''
  /* AUTOMATICALLY GENERATED, DO NOT MODIFY */
  
  /*
  %(blurb)s
   *
   * %(copyright)s
   *
   * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
   * See the COPYING.LIB file in the top-level directory.
   */
  
  ''',
                       blurb=self._blurb, copyright=self._copyright)
  
      def _bottom(self) -> str:
          return mcgen('''
  
  /* Dummy declaration to prevent empty .o file */
  char qapi_dummy_%(name)s;
  ''',
                       name=c_fname(self.fname))
  
  
  class QAPIGenH(QAPIGenC):
      def _top(self) -> str:
          return super()._top() + guardstart(self.fname)
  
      def _bottom(self) -> str:
          return guardend(self.fname)
  
  
  @contextmanager
  def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
      """
      A with-statement context manager that wraps with `start_if()` / `end_if()`.
  
      :param ifcond: A sequence of conditionals, passed to `start_if()`.
      :param args: any number of `QAPIGenCCode`.
  
      Example::
  
          with ifcontext(ifcond, self._genh, self._genc):
              modify self._genh and self._genc ...
  
      Is equivalent to calling::
  
          self._genh.start_if(ifcond)
          self._genc.start_if(ifcond)
          modify self._genh and self._genc ...
          self._genh.end_if()
          self._genc.end_if()
      """
      for arg in args:
          arg.start_if(ifcond)
      yield
      for arg in args:
          arg.end_if()
  
  
  class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
      def __init__(self,
                   prefix: str,
                   what: str,
                   blurb: str,
                   pydoc: str):
          self._prefix = prefix
          self._what = what
          self._genc = QAPIGenC(self._prefix + self._what + '.c',
                                blurb, pydoc)
          self._genh = QAPIGenH(self._prefix + self._what + '.h',
                                blurb, pydoc)
  
      def write(self, output_dir: str) -> None:
          self._genc.write(output_dir)
          self._genh.write(output_dir)
  
  
  class QAPISchemaModularCVisitor(QAPISchemaVisitor):
      def __init__(self,
                   prefix: str,
                   what: str,
                   user_blurb: str,
                   builtin_blurb: Optional[str],
                   pydoc: str):
          self._prefix = prefix
          self._what = what
          self._user_blurb = user_blurb
          self._builtin_blurb = builtin_blurb
          self._pydoc = pydoc
          self._current_module: Optional[str] = None
          self._module: Dict[str, Tuple[QAPIGenC, QAPIGenH]] = {}
          self._main_module: Optional[str] = None
  
      @property
      def _genc(self) -> QAPIGenC:
          assert self._current_module is not None
          return self._module[self._current_module][0]
  
      @property
      def _genh(self) -> QAPIGenH:
          assert self._current_module is not None
          return self._module[self._current_module][1]
  
      @staticmethod
      def _module_dirname(name: str) -> str:
          if QAPISchemaModule.is_user_module(name):
              return os.path.dirname(name)
          return ''
  
      def _module_basename(self, what: str, name: str) -> str:
          ret = '' if QAPISchemaModule.is_builtin_module(name) else self._prefix
          if QAPISchemaModule.is_user_module(name):
              basename = os.path.basename(name)
              ret += what
              if name != self._main_module:
                  ret += '-' + os.path.splitext(basename)[0]
          else:
              assert QAPISchemaModule.is_system_module(name)
              ret += re.sub(r'-', '-' + name[2:] + '-', what)
          return ret
  
      def _module_filename(self, what: str, name: str) -> str:
          return os.path.join(self._module_dirname(name),
                              self._module_basename(what, name))
  
      def _add_module(self, name: str, blurb: str) -> None:
          if QAPISchemaModule.is_user_module(name):
              if self._main_module is None:
                  self._main_module = name
          basename = self._module_filename(self._what, name)
          genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
          genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
          self._module[name] = (genc, genh)
          self._current_module = name
  
      @contextmanager
      def _temp_module(self, name: str) -> Iterator[None]:
          old_module = self._current_module
          self._current_module = name
          yield
          self._current_module = old_module
  
      def write(self, output_dir: str, opt_builtins: bool = False) -> None:
          for name, (genc, genh) in self._module.items():
              if QAPISchemaModule.is_builtin_module(name) and not opt_builtins:
                  continue
              genc.write(output_dir)
              genh.write(output_dir)
  
      def _begin_builtin_module(self) -> None:
          pass
  
      def _begin_user_module(self, name: str) -> None:
          pass
  
      def visit_module(self, name: str) -> None:
          if QAPISchemaModule.is_builtin_module(name):
              if self._builtin_blurb:
                  self._add_module(name, self._builtin_blurb)
                  self._begin_builtin_module()
              else:
                  # The built-in module has not been created.  No code may
                  # be generated.
                  self._current_module = None
          else:
              assert QAPISchemaModule.is_user_module(name)
              self._add_module(name, self._user_blurb)
              self._begin_user_module(name)
  
      def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
          relname = os.path.relpath(self._module_filename(self._what, name),
                                    os.path.dirname(self._genh.fname))
          self._genh.preamble_add(mcgen('''
  #include "%(relname)s.h"
  ''',
                                        relname=relname))
  