#!/usr/bin/env python
from __future__ import absolute_import
import os.path
import glob
import argparse
import logging
import sys
import tempfile
from itertools import dropwhile, takewhile, chain
from functools import partial
from subprocess import check_call as _check_call

try:
    from subprocess import check_output as _check_output
except ImportError:
    import subprocess

    def _check_output(*args, **kwargs):
        process = subprocess.Popen(stdout=subprocess.PIPE, *args, **kwargs)
        output, _ = process.communicate()
        retcode = process.poll()
        if retcode:
            error = subprocess.CalledProcessError(retcode, args[0])
            error.output = output
            raise error
        return output

check_call = partial(_check_call, shell=True)
check_output = partial(_check_output, shell=True)

# Constants
DEFAULT_REQUIREMENTS_FILE = 'requirements.txt'
GLOB_PATTERNS = ('*requirements.txt', 'requirements[_-]*.txt', 'requirements/*.txt')
PIP_IGNORE_FILE = '.pipignore'
SPLIT_PATTERN = '## The following requirements were added by pip --freeze:'


class StdOutFilter(logging.Filter):
    def filter(self, record):
        return record.levelno in [logging.DEBUG, logging.INFO]


def setup_logging(verbose):
    if verbose:
        level = logging.DEBUG
    else:
        level = logging.INFO

    format = '%(message)s'

    logger = logging.getLogger('pip-dump')

    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.addFilter(StdOutFilter())
    stdout_handler.setFormatter(logging.Formatter(format))
    stdout_handler.setLevel(logging.DEBUG)

    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setFormatter(logging.Formatter(format))
    stderr_handler.setLevel(logging.WARNING)

    logger.setLevel(level)
    logger.addHandler(stderr_handler)
    logger.addHandler(stdout_handler)
    return logger


def parse_args():
    parser = argparse.ArgumentParser(
        description='Rewrites requirements.txt to match your virtualenv.'
    )
    parser.add_argument(
        '--verbose', '-v', action='store_true', default=False, help='Show more output'
    )
    parser.add_argument('files', nargs='*')
    return parser.parse_args()


def pip_partition(lines):
    no_split_match = lambda line: line != SPLIT_PATTERN
    split_match = lambda line: line == SPLIT_PATTERN
    first = takewhile(no_split_match, lines)
    second = dropwhile(split_match, dropwhile(no_split_match, lines))
    return list(first), list(second)


def pip_info(filename):
    cmd = 'pip freeze -lr {0}'.format(filename, )
    raw = check_output(cmd)
    lines = raw.split('\n')
    p = pip_partition(lines)
    return p


def append_lines(lines, filename):
    with open(filename, 'a') as f:
        for line in lines:
            f.write('{0}\n'.format(line))


def rewrite(filename, lines):
    with open(filename, 'w') as f:
        for line in sorted(lines, key=str.lower):
            line = line.strip()
            if line:
                f.write('{0}\n'.format(line))


def dump_requirements(files):
    _, tmpfile = tempfile.mkstemp()
    check_call('cat {0} | sort -fu > {1}'.format(' '.join(files), tmpfile))
    _, new = pip_info(tmpfile)
    check_call('rm {0}'.format(tmpfile))
    append_lines(new, files[0])

    for filename in files:
        if os.path.basename(filename) == PIP_IGNORE_FILE:  # never rewrite the pip ignore file
            continue
        pkgs, _ = pip_info(filename)
        rewrite(filename, pkgs)


def find_default_files():
    req_files = list(chain.from_iterable(glob.glob(pattern) for pattern in GLOB_PATTERNS))
    try:
        req_files.remove(DEFAULT_REQUIREMENTS_FILE)
    except ValueError:
        pass

    files = []
    if os.path.exists(DEFAULT_REQUIREMENTS_FILE):
        files.append(DEFAULT_REQUIREMENTS_FILE)
    files += req_files
    if os.path.exists(PIP_IGNORE_FILE):
        files.append(PIP_IGNORE_FILE)

    return files


def main():
    args = parse_args()
    logger = setup_logging(args.verbose)

    if not args.files:
        args.files = find_default_files()
    dump_requirements(args.files)


if __name__ == '__main__':
    main()
