#!/usr/bin/env python3.2

import sys
import argparse
import requests
import json
import hashlib
import os

json._default_encoder = json.JSONEncoder(
  skipkeys=False,
  ensure_ascii=True,
  check_circular=True,
  allow_nan=True,
  indent=None,
  separators=(',', ':'),
  default=None,
)

server_list = []
new_cert = dict(filename="", submissions=dict(), blocks=dict(), master_blocks=dict())

class TimestampServer:
  def __init__(self, api_url):
    self.api_url = api_url

  def _api_url(self, path):
    return self.api_url + path

  def _api_timestamps_new(self):
    return self.api_url + '/timestamps/new'

  def _api_timestamps(self):
    return self.api_url + '/timestamps'
    
  def _api_timestamp(self, id):
    return self.api_url + "/timestamps/%d" % id
    
  def _api_timestamp_by_hash(self, hash):
    return self.api_url + "/timestamps/%s" % hash
    
  def _api_block(self, id):
    return self.api_url + "/blocks/%d" % id
    
  def _api_master_block(self, id):
    return self.api_url + "/master_blocks/%d" % id

  def prepare_submission(self):
    try:
      r = requests.get(self._api_timestamps_new())
      prepare_data = json.loads(r.text)
      return prepare_data['master_id'], prepare_data['salt']
    except ValueError:
      print("Could not communicate with server correctly. Do you have a valid url to a timestamp server?")
      exit()
    

  def submit_timestamp(self, hash, master_id):
    r = requests.post(self._api_timestamps(), 
      data={'hash': hash, 'master_block_id': master_id})
    res = json.loads(r.text)
    if 'error' in res:
      print("Submission failed: %s" % response['error'])
      return res
    else:
      print(r.text)
      return res["timestamp"]

    
  def get_timestamp(self, id):
    r = requests.get(self._api_timestamp(id))
    res = json.loads(r.text)
    return res['timestamp']
    
  def get_timestamp_by_hash(self, hash):
    r = requests.get(self._api_timestamp_by_hash(hash))
    res = json.loads(r.text)
    return res['timestamp']
    
  def get_block(self, id):
    r = requests.get(self._api_block(id))
    res = json.loads(r.text)
    return res['block']
    
  def get_master_block(self, id):
    r = requests.get(self._api_master_block(id))
    res = json.loads(r.text)
    return res['master_block']


class TimestampRequest:
  def __init__(self, file = None):
    if file is not None:
      self.encoded_data = file.read().encode()
  
  def make_hash(self, data, salt):
    hasher = hashlib.sha512()
    hasher.update(salt)
    hasher.update(data)
    return hasher.hexdigest()
  
  def submit_to(self, server):
    master_id, salt = server.prepare_submission()
    hash = self.make_hash(self.encoded_data, salt.encode())
    res = server.submit_timestamp(hash, master_id)
    res["salt"] = salt
    res["server"] = server.api_url
    return res
    
  def fetch_timestamp(self, id, server):
    serv = TimestampServer(server)
    master_id, salt = serv.prepare_submission()
    ts = serv.get_timestamp(id);
    ts["salt"] = salt
    ts["server"] = serv.api_url
    return ts
    
  def fetch_block(self, id, server):
    serv = TimestampServer(server)
    return serv.get_block(id);
    
  def fetch_master_block(self, id, server):
    serv = TimestampServer(server)
    return serv.get_master_block(id);

class Certificate:
  def __init__(self, file):
      self.cert_file = file

  def check_cert(self, filename=None):
    self.cert = None
    if os.path.isfile(self.cert_file):
      try:
        f = open(self.cert_file, "r+")
        self.cert = json.loads(f.read())
        if self.cert['filename'] != filename and filename is not None:
          ok = input("Warning! The certificate file you specified has another filename in it. Are you sure you want to proceed? (y/n): ")
          print(ok)
          if not ok == "y" and not ok == "Y":
            print("Exiting!")
            exit()
        f.close()
      except ValueError:
        print("Malformed Certificate File! Exiting!")
        exit()
    else:
      if filename is not None:
        self.cert = new_cert
        self.cert["filename"] = filename
      else:
        print("Could not find cert file: %s" % self.cert_file)
        exit()
  
  def update_cert(self):
    f = open(self.cert_file, "w")
    f.write(json.dumps(self.cert))
    f.close()
    
  def add_master_block(self, key, master_block):
    self.cert["master_blocks"][key] = master_block
    
  def add_block(self, key, block):
    self.cert["blocks"][key] = block

def do_submit(args):
  cert_file = args.cert or args.file.name + '.tscert'
  cert = Certificate(cert_file)
  cert.check_cert(args.file.name)
  request = TimestampRequest(args.file)
  for server in server_list:
    res = request.submit_to(server)
    if not 'error' in res:
      cert.cert["submissions"][server._api_timestamp(res["id"])] = res
      master_id = res["master_block_id"]
      mblk = request.fetch_master_block(master_id, server.api_url)
      cert.add_master_block(server._api_master_block(master_id), mblk)
  cert.update_cert()
  
def load_config():
  global server_list
  home = os.getenv('USERPROFILE') or os.getenv('HOME')
  config_file = home + '/.ftsconfig'
  if os.path.isfile(config_file):
    try:
      f = open(config_file)
      config = json.loads(f.read())
      f.close()
      for server in config['Servers']:
        server_list.append(TimestampServer(server))
    except ValueError:
      print("Could not load config file! Malformed JSON!")
      exit()
  else:
    f = open(config_file, 'w')
    f.write("{\n\"Servers\": \n[\"http://fts-ruby.herokuapp.com/api/v1\"]\n}")
    f.close()
    server_list = [TimestampServer("http://fts-ruby.herokuapp.com/api/v1")]

#def do_certificate(args):

def do_verify(args):
  cert_file = args.cert or args.file.name + '.tscert'
  cert = Certificate(cert_file)
  cert.check_cert()
  for submission in cert.cert["submissions"]:
    verify_submission(cert.cert["submissions"][submission], cert)
  cert.update_cert()

def verify_submission(submission, cert):
  req = TimestampRequest()
  server = TimestampServer(submission["server"])
  master_id = submission["master_block_id"]
  mb = req.fetch_master_block(master_id, submission["server"])
  mb_key = server._api_master_block(master_id)
  cert.add_master_block(mb_key, mb)
  submission_id = submission["id"]
  ts = req.fetch_timestamp(submission_id, submission["server"])
  ts_key = server._api_timestamp(submission_id)
  cert.cert["submissions"][ts_key] = ts
  if ts["block_id"] != "null":
    blk = req.fetch_block(ts["block_id"], submission["server"])
    blk_key = server._api_block(blk["id"])
    cert.add_block(blk_key, blk)
    print("This certificate has complete data for a submition...")
  
  

# setup command line parser
parser = argparse.ArgumentParser(prog='fts')
parser.add_argument('-c', '--cert')
parser.add_argument('-s', '--server')
cmdparsers = parser.add_subparsers()

# 'submit' command parser
parser_submit = cmdparsers.add_parser('submit')
parser_submit.add_argument('file', type=argparse.FileType('r'))
parser_submit.set_defaults(func=do_submit)

# 'verify' command parserserver_list = [TimestampServer('http://fts-ruby.herokuapp.com')]
parser_verify = cmdparsers.add_parser('verify')
parser_verify.add_argument('file', type=argparse.FileType('r'))
parser_verify.set_defaults(func=do_verify)

# do it live!
if __name__ == "__main__":
  load_config()  
  args = parser.parse_args()
  if args.server is not None:
    server_list = [TimestampServer(args.server)]
  sys.exit( args.func(args) )
