#!/usr/bin/env python
from __future__ import print_function
import os, sys
import argparse
import functools
import shlex

from quickfind.Searcher import Searcher, Ranker, CursesPrinter
import quickfind.Console as Console
from quickfind.source.DirectorySource import DirectorySource, ranker, dirFormatter
from quickfind.source.CtagsSource import CtagsSource, CtagsRanker, CtagsFormatter
from quickfind.source.Util import rec_dir_up,StringRanker, simpleFormatter

class Runner(object):
    def __init__(self, args):
        self.rows, self.columns = Console.getDims()
        self.args = args

    def find(self):
        raise NotImplementedError()

class DirRun(Runner):

    def find(self):

        git_ignore = self.args.g
        if self.args.fileType == 'all':
            ds = DirectorySource(ignore_directories=False, 
                    ignore_files=False, git_ignore=git_ignore)
        elif self.args.fileType == 'files':
            ds = DirectorySource(ignore_files=False, git_ignore=git_ignore)
        else:
            ds = DirectorySource(ignore_directories=False, git_ignore=git_ignore)

        items = ds.fetch()

        sr = ranker(self.args.p)
        s = Searcher(sr, CursesPrinter(dirFormatter))
        try:
            found = s.run(items)
        except KeyboardInterrupt:
            sys.exit(0)

        if found is not None:
            found = os.path.join(found.dir, found.name)
        return found 

class StdinRun(Runner):
    def fetch(self):
        results = []
        for i,line in enumerate(sys.stdin):
            results.append((i, line.strip()))

        # reopen stdin
        f = open("/dev/tty")
        os.dup2(f.fileno(), 0)
        return results

    def find(self):

        items = self.fetch()
        sr = StringRanker.new(get_part=lambda _, x: x[1])
        s = Searcher(sr, CursesPrinter(lambda x,q,d: simpleFormatter(x[1], q,d)))
        try:
            found = s.run(items)
        except KeyboardInterrupt:
            sys.exit(0)

        if found is not None:
            return found[1]

class CtagsRun(Runner):

    def find(self):
        ctagsFile = self.find_ctag_file()
        if ctagsFile is None:
            print("Could not find ctags file")
            sys.exit(1)

        items = CtagsSource(ctagsFile).fetch()
        s = Searcher(CtagsRanker, CursesPrinter(CtagsFormatter(self.columns)))
        try:
            found = s.run(items)
        except KeyboardInterrupt:
            sys.exit(0)

        if found is not None:
            if self.args.f == DEFAULT_COMMAND and found.pattern.isdigit():
                self.args.f = '%s +{2} {1}' % os.environ['EDITOR']
            else:
                self.args.f = '{1}'

            return "%s %s" % (found.file, found.pattern)

    def find_ctag_file(self):
        directory = os.getcwd()
        for dir in rec_dir_up(directory):
            path = os.path.join(dir, "tags")
            if os.path.isfile(path):
                return path

        return None

DEFAULT_COMMAND = "%s {0}" % os.environ['EDITOR']

def build_arg_parser():
    parser = argparse.ArgumentParser(description='A Fuzzy-finder for the terminally inclined')
    parser.add_argument('-c', 
            action="store_true", help='Ctags quickfind')
    parser.add_argument('-g', 
            action="store_false", help='Do not filter out files in .gitignore')
    parser.add_argument('-p', 
            action="store_true", help='Match also on path')
    parser.add_argument('-f', default=DEFAULT_COMMAND,
            help="Format of the output.  When the '-o' flag is provided, defaults to " \
                 "'{1}'.  When File mode is specified, executes '$EDITOR {0}'.")
    parser.add_argument('-', dest='stdin',
            action="store_true", help="Reads from stdin.")
    parser.add_argument('-D', dest='delim', default=None,
            help="Delimiter for {}-style parsing.")
    parser.add_argument('-o', dest='stdout', action="store_true",
            help="Writes the selection to stdout")

    group = parser.add_mutually_exclusive_group()
    group.add_argument('-d', action="store_const", dest="fileType", const="dirs",
                        help='Directories only.')
    group.add_argument('-a', action="store_const", dest="fileType", const="all",
                        help='Both files and directories.')
    parser.set_defaults(fileType="files")

    return parser.parse_args()

def main():
    args = build_arg_parser()
    if args.stdout and args.f == DEFAULT_COMMAND:
        args.f = "{0}"

    if args.c:
        runner = CtagsRun(args)
    elif args.stdin:
        runner = StdinRun(args)
    else:
        runner = DirRun(args)

    item = runner.find()
    if item is not None:
        formatted = args.f.format(item, *item.split(args.delim))
        if args.stdout:
            print(formatted)
        else:
            pieces = shlex.split(formatted)
            os.execvp(pieces[0], pieces)      

if __name__ == '__main__':
    main()
