#!/usr/bin/env python
"""
Name:
    gist

Usage:
    gist list
    gist edit <id>
    gist info <id>
    gist files <id>
    gist delete <id>
    gist archive <id>
    gist content <id>
    gist create <desc> [--public] [FILES ...]
    gist clone <id> [<name>]

Description:
    This program provides a command line interface for interacting with github
    gists.

Commands:
    create
        Create a new gist. A gist can be created in several ways. The content of
        the gist can be piped to the gist,

            $ echo "this is the content" | gist create "gist description"

        The gist can be created from an existing set of files,

            $ gist create "gist description" foo.txt bar.txt

        The gist can be created on the fly,

            $ gist create "gist description"

        which will open the users default editor.

    edit
        You can edit your gists directly with the 'edit' command. This command
        will clone the gist to a temporary directory and open up the default
        editor (defined by the EDITOR environment variable) to edit the files in
        the gist. When the editor is exited the user is prompted to commit the
        changes, which are then pushed back to the remote.

    list
        Returns a list of your gists. The gists are returned as,

            2b1823252e8433ef8682 - mathematical divagations
            a485ee9ddf6828d697be - notes on defenestration
            589071c7a02b1823252e + abecedarian pericombobulations

        The first column is the gists unique identifier; The second column
        indicates whether the gist is public ('+') or private ('-'); The third
        column is the description in the gist, which may be empty.

    clone
        Clones a gist to the current directory. This command will clone any gist
        based on its unique identifier (i.e. not just the users) to the current
        directory.

    delete
        Deletes the specified gist.

    files
        Returns a list of the files in the specified gist.

    archive
        Downloads the specified gist to a temporary directory and adds it to a
        tarball, which is then moved to the current directory.

    content
        Writes the content of each file in the specified gist to the terminal,
        e.g.

            $ gist content c971fca7997aed65ddc9
            foo.txt:
            this is foo


            bar.txt:
            this is bar


        For each file in the gist the first line is the name of the file
        followed by a colon, and then the content of that file is written to the
        terminal.

    info
        This command provides a complete dump of the information about the gist
        as a JSON object. It is mostly useful for debugging.

"""

import docopt
import fcntl
import os
import struct
import sys
import tempfile
import termios

import json

from gist import GistAPI

try:
    import configparser
except ImportError:
    import ConfigParser as configparser


def terminal_width():
    """Returns the terminal width

    Tries to determine the width of the terminal. If there is no terminal, then
    None is returned instead.

    """
    try:
        h, w, hp, wp = struct.unpack('HHHH',
            fcntl.ioctl(0, termios.TIOCGWINSZ,
            struct.pack('HHHH', 0, 0, 0, 0)))
        return w
    except Exception:
        pass


def elide(txt, width=terminal_width()):
    """Elide the provided string

    The string is elided to the specified width, which defaults to the width of
    the terminal.

    Arguments:
        txt: the string to potentially elide
        width: the maximum permitted length of the string

    Returns:
        A string that is no longer than the specified width.

    """
    try:
        if len(txt) > width:
            return txt[:width - 3] + '...'
    except Exception:
        pass
    return txt


def alternative_editor(default):
    """Return the path to the 'alternatives' editor

    Argument:
        default: the default to use if the alternatives editor cannot be found.

    """
    if os.path.exists('/usr/bin/editor'):
        return '/usr/bin/editor'

    return default


def environment_editor(default):
    """Return the user specified environment default

    Argument:
        default: the default to use if the environment variable contains nothing
            useful.

    """
    editor = os.environ.get('EDITOR', '').strip()
    if editor != '':
        return editor

    return default


def configuration_editor(config, default):
    """Return the editor in the config file

    Argument:
        default: the default to use if there is no editor in the config

    """
    try:
        return config.get('gist', 'editor')
    except configparser.NoOptionError:
        return default


def main(argv=sys.argv[1:]):
    args = docopt.docopt(__doc__, argv=argv, version='gist-v0.1.0')

    # Read in the configuration file
    config = configparser.ConfigParser()
    with open(os.path.expanduser('~/.gist')) as fp:
        config.readfp(fp)

    # Determine the editor to use
    editor = None
    editor = alternative_editor(editor)
    editor = environment_editor(editor)
    editor = configuration_editor(config, editor)

    if editor is None:
        raise ValueError('Unable to find an editor.')

    token = config.get('gist', 'token')
    gist = GistAPI(token=token, editor=editor)

    if args['list']:
        gists = gist.list()
        for info in gists:
            public = '+' if info.public else '-'
            desc = '' if info.desc is None else info.desc.encode('utf8')
            line = '{} {} {}'.format(info.id, public, desc)
            print(elide(line))
        return

    if args['info']:
        info = gist.info(args['<id>'])
        print(json.dumps(info, indent=2))
        return

    if args['edit']:
        info = gist.edit(args['<id>'])
        return

    if args['clone']:
        gist.clone(args['<id>'], args['<name>'])
        return

    if args['content']:
        content = gist.content(args['<id>'])
        for name, lines in content.items():
            print('{}:\n{}\n'.format(name, lines))
        return

    if args['files']:
        for f in gist.files(args['<id>']):
            print(f)
        return

    if args['archive']:
        gist.archive(args['<id>'])
        return

    if args['delete']:
        gist.delete(args['<id>'])
        return

    if args['create']:
        if sys.stdin.isatty():
            if args['FILES']:
                files = {os.path.basename(f): open(f).read() for f in args['FILES']}
            else:
                with tempfile.NamedTemporaryFile() as fp:
                    os.system('{} {}'.format(editor, fp.name))
                    fp.flush()
                    fp.seek(0)
                    files = {'file1.txt': fp.read()}

        else:
            files = {'file1.txt': sys.stdin.read()}

        description = args['<desc>']
        public = args['--public']
        data = {k: {'content': v} for k, v in files.items()}

        print(gist.create(description, data, public))


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        sys.stderr.write('{}\n'.format(str(e)))
        sys.stderr.flush()
        sys.exit(1)
