# Copyright 2016 The Gemmlowp Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CC code emitter.

Used by generators to programatically prepare C++ code. Contains some simple
tools that allow generating nicely indented code and do basic correctness
checking.
"""


class Error(Exception):
  """Module level error."""


class NamespaceError(Error):
  """Invalid namespace operation."""


class HeaderError(Error):
  """Invalid cc header structure."""


class ClassError(Error):
  """Invalid class syntax."""


class CCEmitter(object):
  """Emits c++ code."""

  def __init__(self, debug=False):
    self.indent = ''
    self.debug = debug
    self.namespaces = []
    self.classes = []
    self.header_name = None

  def PushIndent(self):
    self.indent += '  '

  def PopIndent(self):
    self.indent = self.indent[:-2]

  def EmitIndented(self, what):
    print(self.indent + what)

  def EmitNewline(self):
    print('')

  def EmitPreprocessor1(self, op, param):
    print('#%s %s' % (op, param))

  def EmitPreprocessor(self, op):
    print('#%s' % op)

  def EmitInclude(self, include):
    self.EmitPreprocessor1('include', include)

  def EmitAssign(self, variable, value):
    self.EmitBinaryOp(variable, '=', value)

  def EmitAssignIncrement(self, variable, value):
    self.EmitBinaryOp(variable, '+=', value)

  def EmitBinaryOp(self, operand_1, op, operand_2):
    self.EmitCode('%s %s %s' % (operand_1, op, operand_2))

  def EmitCall(self, function, params=None):
    if not params:
      params = []
    self.EmitCode('%s(%s)' % (function, ', '.join(map(str, params))))

  def EmitCode(self, code):
    self.EmitIndented('%s;' % code)

  def EmitCodeNoSemicolon(self, code):
    self.EmitIndented('%s' % code)

  def EmitDeclare(self, decl_type, name, value):
    self.EmitAssign('%s %s' % (decl_type, name), value)

  def EmitAssert(self, assert_expression):
    if self.debug:
      self.EmitCall1('assert', assert_expression)

  def EmitHeaderBegin(self, header_name, includes=None):
    if includes is None:
      includes = []
    if self.header_name:
      raise HeaderError('Header already defined.')
    self.EmitPreprocessor1('ifndef', (header_name + '_H_').upper())
    self.EmitPreprocessor1('define', (header_name + '_H_').upper())
    self.EmitNewline()
    if includes:
      for include in includes:
        self.EmitInclude(include)
      self.EmitNewline()
    self.header_name = header_name

  def EmitHeaderEnd(self):
    if not self.header_name:
      raise HeaderError('Header undefined.')
    self.EmitPreprocessor1('endif',
                           ' // %s' % (self.header_name + '_H_').upper())
    self.header_name = None

  def EmitMemberFunctionBegin(self, class_name, class_template_params,
                              class_specializations, function_name,
                              function_params, return_type):
    """Emit member function of a template/specialized class."""
    if class_template_params or class_specializations:
      self.EmitIndented('template<%s>' % ', '.join(class_template_params))

    if class_specializations:
      class_name += '<%s>' % ', '.join(map(str, class_specializations))

    self.EmitIndented('%s %s::%s(%s) {' % (
        return_type, class_name, function_name,
        ', '.join(['%s %s' % (t, n) for (t, n) in function_params])))
    self.PushIndent()

  def EmitFunctionBegin(self, function_name, params, return_type):
    self.EmitIndented('%s %s(%s) {' %
                      (return_type, function_name,
                       ', '.join(['%s %s' % (t, n) for (t, n) in params])))
    self.PushIndent()

  def EmitFunctionEnd(self):
    self.PopIndent()
    self.EmitIndented('}')
    self.EmitNewline()

  def EmitClassBegin(self, class_name, template_params, specializations,
                     base_classes):
    """Emit class block header."""
    self.classes.append(class_name)
    if template_params or specializations:
      self.EmitIndented('template<%s>' % ', '.join(template_params))

    class_name_extended = class_name
    if specializations:
      class_name_extended += '<%s>' % ', '.join(map(str, specializations))
    if base_classes:
      class_name_extended += ' : ' + ', '.join(base_classes)
    self.EmitIndented('class %s {' % class_name_extended)
    self.PushIndent()

  def EmitClassEnd(self):
    if not self.classes:
      raise ClassError('No class on stack.')
    self.classes.pop()
    self.PopIndent()
    self.EmitIndented('};')
    self.EmitNewline()

  def EmitAccessModifier(self, modifier):
    if not self.classes:
      raise ClassError('No class on stack.')
    self.PopIndent()
    self.EmitIndented(' %s:' % modifier)
    self.PushIndent()

  def EmitNamespaceBegin(self, namespace):
    self.EmitCodeNoSemicolon('namespace %s {' % namespace)
    self.namespaces.append(namespace)

  def EmitNamespaceEnd(self):
    if not self.namespaces:
      raise NamespaceError('No namespace on stack.')
    self.EmitCodeNoSemicolon('}  // namespace %s' % self.namespaces.pop())

  def EmitComment(self, comment):
    self.EmitIndented('// ' + comment)

  def EmitOpenBracket(self, pre_bracket=None):
    if pre_bracket:
      self.EmitIndented('%s {' % pre_bracket)
    else:
      self.EmitIndented('{')
    self.PushIndent()

  def EmitCloseBracket(self):
    self.PopIndent()
    self.EmitIndented('}')

  def EmitSwitch(self, switch):
    self.EmitOpenBracket('switch (%s)' % switch)

  def EmitSwitchEnd(self):
    self.EmitCloseBracket()

  def EmitCase(self, value):
    self.EmitCodeNoSemicolon('case %s:' % value)

  def EmitBreak(self):
    self.EmitCode('break')

  def EmitIf(self, condition):
    self.EmitOpenBracket('if (%s)' % condition)

  def EmitElse(self):
    self.PopIndent()
    self.EmitCodeNoSemicolon('} else {')
    self.PushIndent()

  def EmitEndif(self):
    self.EmitCloseBracket()

  def Scope(self, scope, value):
    return '%s::%s' % (scope, value)
