# Copyright 2017 The Abseil Authors.
#
# 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.

"""Helper script used by app_test.py."""

import os
import sys
import types
from typing import Any, Dict, Optional

faulthandler: Optional[types.ModuleType]
try:
  import faulthandler  # pylint: disable=g-import-not-at-top
except ImportError:
  faulthandler = None

from absl import app  # pylint: disable=g-import-not-at-top
from absl import flags

FLAGS = flags.FLAGS
flags.DEFINE_boolean('faulthandler_sigsegv', False, 'raise SIGSEGV')
flags.DEFINE_boolean('raise_exception', False, 'Raise MyException from main.')
flags.DEFINE_boolean(
    'raise_usage_error', False, 'Raise app.UsageError from main.')
flags.DEFINE_integer(
    'usage_error_exitcode', None, 'The exitcode if app.UsageError if raised.')
flags.DEFINE_string(
    'str_flag_with_unicode_args', 'thumb:\U0001F44D', 'smile:\U0001F604'
)
flags.DEFINE_boolean('print_init_callbacks', False,
                     'print init callbacks and exit')


class MyException(Exception):
  pass


class MyExceptionHandler(app.ExceptionHandler):

  def __init__(self, message):
    self.message = message

  def handle(self, exc):
    sys.stdout.write(f'MyExceptionHandler: {self.message}\n')


def real_main(argv):
  """The main function."""
  if os.environ.get('APP_TEST_PRINT_ARGV', False):
    sys.stdout.write('argv: {}\n'.format(' '.join(argv)))

  if FLAGS.raise_exception:
    raise MyException

  if FLAGS.raise_usage_error:
    if FLAGS.usage_error_exitcode is not None:
      raise app.UsageError('Error!', FLAGS.usage_error_exitcode)
    else:
      raise app.UsageError('Error!')

  if FLAGS.faulthandler_sigsegv:
    faulthandler._sigsegv()  # pylint: disable=protected-access
    sys.exit(1)  # Should not reach here.

  if FLAGS.print_init_callbacks:
    app.call_after_init(lambda: _callback_results.append('during real_main'))
    for value in _callback_results:
      print(f'callback: {value}')
    sys.exit(0)

  # Ensure that we have a random C++ flag in flags.FLAGS; this shows
  # us that app.run() did the right thing in conjunction with C++ flags.
  helper_type = os.environ['APP_TEST_HELPER_TYPE']
  if helper_type == 'clif':
    if 'heap_check_before_constructors' in flags.FLAGS:
      print(f'PASS: C++ flag present and helper_type is {helper_type}')
      sys.exit(0)
    else:
      print(f'FAILED: C++ flag absent but helper_type is {helper_type}')
      sys.exit(1)
  elif helper_type == 'pure_python':
    if 'heap_check_before_constructors' in flags.FLAGS:
      print('FAILED: C++ flag present but helper_type is pure_python')
      sys.exit(1)
    else:
      print('PASS: C++ flag absent and helper_type is pure_python')
      sys.exit(0)
  else:
    print(f'Unexpected helper_type "{helper_type}"')
    sys.exit(1)


def custom_main(argv):
  print('Function called: custom_main.')
  real_main(argv)


def main(argv):
  print('Function called: main.')
  real_main(argv)


flags_parser_argv_sentinel = object()


def flags_parser_main(argv):
  print('Function called: main_with_flags_parser.')
  if argv is not flags_parser_argv_sentinel:
    sys.exit(
        'FAILED: main function should be called with the return value of '
        'flags_parser, but found: {}'.format(argv))


def flags_parser(argv):
  print('Function called: flags_parser.')
  if os.environ.get('APP_TEST_FLAGS_PARSER_PARSE_FLAGS', None):
    FLAGS(argv)
  return flags_parser_argv_sentinel


# Holds results from callbacks triggered by `app.run_after_init`.
_callback_results = []

if __name__ == '__main__':
  kwargs: Dict[str, Any] = {'main': main}
  main_function_name = os.environ.get('APP_TEST_CUSTOM_MAIN_FUNC', None)
  if main_function_name:
    kwargs['main'] = globals()[main_function_name]
  custom_argv = os.environ.get('APP_TEST_CUSTOM_ARGV', None)
  if custom_argv:
    kwargs['argv'] = custom_argv.split(' ')
  if os.environ.get('APP_TEST_USE_CUSTOM_PARSER', None):
    kwargs['flags_parser'] = flags_parser

  app.call_after_init(lambda: _callback_results.append('before app.run'))
  app.install_exception_handler(MyExceptionHandler('first'))
  app.install_exception_handler(MyExceptionHandler('second'))
  app.run(**kwargs)

  sys.exit('This is not reachable.')
