#!/usr/bin/env python3
import argparse,os,sys
from math import ceil
from table import Table

# default_padding[radix]=number of digits to allow for.
default_padding={
  2:8,
  8:3,
  10:3,
  16:2
}

# Set up for outputting an ASCII table.
character3=[
  'NUL','SOH','STX','ETX','EOT','ENQ','ACK','BEL',
  ' BS','TAB',' LF',' VT',' FF',' CR',' SO',' SI',
  'DLE','DC1','DC2','DC3','DC4','NAK','SYN','EBT',
  'CAN',' EM','SUB','ESC',' FS',' GS',' RS',' US'
]
for ch in range(32,127):
  character3.append('  '+chr(ch))
character3.append('DEL')

# Handle command line options.
ap=argparse.ArgumentParser(
  add_help=False,
  #usage="%(prog)s [OPTIONS] [string]",
  description="Description: Read text from standard input and/or the command line argument(s), and output the ASCII value of each character in the given or default radix."
)
r=ap.add_argument('--radix','-r',dest='radix',action='store',type=int,default=16,help="Set the base to use when giving ASCII values for characters. The valid range is from 2 to 36. (default: %(default)s)")
ap.add_argument('-b',dest='radix',action='store_const',const=2,help="Binary (--radix 2)")
ap.add_argument('-d',dest='radix',action='store_const',const=10,help="Decimal (--radix 10)")
ap.add_argument('-h',dest='radix',action='store_const',const=16,help="Hexadecimal (--radix 16)")
ap.add_argument('-o',dest='radix',action='store_const',const=8,help="Octal (--radix 8)")
ap.add_argument('--pad',dest='pad',action='store',type=int,default=0,help="Pad with leading zeros to make numeric values occupy this many characters (not counting any prefix). (default: %(default)s)")
ap.add_argument('--prefix','-p',action='store_true',default=False,help="Prefix each numeric character value with notation indicating the base being used.")
ap.add_argument('-t',dest='value_only',action='count',default=0,help="How terse would you like the output to be? -t suppresses the output each corresponding character, giving one numeric value per line. -tt gives one line with values separated by spaces. -ttt outputs all values on one line with nothing between them. (In this last case padding will be set to 2 if it's less than that.")
ap.add_argument('--table',dest='table',action='store_true',help="Rather than translating standard input and the command line into ASCII on standard output, just wright an ASCII table to standard output.")
ap.add_argument('--help',dest='help',action='store_true',default=False,help="Show this usage message.")
ap.add_argument('args',metavar='TEXT',nargs='*',help="If given, this TEXT is appended to the end of standard input (if any).")
try:
  opt=ap.parse_args()
except Exception as e:
  print(str(e), file=sys.stderr)
  raise
  sys.exit(2)
if opt.help:
  ap.print_help()
  sys.exit(0)
if opt.radix<2 or opt.radix>36:
  ap.print_help()
  sys.exit(2)
if opt.prefix:
  if opt.radix==2: opt.prefix='0b'
  elif opt.radix==8: opt.prefix='0'
  elif opt.radix==10: opt.prefix=''
  elif opt.radix==16: opt.prefix='0x'
  else: opt.prefix='%db'%opt.radix
else:
  opt.prefix=''
if opt.value_only:
  if opt.value_only==1:
    opt.delimiter='\n'
  elif opt.value_only==2:
    opt.delimiter=' '
  else:
    opt.delimiter=''
    if opt.pad<2:
      opt.pad=2

# Get the input from stdin and/or command line.
instr=''
if not sys.stdin.isatty():
  instr+=os.fdopen(sys.stdin.fileno(),'rb').read().decode()
if opt.args:
  instr+=' '.join(opt.args)

digits='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
def based_integer(n,b,prefix='',pad=0):
  """Return a string expressing the integer n in base b, optionally
  following the given prefix string."""

  s=[]
  sign=1
  if n<0:
    sign=-1
    n=-n
  elif n==0:
    s=digits[0]
  while n>0:
    s.insert(0,digits[n%b])
    n//=b

  # Now s and n completely change meaning. Keep up.
  s=''.join(s)
  n=len(s)
  if sign==-1:
    if pad>n+1:
      return f"-{prefix}{s:0>{pad-1}}"
    else:
      return f"-{s}"
  return f"{prefix}{s:0>{pad}}"

if opt.table:
  if opt.radix not in default_padding:
    sys.stderr.write("I'm really not set up to output an ASCII table with radix=%d.\n"%opt.radix)
    sys.exit(1)
  if opt.radix==2:
    columns=8
  else:
    columns=opt.radix
  headings=['']
  headings.extend([based_integer(c,opt.radix,'',default_padding[opt.radix]) for c in range(columns)])
  t=Table(colnames=headings)
  for r in range(int(ceil(128.0/columns))):
    b=r*columns # Compute the "base" position of this row.
    values=[based_integer(b,opt.radix,'',default_padding[opt.radix])]
    for c in range(columns):
      ch=b+c
      values.append(character3[ch] if ch<len(character3) else chr(ch))
    t.append(Table.Row(t,values))
  t.output()
else:
  if opt.value_only:
    print(opt.delimiter.join([
      based_integer(ord(ch),opt.radix,opt.prefix,opt.pad) for ch in instr
    ]))
  else:
    for ch in instr:
      n=based_integer(ord(ch),opt.radix,opt.prefix,opt.pad)
      print('%r: %s'%(ch,n))
