#!/usr/bin/python3

import inspect
import os
import sys

from optparse import OptionParser


def error(message, exit_code=1):
    sys.stderr.write('pyvim: %s\n' % message)
    sys.exit(exit_code)


def get_object(path):
    parts = path.split('.')

    module = parts[0]
    remaining = parts[1:]

    if not remaining:
        return __import__(module)

    module = __import__(module, fromlist=[remaining[0]])

    obj = getattr(module, remaining.pop(0))

    while remaining:
        obj = getattr(obj, remaining.pop(0))
    return obj


def import_path_to_file_path(path):
    try:
        obj = get_object(path)
    except (AttributeError, ImportError) as e:
        error('error opening %r: %s' % (path, str(e)))

    try:
        _source, lineno = inspect.findsource(obj)
    except TypeError:
        try:
            _source, lineno = inspect.findsource(obj.__class__)
        except TypeError as e:
            error('error opening %r: %s' % (path, str(e)))
    except IOError as e:
        # allow opening an empty module (e.g. __init__.py)
        if inspect.ismodule(obj):
            sourcefile = inspect.getsourcefile(obj)
            if sourcefile:
                lineno = 0
                e = None

        if e:
            error('error opening %r: %s' % (path, str(e)))

    try:
        path = inspect.getsourcefile(obj)
    except TypeError:
        try:
            path = inspect.getsourcefile(obj.__class__)
        except TypeError as e:
            error('error opening %r: %s' % (path, str(e)))
    except IOError:
        path = obj.__file__

    if lineno:
        additional_args = ['+%d' % (lineno+1)]
    else:
        additional_args = []

    dirname, basename = os.path.split(path)

    return dirname, basename, additional_args


def get_editor():
    editor = os.environ.get('EDITOR', None)
    if editor:
        return editor

    editor = '/usr/bin/xdg-open'
    if os.path.isfile(editor):
        return editor

    error("$EDITOR not configured and no suitable fallback found.")


def main():
    parser = OptionParser()
    parser.add_option(
        "-d", "--print-dir",
        dest="print_dir",
        action='store_true',
        help="Print directory to standard out rather than opening the file.")

    parser.add_option(
        "-l", "--print-path",
        dest="print_path",
        action='store_true',
        help="Print absolute file path to standard out rather than opening the file.")

    parser.add_option(
        "-a", "--absolute-open",
        dest="absolute_open",
        action='store_true',
        help="Open the file using the absolute path rather than changing to the parent directory.")

    parser.add_option(
        "-v", "--verbose",
        dest="verbose",
        action='store_true',
        help="Display commands before they're run.")

    options, args = parser.parse_args()

    if not args:
        parser.error("No import path supplied")

    dirname, basename, additional_args = import_path_to_file_path(args[0])

    if options.print_dir:
        print(dirname)
        return

    if options.print_dir:
        print(dirname)
        return

    if options.print_path:
        print(os.path.join(dirname, basename))
        return

    editor = get_editor()

    # this allows you to do :e . in vim to see the rest of the source code.
    if options.absolute_open:
        path = os.path.join(dirname, basename)
    else:
        if options.verbose:
            print('cd %s' % dirname)
        os.chdir(dirname)
        path = basename

    command = [editor, path] + additional_args + args[1:]

    if options.verbose:
        print(' '.join(command))
    os.execvp(editor, command)


if __name__ == '__main__':
    main()
