# ---File / String Search----
def grep(string, pattern):
    if not isinstance(string, str):  # -> convert input to a string
        string = repr(string)
    matching_lines = []  # Find all matching lines
    for line in string.split('\n'):
        if not fnmatch.fnmatch(string, pattern):
            continue
        matching_lines.append(line)
    return matching_lines


def correct_zeros(M):
    index_gen = iprod(*[xrange(_) for _ in M.shape])
    for index in index_gen:
        if M[index] < 1E-18:
            M[index] = 0
    return M


def print_grep(*args, **kwargs):
    matching_lines = grep(*args, **kwargs)
    print('Matching Lines:')  # Print matching lines
    print('\n    '.join(matching_lines))


def print_glob(*args, **kwargs):
    matching_fnames = glob(*args, **kwargs)
    print('Matching Fnames:')  # Print matching fnames
    print('\n    '.join(matching_fnames))


def eval_from(fpath, err_onread=True):
    'evaluate a line from a test file'
    print('[util] Evaling: fpath=%r' % fpath)
    text = read_from(fpath)
    if text is None:
        if err_onread:
            raise Exception('Error reading: fpath=%r' % fpath)
        print('[util] * could not eval: %r ' % fpath)
        return None
    return eval(text)


def read_from(fpath):
    if not checkpath(fpath):
        print('[util] * FILE DOES NOT EXIST!')
        return None
    print('[util] * Reading text file: %r ' % split(fpath)[1])
    try:
        text = open(fpath, 'r').read()
    except Exception:
        print('[util] * Error reading fpath=%r' % fpath)
        raise
    if VERY_VERBOSE:
        print('[util] * Read %d characters' % len(text))
    return text


def write_to(fpath, to_write):
    if __PRINT_WRITES__:
        print('[util] * Writing to text file: %r ' % fpath)
    with open(fpath, 'w') as file:
        file.write(to_write)


def save_pkl(fpath, data):
    with open(fpath, 'wb') as file:
        cPickle.dump(data, file)


def load_pkl(fpath):
    with open(fpath, 'wb') as file:
        return cPickle.load(file)


def save_npz(fpath, *args, **kwargs):
    print_(' * save_npz: %r ' % fpath)
    sys.stdout.flush()
    np.savez(fpath, *args, **kwargs)
    print('... success')


def load_npz(fpath):
    print('[util] load_npz: %r ' % split(fpath)[1])
    print('[util] filesize is: ' + file_megabytes_str(fpath))
    npz = np.load(fpath, mmap_mode='r + ')
    data = tuple(npz[key] for key in sorted(npz.keys()))
    #print(' * npz.keys() = %r ' + str(npz.keys()))
    npz.close()
    return data


def load_cache_npz(input_data, uid='', cache_dir='.', is_sparse=False):
    data_fpath = __cache_data_fpath(input_data, uid, cache_dir)
    cachefile_exists = checkpath(data_fpath)
    if cachefile_exists:
        try:
            print('util.load_cache> Trying to load cached data: %r' % split(data_fpath)[1])
            print('util.load_cache> Cache filesize: ' + file_megabytes_str(data_fpath))
            sys.stdout.flush()
            if is_sparse:
                with open(data_fpath, 'rb') as file_:
                    data = cPickle.load(file_)
            else:
                npz = np.load(data_fpath)
                data = npz['arr_0']
                npz.close()
            print('...success')
            return data
        except Exception as ex:
            print('...failure')
            print('util.load_cache> %r ' % ex)
            print('util.load_cache>...cannot load data_fpath=%r ' % data_fpath)
            raise CacheException(repr(ex))
    else:
        raise CacheException('nonexistant file: %r' % data_fpath)
    raise CacheException('other failure')


def save_cache_npz(input_data, data, uid='', cache_dir='.', is_sparse=False):
    data_fpath = __cache_data_fpath(input_data, uid, cache_dir)
    print('[util] caching data: %r' % split(data_fpath)[1])
    sys.stdout.flush()
    if is_sparse:
        with open(data_fpath, 'wb') as outfile:
            cPickle.dump(data, outfile, cPickle.HIGHEST_PROTOCOL)
    else:
        np.savez(data_fpath, data)
    print('...success')


#def cache_npz_decorator(npz_func):
    #def __func_wrapper(input_data, *args, **kwargs):
        #ret = npz_func(*args, **kwargs)


class CacheException(Exception):
    pass


def __cache_data_fpath(input_data, uid, cache_dir):
    hashstr_   = hashstr(input_data)
    shape_lbl  = str(input_data.shape).replace(' ', '')
    data_fname = uid + '_' + shape_lbl + '_' + hashstr_ + '.npz'
    data_fpath = join(cache_dir, data_fname)
    return data_fpath


def execstr_timeitsetup(dict_, exclude_list=[]):
    '''
    Example:
    import timeit
    local_dict = locals().copy()
    exclude_list=['_*', 'In', 'Out', 'rchip1', 'rchip2']
    local_dict = locals().copy()
    setup = util.execstr_timeitsetup(local_dict, exclude_list)
    timeit.timeit('somefunc', setup)
    '''
    old_thresh =  np.get_printoptions()['threshold']
    np.set_printoptions(threshold=1000000000)
    matches = fnmatch.fnmatch
    excl_valid_keys = [key for key in dict_.iterkeys() if not any((matches(key, pat) for pat in iter(exclude_list)))]
    valid_types = set([np.ndarray, np.float32, np.float64, np.int64, int, float])
    type_valid_keys = [key for key in iter(excl_valid_keys) if type(dict_[key]) in valid_types]
    exec_list = []
    for key in type_valid_keys:
        val = dict_[key]
        try:
            val_str = np.array_repr(val)
        except Exception:
            val_str = repr(val)  # NOQA
        exec_list.append(key + ' = ' + repr(dict_[key]))
    exec_str  = '\n'.join(exec_list)
    import_str = textwrap.dedent('''
    import numpy as np
    from numpy import array, float32, float64, int32, int64
    import util
    from spatial_verification2 import *
                                 ''')
    setup = import_str + exec_str
    np.set_printoptions(threshold=old_thresh)
    return setup



# from http://stackoverflow.com/questions/6796492/python-temporarily-redirect-stdout-stderr
class RedirectStdout(object):
    def __init__(self, lbl=None, autostart=False, show_on_exit=True):
        self._stdout_old = sys.stdout
        self.stream = cStringIO.StringIO()
        self.record = '<no record>'
        self.lbl = lbl
        self.show_on_exit = show_on_exit
        if autostart:
            self.start()

    def start(self):
        sys.stdout.flush()
        sys.stdout = self.stream

    def stop(self):
        self.stream.flush()
        sys.stdout = self._stdout_old
        self.stream.seek(0)
        self.record = self.stream.read()
        return self.record

    def update(self):
        self.stop()
        self.dump()
        self.start()

    def dump(self):
        print(indent(self.record, self.lbl))

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.stop()
        if not self.lbl is None:
            if self.show_on_exit:
                self.dump()
