#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""This program either generates the parser files for Torque, generating
the source and header files directly in V8's src directory."""

# for py2/py3 compatibility
from __future__ import print_function

import subprocess
import sys
import re
from subprocess import Popen, PIPE

PYTHON3 = sys.version_info >= (3, 0)

def maybe_decode(arg, encoding="utf-8"):
  return arg.decode(encoding) if PYTHON3 else arg

def maybe_encode(arg, encoding="utf-8"):
  return arg.encode(encoding) if PYTHON3 else arg

kPercentEscape = r'α';  # Unicode alpha
kDerefEscape = r'☆'; # Unicode star
kAddressofEscape = r'⌂'; # Unicode house

def preprocess(input):
  # Special handing of '%' for intrinsics, turn the percent
  # into a unicode character so that it gets treated as part of the
  # intrinsic's name if it's already adjacent to it.
  input = re.sub(r'%([A-Za-z])', kPercentEscape + r'\1', input)
  # Similarly, avoid treating * and & as binary operators when they're
  # probably used as address operators.
  input = re.sub(r'([^/])\*([a-zA-Z(])', r'\1' + kDerefEscape + r'\2', input)
  input = re.sub(r'&([a-zA-Z(])', kAddressofEscape + r'\1', input)


  input = re.sub(r'(if\s+)constexpr(\s*\()', r'\1/*COxp*/\2', input)
  input = re.sub(r'(\s+)operator\s*(\'[^\']+\')', r'\1/*_OPE \2*/', input)
  input = re.sub(r'\btypeswitch\s*(\([^{]*\))\s{', r' if /*tPsW*/ \1 {', input)
  input = re.sub(r'\bcase\s*(\([^{]*\))\s*:\s*deferred\s*{', r' if /*cAsEdEfF*/ \1 {', input)
  input = re.sub(r'\bcase\s*(\([^{]*\))\s*:\s*{', r' if /*cA*/ \1 {', input)

  input = re.sub(r'\bgenerates\s+\'([^\']+)\'\s*',
      r'_GeNeRaTeS00_/*\1@*/', input)
  input = re.sub(r'\bconstexpr\s+\'([^\']+)\'\s*',
      r' _CoNsExP_/*\1@*/', input)
  input = re.sub(r'\notherwise',
      r'\n otherwise', input)
  input = re.sub(r'(\n\s*\S[^\n]*\s)otherwise',
      r'\1_OtheSaLi', input)
  input = re.sub(r'@if\(', r'@iF(', input)
  input = re.sub(r'@export', r'@eXpOrT', input)
  input = re.sub(r'js-implicit[ \n]+', r'jS_iMpLiCiT_', input)
  input = re.sub(r'^(\s*namespace\s+[a-zA-Z_0-9]+\s*{)(\s*)$', r'\1}\2', input, flags = re.MULTILINE)

  # includes are not recognized, change them into comments so that the
  # formatter ignores them first, until we can figure out a way to format cpp
  # includes within a JS file.
  input = re.sub(r'^#include', r'// InClUdE', input, flags=re.MULTILINE)

  return input

def postprocess(output):
  output = re.sub(r'\/\*COxp\*\/', r'constexpr', output)
  output = re.sub(r'(\S+)\s*: type([,>])', r'\1: type\2', output)
  output = re.sub(r'(\n\s*)labels( [A-Z])', r'\1    labels\2', output)
  output = re.sub(r'\/\*_OPE \'([^\']+)\'\*\/', r"operator '\1'", output)
  output = re.sub(r'\bif\s*\/\*tPsW\*\/', r'typeswitch', output)
  output = re.sub(r'\bif\s*\/\*cA\*\/\s*(\([^{]*\))\s*{', r'case \1: {', output)
  output = re.sub(r'\bif\s*\/\*cAsEdEfF\*\/\s*(\([^{]*\))\s*{', r'case \1: deferred {', output)
  output = re.sub(r'\n_GeNeRaTeS00_\s*\/\*([^@]+)@\*\/',
      r"\n    generates '\1'", output)
  output = re.sub(r'_GeNeRaTeS00_\s*\/\*([^@]+)@\*\/',
      r"generates '\1'", output)
  output = re.sub(r'_CoNsExP_\s*\/\*([^@]+)@\*\/',
      r"constexpr '\1'", output)
  output = re.sub(r'\n(\s+)otherwise',
      r"\n\1    otherwise", output)
  output = re.sub(r'\n(\s+)_OtheSaLi',
      r"\n\1otherwise", output)
  output = re.sub(r'_OtheSaLi',
      r"otherwise", output)
  output = re.sub(r'@iF\(', r'@if(', output)
  output = re.sub(r'@eXpOrT',
      r"@export", output)
  output = re.sub(r'jS_iMpLiCiT_',
      r"js-implicit ", output)
  output = re.sub(r'}\n *label ', r'} label ', output);
  output = re.sub(r'^(\s*namespace\s+[a-zA-Z_0-9]+\s*{)}(\s*)$', r'\1\2', output, flags = re.MULTILINE);

  output = re.sub(kPercentEscape, r'%', output)
  output = re.sub(kDerefEscape, r'*', output)
  output = re.sub(kAddressofEscape, r'&', output)


  output = re.sub( r'^// InClUdE',r'#include', output, flags=re.MULTILINE)

  return output

def process(filename, lint, should_format):
  with open(filename, 'r') as content_file:
    content = content_file.read()

  original_input = content

  if sys.platform.startswith('win'):
    p = Popen(['clang-format', '-assume-filename=.ts'], stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True)
  else:
    p = Popen(['clang-format', '-assume-filename=.ts'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
  output, err = p.communicate(maybe_encode(preprocess(content)))
  output = postprocess(maybe_decode(output))
  rc = p.returncode
  if (rc != 0):
    print("error code " + str(rc) + " running clang-format. Exiting...")
    sys.exit(rc);

  if (output != original_input):
    if lint:
      print(filename + ' requires formatting', file=sys.stderr)

    if should_format:
      output_file = open(filename, 'wb')
      output_file.write(maybe_encode(output))
      output_file.close()

def print_usage():
  print('format-torque -i file1[, file2[, ...]]')
  print('    format and overwrite input files')
  print('format-torque -l file1[, file2[, ...]]')
  print('    merely indicate which files need formatting')

def Main():
  if len(sys.argv) < 3:
    print("error: at least 2 arguments required")
    print_usage();
    sys.exit(-1)

  def is_option(arg):
    return arg in ['-i', '-l', '-il']

  should_format = lint = False
  use_stdout = True

  flag, files = sys.argv[1], sys.argv[2:]
  if is_option(flag):
    if '-i' == flag:
      should_format = True
    elif '-l' == flag:
      lint = True
    else:
      lint = True
      should_format = True
  else:
    print("error: -i and/or -l flags must be specified")
    print_usage();
    sys.exit(-1);

  for filename in files:
    process(filename, lint, should_format)

  return 0

if __name__ == '__main__':
  sys.exit(Main());
