#!/usr/bin/env python

# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Fix some flake8 errors by rewriting files for which flake8 emitted
an error/warning. Usage (from the root dir):

    $ python3 -m flake8 --exit-zero | python3 scripts/fix_flake8.py
"""

import sys
import tempfile
import shutil
from collections import defaultdict
from collections import namedtuple
from pprint import pprint as pp  # NOQA


ntentry = namedtuple('ntentry', 'msg, issue, lineno, pos, descr')


# =====================================================================
# utils
# =====================================================================


def enter_pdb():
    from pdb import set_trace as st  # trick GIT commit hook
    sys.stdin = open('/dev/tty')
    st()


def read_lines(fname):
    with open(fname, 'rt') as f:
        return f.readlines()


def read_line(fname, lineno):
    with open(fname, 'rt') as f:
        for i, line in enumerate(f, 1):
            if i == lineno:
                return line
    raise ValueError("lineno too big")


def write_file(fname, newlines):
    with tempfile.NamedTemporaryFile('wt', delete=False) as f:
        for line in newlines:
            f.write(line)
    shutil.move(f.name, fname)


# =====================================================================
# handlers
# =====================================================================


def handle_f401(fname, lineno):
    """ This is 'module imported but not used'
    Able to handle this case:
        import os

    ...but not this:
        from os import listdir
    """
    line = read_line(fname, lineno).strip()
    if line.startswith('import '):
        return True
    else:
        mods = line.partition(' import ')[2]  # everything after import
        return ',' not in mods


# =====================================================================
# converters
# =====================================================================


def remove_lines(fname, entries):
    """Check if we should remove lines, then do it.
    Return the numner of lines removed.
    """
    to_remove = []
    for entry in entries:
        msg, issue, lineno, pos, descr = entry
        # 'module imported but not used'
        if issue == 'F401' and handle_f401(fname, lineno):
            to_remove.append(lineno)
        # 'blank line(s) at end of file'
        elif issue == 'W391':
            lines = read_lines(fname)
            i = len(lines) - 1
            while lines[i] == '\n':
                to_remove.append(i + 1)
                i -= 1
        # 'too many blank lines'
        elif issue == 'E303':
            howmany = descr.replace('(', '').replace(')', '')
            howmany = int(howmany[-1])
            for x in range(lineno - howmany, lineno):
                to_remove.append(x)

    if to_remove:
        newlines = []
        for i, line in enumerate(read_lines(fname), 1):
            if i not in to_remove:
                newlines.append(line)
        print("removing line(s) from %s" % fname)
        write_file(fname, newlines)

    return len(to_remove)


def add_lines(fname, entries):
    """Check if we should remove lines, then do it.
    Return the numner of lines removed.
    """
    EXPECTED_BLANK_LINES = (
        'E302',  # 'expected 2 blank limes, found 1'
        'E305')  # ìexpected 2 blank lines after class or fun definition'
    to_add = {}
    for entry in entries:
        msg, issue, lineno, pos, descr = entry
        if issue in EXPECTED_BLANK_LINES:
            howmany = 2 if descr.endswith('0') else 1
            to_add[lineno] = howmany

    if to_add:
        newlines = []
        for i, line in enumerate(read_lines(fname), 1):
            if i in to_add:
                newlines.append('\n' * to_add[i])
            newlines.append(line)
        print("adding line(s) to %s" % fname)
        write_file(fname, newlines)

    return len(to_add)


# =====================================================================
# main
# =====================================================================


def build_table():
    table = defaultdict(list)
    for line in sys.stdin:
        line = line.strip()
        if not line:
            break
        fields = line.split(':')
        fname, lineno, pos = fields[:3]
        issue = fields[3].split()[0]
        descr = fields[3].strip().partition(' ')[2]
        lineno, pos = int(lineno), int(pos)
        table[fname].append(ntentry(line, issue, lineno, pos, descr))
    return table


def main():
    table = build_table()

    # remove lines (unused imports)
    removed = 0
    for fname, entries in table.items():
        removed += remove_lines(fname, entries)
    if removed:
        print("%s lines were removed from some file(s); please re-run" %
              removed)
        return

    # add lines (missing \n between functions/classes)
    added = 0
    for fname, entries in table.items():
        added += add_lines(fname, entries)
    if added:
        print("%s lines were added from some file(s); please re-run" %
              added)
        return


main()
