#!/usr/bin/env python

from __future__ import absolute_import

import collections
import logging
import optparse
import os
import re
import sys

try:
    import reps
except ImportError:
    sys.path.append('.')

from reps import consts
from reps import ioutils
from reps.conf import Conf, LocalConf
from reps.model import RepoManager
import reps

logger = logging


class Program(object):
    def scan_fs(self, cwd):
        locs = []
        for relpath, dirs, files in os.walk(cwd):
            logger.debug('Scanning %s' % relpath)
            if consts.REPO_CONFIG in files:
                relpath = re.sub('^\./', '', relpath)
                locs.append(relpath)
        return sorted(locs)

    def invoke(self, bundle, recurse=False):
        cmd, args, kwargs = bundle

        locs = ['.']
        if recurse:
            locs = self.scan_fs('.')

        for loc in locs:
            try:
                oldcwd = os.getcwd()
                os.chdir(loc)
                if recurse:
                    ioutils.inform('Entering %s' % loc, major=True)
                cmd(*args, **kwargs)
            finally:
                os.chdir(oldcwd)

    def cmd_list(self, depth=None, excluded_dirs='', update=False):
        repo_manager = RepoManager()
        excluded_dirs = excluded_dirs and excluded_dirs.split(',') or []
        repo_manager.find_repos('.', max_depth=depth, excluded_dirs=excluded_dirs)

        Conf.write_config(repo_manager, filehandle=sys.stdout)
        if update:
            Conf.write_config(repo_manager, filepath=consts.REPO_CONFIG)
            ioutils.inform('Wrote %s' % consts.REPO_CONFIG)
        else:
            ioutils.suggest('Run with -u to update %s' % consts.REPO_CONFIG)

    def get_repo_manager(self, local_repos_arg=None):
        # strip off trailing / because dir names cannot contain a /
        # the / is likely there due to tab completion in the terminal
        for i, arg in enumerate(local_repos_arg):
            if len(arg) > 1 and arg.endswith('/'):
                local_repos_arg[i] = arg[:-1]

        # NOTE: If the only argument is '.' then it's an in-repo pull and we
        # rewrite local_repos_arg to the current dir and then navigate one dir
        # up the tree to find the .reconfig there.
        if len(local_repos_arg) == 1 and '.' == local_repos_arg[0]:
            local_repos_arg = [os.path.basename(os.getcwd())]
            os.chdir('..')

        try:
            repo_manager = Conf.read_config(consts.REPO_CONFIG)
        except IOError:
            ioutils.complain("Could not read: %s" % consts.REPO_CONFIG)
            sys.exit(1)

        local_repos = LocalConf.items(consts.REPO_CONFIG_LOCAL)
        if local_repos_arg:
            repo_manager.activate(local_repos_arg)
        elif local_repos:
            repo_manager.activate(local_repos)
        else:
            repo_manager.activate_all()

        return repo_manager

    def cmd_compact(self, do_compact=False, local_repos_arg=None):
        repo_manager = self.get_repo_manager(local_repos_arg=local_repos_arg)

        for repo in repo_manager.active_repos():
            repo.cmd_compact(check=not do_compact)

        if not do_compact:
            ioutils.suggest('Run with -c to compact')

    def cmd_pull(self, local_repos_arg=None):
        repo_manager = self.get_repo_manager(local_repos_arg=local_repos_arg)

        # dry run first
        clean = True
        for repo in repo_manager.active_repos():
            if not repo.is_checked_out() and os.path.exists(repo.path):
                ioutils.complain("Cannot pull '%s', path exists" % repo.path)
                clean = False

        if clean:
            to_merge = []
            for repo in repo_manager.active_repos():
                if repo.cmd_fetch():
                    to_merge.append(repo)

            for repo in to_merge:
                repo.cmd_merge()



if __name__ == '__main__':
    usage = ['%s [command]' % os.path.basename(sys.argv[0])]
    usage.append('\nCommands:')
    usage.append('  list    [-d 1] [-u]               List repositories')
    usage.append('  compact [repo1 repo2 ...] [-c]    Compact repositories')
    usage.append('  pull    [repo1 repo2 ...]         Pull repositories')
    usage = '\n'.join(usage)
    optparser = optparse.OptionParser(usage=usage)
    optparser.add_option('-c', '--compact', action='store_true', help='Perform compaction')
    optparser.add_option('-d', '--depth', action='store', type="int", help='Recurse to given depth')
    optparser.add_option('-E', '--exclude', action='store', help='Directories to exclude from scan')
    optparser.add_option('-u', '--update', action='store_true', help='Update %s' % consts.REPO_CONFIG)
    optparser.add_option('-r', '--recurse', action='store_true', help='Run command recursively')
    optparser.add_option('-v', '--verbose', action='store_true', help='Print debug output')
    optparser.add_option('-V', '--version', action='store_true', help='Print version')
    (options, args) = optparser.parse_args()

    def print_help():
        optparser.print_help()
        sys.exit(2)

    if options.version:
        print(reps.__version__)
        sys.exit(0)


    try:
        cmd = args.pop(0)
    except IndexError:
        print_help()

    def set_log_params(verbose=False):
        logfmt = '%(levelname)s: %(message)s'
        level = logging.DEBUG if verbose else logging.ERROR
        logging.basicConfig(format=logfmt, level=level)
    set_log_params(verbose=options.verbose)

    program = Program()
    if cmd == 'list':
        bundle = (program.cmd_list, [], {'depth': options.depth,
                                         'excluded_dirs': options.exclude,
                                         'update': options.update})
        program.invoke(bundle, recurse=options.recurse)
    elif cmd == 'compact':
        bundle = (program.cmd_compact, [], {'do_compact': options.compact, 'local_repos_arg': args})
        program.invoke(bundle, recurse=options.recurse)
    elif cmd == 'pull':
        bundle = (program.cmd_pull, [], {'local_repos_arg': args})
        program.invoke(bundle, recurse=options.recurse)
    else:
        print_help()
