#!/usr/bin/env python

from __future__ import print_function

import optparse
import jwt
import sys
import json
import time

__prog__ = 'jwt'
__version__ = '0.1'


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():
        # Try to decode
        try:
            if not sys.stdin.isatty():
                token = sys.stdin.read()
            else:
                token = arguments[0]
            token = token.encode('utf-8')
            valid_jwt = jwt.header(token)
            if valid_jwt:
                try:
                    print(json.dumps(jwt.decode(token, key=options.key, verify=options.verify)))
                    sys.exit(0)
                except jwt.DecodeError as e:
                    print(e)
                    sys.exit(1)
        except jwt.DecodeError:
            pass

        # 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:
            print(jwt.encode(payload, key=options.key, algorithm=options.algorithm))
            sys.exit(0)
        except Exception as e:
            print(e)
            sys.exit(1)
    else:
        p.print_help()

if __name__ == '__main__':
     main()
