#!/usr/bin/env python3
"""
tidy-imports *.py
tidy-imports < foo.py

Automatically improves python import statements.

  - Adds missing imports and mandatory imports.
  - Removes unused imports.
  - Nicely formats imports (sorts, aligns, wraps).

If filenames are given on the command line, rewrites them.  Otherwise, if
stdin is not a tty, read from stdin and write to stdout.

Only top-level import statements are touched.

"""

# pyflyby/tidy-imports
# Copyright (C) 2011, 2012, 2014 Karl Chen.
# License: MIT http://opensource.org/licenses/MIT

from __future__ import print_function, absolute_import, division, with_statement

from   distutils.spawn          import find_executable
import subprocess
import sys
import os

from   pyflyby._cmdline         import hfmt, parse_args, process_actions
from   pyflyby._imports2s       import (canonicalize_imports,
                                        fix_unused_and_missing_imports,
                                        replace_star_imports,
                                        transform_imports)
from   pyflyby._log             import logger

if sys.version_info[0] == 3:
    # can't install/import it on Python 2
    try:
        import toml
        TOML_AVAIL = True
    except ModuleNotFoundError:
        TOML_AVAIL = False

else:
    TOML_AVAIL = False



def _get_pyproj_toml_config():
    """
    Try to find current project pyproject.toml
    in cwd or parents directories.
    """
    if not TOML_AVAIL:
        return None

    from pathlib import Path


    cwd = Path(os.getcwd())


    for pth in [cwd] + list(cwd.parents):
        pyproj_toml = pth /'pyproject.toml'
        if pyproj_toml.exists() and pyproj_toml.is_file():
            return pyproj_toml.read_text()

    return None





def _addopts(parser):
    """
    Callbacks to the parser to fill in extra options.
    """
    parser.add_option('--add-missing',
                        default=True, action='store_true',
                        help=hfmt('''
                            (Default) Add missing imports.'''))
    parser.add_option('--no-add-missing', dest='add_missing',
                        default=True, action='store_false',
                        help=hfmt('''
                            Don't add missing imports.'''))
    parser.add_option('--remove-unused',
                        default="AUTOMATIC", action='store_true',
                        help=hfmt('''
                            Remove unused imports
                            (default unless filename == __init__.py).'''))
    parser.add_option('--no-remove-unused', dest='remove_unused',
                        action='store_false',
                        help=hfmt('''
                            Don't remove unused imports
                            (default if filename == __init__.py).'''))
    parser.add_option('--add-mandatory',
                        default=True, action='store_true',
                        help=hfmt('''
                            (Default) Add mandatory imports.'''))
    parser.add_option('--no-add-mandatory', dest='add_mandatory',
                        default=True, action='store_false',
                        help=hfmt('''
                            Don't add mandatory imports.'''))
    parser.add_option('--replace-star-imports',
                        default=False, action='store_true',
                        help=hfmt('''
                            Replace 'from foo.bar import *' with full list
                            of imports before removing unused imports.'''))
    parser.add_option('--no-replace-star-imports',
                        dest='replace_star_imports',
                        action='store_false',
                        help=hfmt('''
                            (Default) Don't replace 'from foo.bar import
                            *'.'''))
    parser.add_option('--canonicalize',
                        default=True, action='store_true',
                        help=hfmt('''
                            (Default) Replace imports with canonical
                            equivalent imports, according to database.'''))
    parser.add_option('--no-canonicalize', dest='canonicalize',
                        default=True, action='store_false',
                        help=hfmt('''
                            Don't canonicalize imports.'''))
    parser.add_option('--py23-fallback', dest='py23_fallback',
                        default=False, action='store_true',
                        help=hfmt('''
                            Automatically fallback to python2/python3 if the
                            source file has a syntax error.'''))
    parser.add_option('--no-py23-fallback', dest='py23_fallback',
                        action='store_false',
                        help=hfmt('''
                            (Default) Do not automatically fallback to
                            python2/python3 if the source file has a syntax
                            error.'''))


    def transform_callback(option, opt_str, value, group):
        k, v = value.split("=", 1)
        group.values.transformations[k] = v
    parser.add_option("--transform", action='callback',
                        type="string", callback=transform_callback,
                        metavar="OLD=NEW",
                        dest="transformations", default={},
                        help=hfmt('''
                            Replace OLD with NEW in imports.
                            May be specified multiple times.'''))
    def no_add_callback(option, opt_str, value, group):
        group.values.add_missing = False
        group.values.add_mandatory = False
    parser.add_option('--no-add', action='callback',
                        callback=no_add_callback,
                        help=hfmt('''
                            Equivalent to --no-add-missing
                            --no-add-mandatory.'''))
def main():

    config_text = _get_pyproj_toml_config()
    if config_text:
        default_config = toml.loads(config_text).get('tool', {}).get('pyflyby',{})
    else:
        default_config = {}

    def _add_opts_and_defaults(parser):
        _addopts(parser)
        parser.set_defaults(**default_config)
    options, args = parse_args(
        _add_opts_and_defaults, import_format_params=True, modify_action_params=True, defaults=default_config)
    def modify(x):
        if options.canonicalize:
            x = canonicalize_imports(x, params=options.params)
        if options.transformations:
            x = transform_imports(x, options.transformations,
                                  params=options.params)
        if options.replace_star_imports:
            x = replace_star_imports(x, params=options.params)
        return fix_unused_and_missing_imports(
            x, params=options.params,
            add_missing=options.add_missing,
            remove_unused=options.remove_unused,
            add_mandatory=options.add_mandatory,
            )

    if options.py23_fallback:
        try:
            process_actions(args, options.actions, modify,
                            reraise_exceptions=SyntaxError)
        except SyntaxError as e:
            python = 'python2' if sys.version_info[0] == 3 else 'python3'
            python_full = find_executable(python)
            if not python_full:
                logger.error("Fallback failed: could not find %s", python)
                raise
            logger.info("SyntaxError detected ({}), falling back to {}".format(
                e, python))
            args = [python_full] + sys.argv + ['--no-py23-fallback']
            try:
                raise SystemExit(subprocess.call(args))
            except KeyboardInterrupt:
                sys.exit(1)
    else:
        process_actions(args, options.actions, modify)


if __name__ == '__main__':
    main()
