#!/usr/bin/env python

# Copyright 2017 Google Inc.
#
# 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.

"""Analyze an entire project using pytype."""

from __future__ import print_function

import argparse
import logging
import os
import sys

import importlab.environment
import importlab.fs
import importlab.graph
import importlab.output

from pytype import file_utils
from pytype import utils
from pytype.tools import environment
from pytype.tools import tool_utils
from pytype.tools.analyze_project import config
from pytype.tools.analyze_project import pytype_runner


def parse_args():
  """Parse command line args."""

  parser = argparse.ArgumentParser()
  parser.add_argument(
      'filenames', metavar='filename', type=str, nargs='*',
      help='input file(s)')
  parser.add_argument(
      '--tree', dest='tree', action='store_true', default=False,
      help='Display import tree.')
  parser.add_argument(
      '--unresolved', dest='unresolved', action='store_true', default=False,
      help='Display unresolved dependencies.')
  parser.add_argument(
      '-v', '--verbosity', dest='verbosity', type=int, action='store',
      default=1,
      help='Set logging level: 0=ERROR, 1 =WARNING (default), 2=INFO.')
  parser.add_argument(
      '--config', dest='config', type=str, action='store', default='',
      help='Configuration file.')
  parser.add_argument(
      '--generate-config', dest='generate_config', type=str, action='store',
      default='',
      help='Write out a dummy configuration file.')
  return parser.parse_args()


def read_config_file(args):
  """Read config from args.config or from setup.cfg."""

  ret = config.Config()
  if args.config:
    if not ret.read_from_file(args.config):
      logging.critical('Could not read config file: %s\n'
                       '  Generate a sample configuration via:\n'
                       '  pytype-all --generate-config sample.cfg', args.config)
      sys.exit(1)
  else:
    # Try reading from setup.cfg
    filepath = ret.read_from_setup_cfg(os.getcwd())
    if filepath:
      logging.info('Reading config from: %s', filepath)
    else:
      logging.info('No config file specified, and no [pytype] section in '
                   'setup.cfg. Using default configuration.')
  return ret


class PytdFileSystem(importlab.fs.ExtensionRemappingFileSystem):
  """File system that remaps .py file extensions to pytd."""

  def __init__(self, underlying):
    super(PytdFileSystem, self).__init__(underlying, 'pytd')


def create_importlab_environment(conf, typeshed):
  """Create an importlab environment from the python version and path."""
  python_version = utils.split_version(conf.python_version)
  path = importlab.fs.Path()
  for p in conf.pythonpath:
    path.add_path(p, 'os')
  for p in typeshed.get_pytd_paths(python_version):
    path.add_fs(PytdFileSystem(importlab.fs.OSFileSystem(p)))
  for p in typeshed.get_typeshed_paths(python_version):
    path.add_path(p, 'pyi')
  return importlab.environment.Environment(path, python_version)


def main():
  args = parse_args()
  tool_utils.setup_logging_or_die(args.verbosity)

  if args.generate_config:
    config.generate_sample_config_or_die(args.generate_config)
    sys.exit(0)

  if not args.filenames:
    logging.warning('Nothing to do!')
    sys.exit(0)
  filenames = file_utils.expand_source_files(args.filenames)

  conf = read_config_file(args)
  if not conf.pythonpath:
    conf.pythonpath = environment.compute_pythonpath(filenames)
  logging.info('\n  '.join(['Configuration:'] + str(conf).split('\n')))

  # Importlab needs the python exe, so we check it as early as possible.
  environment.check_python_exe_or_die(conf.python_version)

  typeshed = environment.initialize_typeshed_or_die()
  env = create_importlab_environment(conf, typeshed)
  try:
    import_graph = importlab.graph.ImportGraph.create(env, filenames)
  except Exception as e:  # pylint: disable=broad-except
    logging.critical('Cannot parse input files:\n%s', str(e))
    sys.exit(1)

  if args.tree:
    print('Source tree:')
    importlab.output.print_tree(import_graph)
    sys.exit(0)

  if args.unresolved:
    print('Unresolved dependencies:')
    for imp in sorted(import_graph.get_all_unresolved()):
      print(' ', imp.name)
    sys.exit(0)

  # --tree and --unresolved can run without pytype being available, so we do
  # this check only if we're going to fire up the pytype runner.
  environment.check_pytype_or_die()

  logging.info('Source tree:\n%s',
               importlab.output.formatted_deps_list(import_graph))
  tool_utils.makedirs_or_die(conf.output_dir,
                             'Could not create output directory')
  runner = pytype_runner.PytypeRunner(
      filenames, import_graph.sorted_source_files(), conf)
  runner.run()


if __name__ == '__main__':
  sys.exit(main())
