#!/usr/bin/env python

from __future__ import print_function

import optparse
import sys
import json
import time
import jwt

__prog__ = 'jwt'
__version__ = '1.0.0'


def fix_optionparser_whitespace(input):
    """
    Hacks around whitespace hypersensitivity in OptionParser
    """
    newline = ' ' * 80
    doublespace = '\033[8m.\033[0m' * 2
    return input.replace('  ', doublespace).replace('\n', newline)


def main():
    """Encodes or decodes JSON Web Tokens based on input

Decoding examples:

  %prog --key=secret json.web.token
  %prog --no-verify json.web.token

Encoding requires the key option and takes space separated key/value pairs
separated by equals (=) as input. Examples:

  %prog --key=secret iss=me exp=1302049071
  %prog --key=secret foo=bar exp=+10

The exp key is special and can take an offset to current Unix time.
"""
    p = optparse.OptionParser(
        description=fix_optionparser_whitespace(main.__doc__),
        prog=__prog__,
        version='%s %s' % (__prog__, __version__),
        usage='%prog [options] input'
    )

    p.add_option(
        '-n', '--no-verify',
        action='store_false',
        dest='verify',
        default=True,
        help='ignore signature verification on decode'
    )

    p.add_option(
        '--key',
        dest='key',
        metavar='KEY',
        default=None,
        help='set the secret key to sign with'
    )

    p.add_option(
        '--alg',
        dest='algorithm',
        metavar='ALG',
        default='HS256',
        help='set crypto algorithm to sign with. default=HS256'
    )

    options, arguments = p.parse_args()

    if len(arguments) > 0 or not sys.stdin.isatty():
        if len(arguments) == 1 and ( options.verify is False or options.key is not None ):
            # Try to decode
            try:
                if not sys.stdin.isatty():
                    token = sys.stdin.read()
                else:
                    token = arguments[0]

                token = token.encode('utf-8')
                data = jwt.decode(token, key=options.key, verify=options.verify)

                print(json.dumps(data))
                sys.exit(0)
            except jwt.DecodeError as e:
                print(e)
                sys.exit(1)

        # Try to encode
        if options.key is None:
            print('Key is required when encoding. See --help for usage.')
            sys.exit(1)

        # Build payload object to encode
        payload = {}

        for arg in arguments:
            try:
                k, v = arg.split('=', 1)

                # exp +offset special case?
                if k == 'exp' and v[0] == '+' and len(v) > 1:
                    v = str(int(time.time()+int(v[1:])))

                # Cast to integer?
                if v.isdigit():
                    v = int(v)
                else:
                    # Cast to float?
                    try:
                        v = float(v)
                    except ValueError:
                        pass

                # Cast to true, false, or null?
                constants = {'true': True, 'false': False, 'null': None}

                if v in constants:
                    v = constants[v]

                payload[k] = v
            except ValueError:
                print('Invalid encoding input at {}'.format(arg))
                sys.exit(1)

        try:
            token = jwt.encode(
                payload,
                key=options.key,
                algorithm=options.algorithm
            )

            print(token)
            sys.exit(0)
        except Exception as e:
            print(e)
            sys.exit(1)
    else:
        p.print_help()

if __name__ == '__main__':
    main()
