#!/usr/bin/env python

import getopt
import sys
from optparse import OptionParser

name = 'bignum'
version = '1.0'

class BigNum(object):

	si_mapping = {
                None: "doesn't have a unit",
                1: ((u'd', "deca"),  (u'd', "deci")),
		2: ((u'h', "hecto"), (u'c', "centi")),
		3: ((u'k', "kilo"),  (u'm', "milli")),
		6: ((u'M', "Mega"),  (u'\u00B5', "micro")),
		9: ((u'G', "Giga"),  (u'n', "nano")),
		12: ((u'T', "Tera"),  (u'p', "pico")),
		15: ((u'P', "Peta"),  (u'f', "femto")),
		18: ((u'E', "Exa"),   (u'a', "atto")),
		21: ((u'Z', "Zetta"), (u'z', "zepto")),
		24: ((u'Y', "Yotta"), (u'y', "yocto")),
                u'd': (('deca', 'deci'), (1, -1)),
                u'h': (('hecto',), (2)),
                u'c': (('centi',), (-2,)),
                u'k': (('kilo',), (3,)),
                u'm': (('milli',), (-3,)),
                u'M': (('Mega',), (6,)),
                u'\u00B5': (('micro',), (-6,)),
                u'G': (('Giga',), (9,)),
                u'n': (('nano',), (-9,)),
                u'T': (('Tera',), (12,)),
                u'p': (('pico',), (-12,)),
                u'P': (('Peta',), (15,)),
                u'f': (('femto',), (-15,)),
                u'E': (('Exa',), (18,)),
                u'a': (('atto',), (-18,)),
                u'Z': (('Zetta',), (21,)),
                u'z': (('zepto',), (-21,)),
                u'Y': (('Yotta',), (24,)),
                u'y': (('yocto',), (-24,)),
                u'deca': u'd',
                u'deci': u'd',
                u'hecto': u'h',
                u'centi': u'c',
                u'kilo': u'k',
                u'milli': u'm',
                u'Mega': u'M',
                u'micro': u'\u00B5',
                u'Giga': u'G',
                u'nano': u'n',
                u'Tera': u'T',
                u'pico': u'p',
                u'Peta': u'P',
                u'femto': u'f',
                u'Exa': u'E',
                u'atto': u'a',
                u'Zetta': u'Z',
                u'zepto': u'z',
                u'Yotta': u'Y',
                u'yocto': u'y',
		}

	def __init__(self, num=0, size=0, unit='', closest=False):
		self.num = num
		self.size = size
		self.unit = unit
		self.closest = closest

	def lookup(self, exp, sign='+'):
                if abs(int(exp)) == 0:
                    return None #self.si_mapping[None]
		if sign == '-':
			return self.si_mapping[exp][1]
		return self.si_mapping[exp][0]
                
        def lookup_full(self, key, sign=None):
            try:
                key = int(key)
            except ValueError:
                pass
            if key not in self.si_mapping:
                raise ValueError, 'Unknown unit'
            if isinstance(key, int):
                exp = key
                out = []
                if sign in ('+', '-'):
                    abbr, name = self.lookup(key, sign)
                    out.append((exp, abbr, name))
                else:
                    abbr, name = self.lookup(key, '+')
                    out = [(exp, abbr, name)]
                    abbr, name = self.lookup(key, '-')
                    out.append((exp, abbr, name))
                for exp, abbr, name in out:
                    print "10^%i has the name '%s' and the symbol '%s'" % (exp, name, abbr)
                return    
            elif issubclass(type(key), basestring):
                if len(key) == 1:
                    abbr = key
                    if key == 'd':
                        if sign == '+':
                            exp = self.si_mapping['d'][1][0]
                            name = self.si_mapping['d'][0][0]
                        elif sign == '-':
                            exp = self.si_mapping['d'][1][1]
                            name = self.si_mapping['d'][0][1]
                        else:    
                            print """'d' is either the symbol for 'deca', \
10^1, or the symbol for 'deci', 10^-1"""
                            return
                    else:
                        exp = self.si_mapping[key][1][0]
                        name = self.si_mapping[key][0][0]
                    print "'%s' is the symbol for '%s', a number with %i digits" % (abbr, name, exp)
                    return
                elif len(key) > 1:
                    name = key
                    if key == 'deca':
                        exp = self.si_mapping['d'][1][0]
                    elif key == 'deci':
                        exp = self.si_mapping['d'][1][1]
                    else:
                        abbr = self.si_mapping[key]
                        exp = self.si_mapping[abbr][1][0]
                    print "'%s' has the symbol '%s' and is a number with %i digits" % (name, abbr, exp)    
                    return
        
	def bignum(self, num=0, size=1):
		num = num or self.num
		size = size or self.size

		snum = "%e" % num
		spec, exp = snum.split('e')
		sign, exp = exp[0], int(exp[1:])
		_, rest = divmod(exp, 3)
		exp = exp - rest
		si = self.lookup(exp, sign)
		left, right = spec.split('.')
		if rest:
			left = left + right[:rest]
			right = right[rest:]
			if len(left) == 3 and self.closest:
				si = self.lookup(exp+3, sign)
				left = '0.'+left+right[:size]
				left = left.rstrip('0')
				size = 0
		if size:
			pp = "%s.%s" % (left, right[:size])
		else:
			pp = left
		return pp, si

	def pprint(self, num=0, size=1, unit='', p=True):
		num = num or self.num
		pp, si = self.bignum(num, size)
		outstring = "%s%s %s" % (pp, si[0], unit)
		if p:
			print outstring
		return outstring

def usage():
	print """%(name)s %(version)s

Usage: %(name)s [options] <big number>

	Options:
	-h
	--help			Show this help and exit
	-c
	--closest		Round off to the nearest SI unit	
	-s <significant digits>
	--sign=<significant digits>
				Number of significant digits
	-u <unit>
	--unit <unit>		SI unit (m, kg, l...)s
        -v
        --version               Print version and exit

	Examples:
	%(name)s 500000 -> 500k
	%(name)s -c 500000 -> 0.5M
	%(name)s -s 2 7870977786795764 -> 7.87P
	%(name)s -u kg 9999999999999999 -> 10Pkg
""" % {'name': name, 'version': version}

if __name__ == '__main__':
        op = OptionParser(version="%%prog %s" % version)
        op.add_option("-c", "--closest", action="store_true",
            dest="closest", default=False,
            help="round off to the nearest SI unit")
        op.add_option("-s", "--sign", type="int", dest="size",
            help="number of significant digits")
        op.add_option("-u", "--unit", dest="unit",
            help="SI unit (m, kg, l...)")
        op.add_option("-l", "--lookup", dest="lookup",
            help="lookup an SI-unit")
        try:
		opts, args = getopt.getopt(sys.argv[1:], "chvl:u:s:", ["closest", "help", "unit=", "sign=", "lookup="])
	except getopt.GetoptError:
                print 'foo'
		usage()
		sys.exit(2)
	num = 3.1415
	size = 0
	unit = ''
	closest = False
        lookup = False
	for o, a in opts:
		if o in ('-h', '--help'):
			usage()
			sys.exit()
		if o in ('-v', '--version'):
                        print name, version
			sys.exit()
		if o in ('-c', '--closest'):
			closest = True
		if o in ('-u', '--unit'):
			unit = a
		if o in ('-l', '--lookup'):
			lookup = a
		if o in ('-s', '--sign'):
			try:
				size = int(a)
			except ValueError:
				print "'%s' cannot be interpreted as an integer" % a

        if lookup:
            bn = BigNum()
            bn.lookup_full(lookup)
            sys.exit()

	if not args:
		usage()
		sys.exit(2)

	bn = BigNum(num, size, unit, closest)
	for num in args:
		try:
			num = long(num)
		except ValueError:
			try:
				num = float(num)
			except ValueError:
				print "'%s' cannot be interpreted as a number" % num
				sys.exit(255)
		print bn.pprint(num, size, unit, False)
