#!/usr/bin/env python
from operator import itemgetter
import argparse
import os
import shlex
import subprocess
import sys
import time

blacklist = [
    ".git",
    ".svn"
]

trows, tcols = os.popen('stty size', 'r').read().split()
trows = int(trows)
tcols = int(tcols)


def call(cmd, silence = True):
    """Calls a shell command given 'cmd' which can also contain arguments."""
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE)
    out, err = p.communicate()
    return out

def highlight(str, match):
    """Highlights any occurrences of 'match' inside 'str' in a colour."""
    return str.replace(match, "\033[91m"+match+"\033[0m")


def is_binary_file(path):
    """Tries to work out if a file is binary or not. Uses the method file(1)
    uses."""
    with open(path, "rb") as fp:
        textchars = bytearray([7,8,9,10,12,13,27]) \
                + bytearray(range(0x20, 0x100))
        return bool(fp.read(1024).translate(None, textchars))


def search_text(path, query):
    """Searches a text file given at 'path' for some query string 'query'."""
    if is_binary_file(path):
        return []

    matches = []
    with open(path, 'r') as fp:
        ln = 1
        for line in fp.readlines():
            if query in line:
                matches.append((ln,line.strip(),line.strip().index(query)))
            ln += 1
    return matches


def search_names(query):
    """Searches for files who's name contains the string 'query'."""
    print "--- files ---"
    for root,dirs,files in os.walk("."):
        dirs[:]  = [d for d in dirs  if d not in blacklist]
        files[:] = [f for f in files if f not in blacklist]

        for file in files:
            if query in file:
                print root+"/"+highlight(file, query)


def search_files(query):
    """Searches for files who's contents contains the string 'query' and
    prints the lines that 'query' is on."""
    print "--- texts ---"
    for root,dirs,files in os.walk("."):
        dirs[:]  = [d for d in dirs  if d not in blacklist]
        files[:] = [f for f in files if f not in blacklist]

        for file in files:
            matches = search_text(root+"/"+file,query)
            if len(matches) > 0:
                print "\033[34m"+root+"/"+file+"\033[0m"
                lnl = len(str(max(matches,key=itemgetter(0))[0]))
                for ln,line,idx in matches:
                    low = max(0, idx-tcols/2)
                    high = min(len(line), low+tcols-lnl-5)
                    print ("{0: >"+str(lnl)+"} ").format(ln), \
                            highlight(line[low:high], query)


if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="Search for things.")
    ap.add_argument("query",
            nargs="+",
            type=str,
            help="What to find.")
    ap.add_argument("--filenames",
            action="store_true",
            help="Only search file names.")
    ap.add_argument("--files",
            action="store_true",
            help="Only search the contents of text files.")
    args = ap.parse_args()

    # Ignore paths in the search ignore file.
    if os.path.isfile(os.path.expanduser("~/.searchignore")):
        with open(os.path.expanduser("~/.searchignore"), "r") as fp:
            blacklist.extend([line.strip() for line in fp.readlines()])

    # If no default arguments are given, search both names and texts.
    if args.filenames == False and args.files == False:
        args.filenames, args.files = True, True

    for q in args.query:
        print "===",q,"==="
        if args.filenames:
            search_names(q)
        if args.files:
            search_files(q)

