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

from __future__ import print_function
import collections
import json
import os.path
import re
import sys

description = ''


primitiveTypes = ['integer', 'number', 'boolean', 'string', 'object',
                  'any', 'array', 'binary']


def assignType(item, type, is_array=False, map_binary_to_string=False):
  if is_array:
    item['type'] = 'array'
    item['items'] = collections.OrderedDict()
    assignType(item['items'], type, False, map_binary_to_string)
    return

  if type == 'enum':
    type = 'string'
  if map_binary_to_string and type == 'binary':
    type = 'string'
  if type in primitiveTypes:
    item['type'] = type
  else:
    item['$ref'] = type


def createItem(d, experimental, deprecated, name=None):
  result = collections.OrderedDict(d)
  if name:
    result['name'] = name
  global description
  if description:
    result['description'] = description.strip()
  if experimental:
    result['experimental'] = True
  if deprecated:
    result['deprecated'] = True
  return result


def parse(data, file_name, map_binary_to_string=False):
  protocol = collections.OrderedDict()
  protocol['version'] = collections.OrderedDict()
  protocol['domains'] = []
  domain = None
  item = None
  subitems = None
  nukeDescription = False
  global description
  lines = data.split('\n')
  for i in range(0, len(lines)):
    if nukeDescription:
      description = ''
      nukeDescription = False
    line = lines[i]
    trimLine = line.strip()

    if trimLine.startswith('#'):
      if len(description):
        description += '\n'
      description += trimLine[2:]
      continue
    else:
      nukeDescription = True

    if len(trimLine) == 0:
      continue

    match = re.compile(
        r'^(experimental )?(deprecated )?domain (.*)').match(line)
    if match:
      domain = createItem({'domain' : match.group(3)}, match.group(1),
                          match.group(2))
      protocol['domains'].append(domain)
      continue

    match = re.compile(r'^  depends on ([^\s]+)').match(line)
    if match:
      if 'dependencies' not in domain:
        domain['dependencies'] = []
      domain['dependencies'].append(match.group(1))
      continue

    match = re.compile(r'^  (experimental )?(deprecated )?type (.*) '
                       r'extends (array of )?([^\s]+)').match(line)
    if match:
      if 'types' not in domain:
        domain['types'] = []
      item = createItem({'id': match.group(3)}, match.group(1), match.group(2))
      assignType(item, match.group(5), match.group(4), map_binary_to_string)
      domain['types'].append(item)
      continue

    match = re.compile(
        r'^  (experimental )?(deprecated )?(command|event) (.*)').match(line)
    if match:
      list = []
      if match.group(3) == 'command':
        if 'commands' in domain:
          list = domain['commands']
        else:
          list = domain['commands'] = []
      else:
        if 'events' in domain:
          list = domain['events']
        else:
          list = domain['events'] = []

      item = createItem({}, match.group(1), match.group(2), match.group(4))
      list.append(item)
      continue

    match = re.compile(
        r'^      (experimental )?(deprecated )?(optional )?'
        r'(array of )?([^\s]+) ([^\s]+)').match(line)
    if match:
      param = createItem({}, match.group(1), match.group(2), match.group(6))
      if match.group(3):
        param['optional'] = True
      assignType(param, match.group(5), match.group(4), map_binary_to_string)
      if match.group(5) == 'enum':
        enumliterals = param['enum'] = []
      subitems.append(param)
      continue

    match = re.compile(r'^    (parameters|returns|properties)').match(line)
    if match:
      subitems = item[match.group(1)] = []
      continue

    match = re.compile(r'^    enum').match(line)
    if match:
      enumliterals = item['enum'] = []
      continue

    match = re.compile(r'^version').match(line)
    if match:
      continue

    match = re.compile(r'^  major (\d+)').match(line)
    if match:
      protocol['version']['major'] = match.group(1)
      continue

    match = re.compile(r'^  minor (\d+)').match(line)
    if match:
      protocol['version']['minor'] = match.group(1)
      continue

    match = re.compile(r'^    redirect ([^\s]+)').match(line)
    if match:
      item['redirect'] = match.group(1)
      continue

    match = re.compile(r'^      (  )?[^\s]+$').match(line)
    if match:
      # enum literal
      enumliterals.append(trimLine)
      continue

    print('Error in %s:%s, illegal token: \t%s' % (file_name, i, line))
    sys.exit(1)
  return protocol


def loads(data, file_name, map_binary_to_string=False):
  if file_name.endswith(".pdl"):
    return parse(data, file_name, map_binary_to_string)
  return json.loads(data)
