#!/usr/bin/env python
'''stash is a utility for operating on files across many folders.
'''
import json
import os
import shutil
import sys

STASH_FILE = '.stash'

def __stash_file_path():
    return os.path.expanduser(os.path.join('~', STASH_FILE))

def __ensure_stash_file_exists():
    if not os.path.exists(__stash_file_path()):
        with open(__stash_file_path(), 'w') as f:
            f.write('{}')

def __update(func):
    with open(__stash_file_path(), 'r+') as f:
        try:
            data = json.load(f)
        except ValueError:
            sys.stderr.write('Stash file corrupted. Delete this file to continue: %s\n' % __stash_file_path())
            return 1

        data = func(data)
        f.seek(0)
        json.dump(data, f, sort_keys=True, indent=4, separators=(',', ': '))
        f.truncate()
    return 0

def stash(*filenames):
    filenames = map(os.path.abspath, filenames)

    for filename in filenames:
        if not os.path.exists(filename):
            sys.stderr.write('File not found: %s\n' % filename)
            return 1

    def f(data):
        print 'Adding files:', '\n\t'.join(filenames)
        if 'files' not in data:
            data['files'] = []
        data['files'].extend(filenames)
        return data

    return __update(f)

def pop():
    def f(data):
        if 'files' not in data or len(data['files']) == 0:
            sys.stderr.write('Error: Stash empty. Cannot pop.\n')
            return data
        print data['files'].pop()
        return data

    return __update(f)

def move_all(target='.'):
    return move(target, True)

def move(target='.', move_all=False):
    def f(data):
        if 'files' not in data or len(data['files']) == 0:
            sys.stderr.write('Error: Stash empty. Cannot pop.\n')
            return data
        while len(data['files']):
            file = data['files'].pop()
            t = os.path.join(target, os.path.basename(file))
            os.rename(file, t)
            print >>sys.stderr, 'Moved from %s to %s' % (file, t)
            if not move_all:
                break
        return data

    if not os.path.isdir(target):
        print >>sys.stderr, 'Error, target is not a directoy:', target
        return 1

    return __update(f)

def copy_all(target='.'):
    return __copy(True, target)

def copy(target='.'):
    return __copy(False, target)

def __copy(copy_all, target='.'):
    def f(data):
        if 'files' not in data or len(data['files']) == 0:
            sys.stderr.write('Error: Stash empty. Cannot pop.\n')
            return data

        while len(data['files']):
            file = data['files'].pop()
            t = os.path.join(target, os.path.basename(file))
            if os.path.isdir(file):
                shutil.copytree(file, t)
                print >>sys.stderr, 'Copied dir from %s to %s' % (file, t)
            else:
                shutil.copy2(file, t)
                print >>sys.stderr, 'Copied file from %s to %s' % (file, t)

            if not copy_all:
                break

        return data

    if not os.path.isdir(target):
        print >>sys.stderr, 'Error, target is not a directoy:', target
        return 1

    return __update(f)

def list():
    def f(data):
        if 'files' not in data or len(data['files']) == 0:
            return data
        for f in data['files'][::-1]:
            print f
        return data

    return __update(f)

USAGE = '''\
Usage: stash [command] [file]
If command is omitted 'stash' is assumed
Commands:
   stash [file]:    save given file at the top of the stash
   pop:             print the file at the top of the stash. remove it from the
                    stash.
   cp [target]:     copy the file at the top of the stash to given target dir.
                    remove it from the stash.
                    if target dir is omitted, current directory is assumed.
   cpall [target]:  copy all the  files in the stash to given target dir then
                    clear stash.
                    if target dir is omitted, current directory is assumed.
   mv [target]:     move the file at the top of the stash to given target dir.
                    remove it from the stash.
                    if target dir is omitted, current directory is assumed.
   ls:              list all files in the stash, in reverse insertion order.
'''

def usage():
    print >>sys.stderr, USAGE


COMMANDS = {
        'stash':    stash,
        'p':        pop,
        'pop':      pop,
        'cp':       copy,
        'cpall':    copy_all,
        'mv':       move,
        'mvall':    move_all,
        'ls':       list,
        }

def main(args):
    __ensure_stash_file_exists()

    if len(args) == 0:
        usage()
        return 1

    cmd = args[0]
    if cmd in COMMANDS:
        return COMMANDS[cmd](*args[1:])

    if os.path.exists(cmd):
        return stash(*args[0:])

    print >>sys.stderr, 'Unrecognized command:', cmd
    usage()
    return 1

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))

