# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import json
import struct_generator


def _JSONToCString16(json_string_literal):
  """Converts a JSON string literal to a C++ UTF-16 string literal. This is
  done by converting \\u#### to \\x####.
  """
  c_string_literal = json_string_literal
  escape_index = c_string_literal.find('\\')
  while escape_index > 0:
    if c_string_literal[escape_index + 1] == 'u':
      # We close the C string literal after the 4 hex digits and reopen it right
      # after, otherwise the Windows compiler will sometimes try to get more
      # than 4 characters in the hex string.
      c_string_literal = (
        c_string_literal[0 : escape_index + 1]
        + 'x'
        + c_string_literal[escape_index + 2 : escape_index + 6]
        + '" u"'
        + c_string_literal[escape_index + 6 :]
      )
    escape_index = c_string_literal.find('\\', escape_index + 6)
  return c_string_literal


def _GenerateString(content, lines, indent='  '):
  """Generates an UTF-8 string to be included in a static structure initializer.
  If content is not specified, uses nullptr.
  """
  if content is None:
    lines.append(indent + 'nullptr,')
  else:
    # json.dumps quotes the string and escape characters as required.
    lines.append(indent + '%s,' % json.dumps(content))


def _GenerateString16(content, lines, indent='  '):
  """Generates an UTF-16 string to be included in a static structure
  initializer. If content is not specified, uses nullptr.
  """
  if content is None:
    lines.append(indent + 'nullptr,')
  else:
    # json.dumps quotes the string and escape characters as required.
    lines.append(indent + 'u%s,' % _JSONToCString16(json.dumps(content)))


def _GenerateArrayVariableName(element_name, field_name, field_name_count):
  # Generates a unique variable name for an array variable.
  var = 'array_%s_%s' % (element_name, field_name)
  if var not in field_name_count:
    field_name_count[var] = 0
    return var
  new_var = '%s_%d' % (var, field_name_count[var])
  field_name_count[var] += 1
  return new_var


def _GenerateArray(
  element_name, field_info, content, lines, indent, field_name_count
):
  """Generates an array to be included in a static structure initializer. If
  content is not specified, uses nullptr. The array is assigned to a temporary
  variable which is initialized before the structure.
  """
  if not content:
    lines.append(indent + '{},')
    return

  # Create a new array variable and use it in the structure initializer.
  # This prohibits nested arrays. Add a clash detection and renaming mechanism
  # to solve the problem.
  var = _GenerateArrayVariableName(
    element_name, field_info['field'], field_name_count
  )
  lines.append(indent + '%s,' % var)
  # Generate the array content.
  array_lines = []
  field_info['contents']['field'] = var
  array_lines.append(
    struct_generator.GenerateField(field_info['contents']) + '[] = {'
  )
  for subcontent in content:
    GenerateFieldContent(
      element_name,
      field_info['contents'],
      subcontent,
      array_lines,
      indent,
      field_name_count,
    )
  array_lines.append('};')
  # Prepend the generated array so it is initialized before the structure.
  lines.reverse()
  array_lines.reverse()
  lines.extend(array_lines)
  lines.reverse()


def _GenerateStruct(
  element_name, field_info, content, lines, indent, field_name_count
):
  """Generates a struct to be included in a static structure initializer. If
  content is not specified, uses {}.
  """
  if content is None:
    lines.append(indent + '{},')
    return

  fields = field_info['fields']
  lines.append(indent + '{')
  for field in fields:
    subcontent = content.get(field['field'])
    GenerateFieldContent(
      element_name, field, subcontent, lines, '  ' + indent, field_name_count
    )
  lines.append(indent + '},')


def GenerateFieldContent(
  element_name, field_info, content, lines, indent, field_name_count
):
  """Generate the content of a field to be included in the static structure
  initializer. If the field's content is not specified, uses the default value
  if one exists.
  """
  if content is None:
    content = field_info.get('default', None)
  type = field_info['type']
  if type in ('bool', 'int', 'enum', 'class'):
    if isinstance(content, bool):
      # Re-format Python `True` and `False` as C++ `true` and `false`.
      lines.append('%s%s,' % (indent, str(content).lower()))
    else:
      lines.append('%s%s,' % (indent, content))
  elif type == 'string':
    _GenerateString(content, lines, indent)
  elif type == 'string16':
    _GenerateString16(content, lines, indent)
  elif type == 'array':
    _GenerateArray(
      element_name, field_info, content, lines, indent, field_name_count
    )
  elif type == 'struct':
    _GenerateStruct(
      element_name, field_info, content, lines, indent, field_name_count
    )
  else:
    raise RuntimeError('Unknown field type "%s"' % type)


def GenerateElement(type_name, schema, element_name, element, field_name_count):
  """Generate the static structure initializer for one element."""
  lines = []
  lines.append('const %s %s = {' % (type_name, element_name))
  for field_info in schema:
    content = element.get(field_info['field'], None)
    if content == None and not field_info.get('optional', False):
      raise RuntimeError(
        'Mandatory field "%s" omitted in element "%s".'
        % (field_info['field'], element_name)
      )
    GenerateFieldContent(
      element_name, field_info, content, lines, '  ', field_name_count
    )
  lines.append('};')
  return '\n'.join(lines)


def GenerateElements(type_name, schema, description, field_name_count={}):
  """Generate the static structure initializer for all the elements in the
  description['elements'] dictionary, as well as for any variables in
  description['int_variables'].
  """
  result = []
  for var_name, value in description.get('int_variables', {}).items():
    result.append('const int %s = %s;' % (var_name, value))
  result.append('')

  for element_name, element in description.get('elements', {}).items():
    result.append(
      GenerateElement(
        type_name, schema, element_name, element, field_name_count
      )
    )
    result.append('')
  return '\n'.join(result)
