#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import io
import datetime
import bz2
import shutil
import operator
from collections import OrderedDict
from ipaddress   import ip_address


# ###### Abort with error ###################################################
def error(logstring):
   sys.stderr.write(datetime.datetime.now().isoformat() + \
                    ' ===== ERROR: ' + logstring + ' =====\n')
   sys.exit(1)



# ###### Read input and prepare output ######################################

# Input types:
IT_NONE       = 0
IT_PING       = 1
IT_TRACEROUTE = 2

# Output types:
OT_POSTGRES   = 1
OT_MONGODB    = 2

def processInput(inputFile, outputType):
   inputType  = IT_NONE
   lineNumber = 0
   output     = {}
   hopCheck   = {}
   for inputLine in inputFile.readlines():
      lineNumber = lineNumber + 1
      tuples = inputLine.rstrip().split(' ')
      if len(tuples) > 0:
         # ====== Ping ======================================================
         if tuples[0] == '#P':
            if len(tuples) >= 7:
               # ------ Handle input ----------------------------------------
               if inputType == IT_NONE:
                  inputType = IT_PING
               elif inputType != IT_PING:
                  raise Exception('Multiple input types in the same file?!')

               sourceIP      = ip_address(tuples[1])
               destinationIP = ip_address(tuples[2])
               timeStamp     = int(tuples[3], 16)
               checksum      = int(tuples[4], 16)
               status        = int(tuples[5])
               rtt           = int(tuples[6])

               assert ('0x' + tuples[3]) == hex(timeStamp)
               assert ('0x' + tuples[4]) == hex(checksum)
               # print('ping', sourceIP, destinationIP, timeStamp, status, rtt)

               # ------ Generate output -------------------------------------
               label = str(sourceIP) + '-' + str(destinationIP) + '-' + str(timeStamp)
               if outputType == OT_POSTGRES:
                  timeStampDT  = datetime.datetime(1970, 1, 1, 0, 0, 0, 0) +  datetime.timedelta(microseconds = timeStamp)
                  timeStampStr = timeStampDT.strftime("%Y-%m-%dT%H:%M:%S.%f")
                  output[label] = '(' + \
                     '\'' + timeStampStr       + '\',' + \
                     '\'' + str(sourceIP)      + '\',' + \
                     '\'' + str(destinationIP) + '\',' + \
                     str(status) + ',' + \
                     str(rtt) + \
                     ')'

               elif outputType == OT_MONGODB:
                  output[label] = OrderedDict([
                                     ( 'source',      str(sourceIP)),
                                     ( 'destination', str(destinationIP)),
                                     ( 'timestamp',   int(timeStamp)),
                                     ( 'checksum',    int(checksum)),
                                     ( 'status',      int(status)),
                                     ( 'rtt',         int(rtt)) ])

            else:
               raise Exception('Bad input for Ping in line ' + str(lineNumber))


         # ====== Traceroute ================================================
         elif tuples[0] == '#T':
            if len(tuples) >= 9:
               # ------ Handle input ----------------------------------------
               if inputType == IT_NONE:
                  inputType = IT_TRACEROUTE
               elif inputType != IT_TRACEROUTE:
                  raise Exception('Multiple input types in the same file?!')

               sourceIP      = ip_address(tuples[1])
               destinationIP = ip_address(tuples[2])
               timeStamp     = int(tuples[3], 16)
               roundNumber   = int(tuples[4])
               checksum      = int(tuples[5], 16)
               totalHops     = int(tuples[6])
               statusFlags   = int(tuples[7], 16)
               pathHashStr   = tuples[8]
               pathHash      = int(pathHashStr, 16)

               assert ('0x' + tuples[3]) == hex(timeStamp)
               assert ('0x' + tuples[5]) == hex(checksum)
               assert ('0x' + tuples[7]) == hex(statusFlags)
               assert ('0x' + tuples[8]) == hex(pathHash)
               # print('traceroute', sourceIP, destinationIP, timeStamp, roundNumber, checksum, totalHops, statusFlags, pathHash)

               if outputType == OT_POSTGRES:
                  timeStampDT  = datetime.datetime(1970, 1, 1, 0, 0, 0, 0) +  datetime.timedelta(microseconds = timeStamp)
                  timeStampStr = timeStampDT.strftime("%Y%m%dT%H%M%S.%f")

               elif outputType == OT_MONGODB:
                  label = str(sourceIP) + '-' + str(destinationIP) + '-' + str(timeStamp) + '-' + str(roundNumber).zfill(3)
                  # MongoDB only supports signed integers:
                  if pathHash > 0x7FFFFFFFFFFFFFFF:
                     pathHash -= 0x10000000000000000
                  hopCheck[label] = 0
                  output[label] = OrderedDict([
                                     ( 'source',                   str(sourceIP)),
                                     ( 'destination',              str(destinationIP)),
                                     ( 'timestamp',                int(timeStamp)),
                                     ( 'round',                    int(roundNumber)),
                                     ( 'checksum',                 int(checksum)),
                                     ( 'totalHops',                int(totalHops)),
                                     ( 'statusflags',              int(statusFlags)),
                                     ( 'pathhash',                 int(pathHash)),
                                     ( 'hops',                     [] ) ])

            else:
               raise Exception('Bad input for Traceroute in line ' + str(lineNumber))


         elif ((tuples[0] == '\t') and (inputType == IT_TRACEROUTE)):
            if len(tuples) >= 4:
               # ------ Handle input ----------------------------------------
               hopNumber = int(tuples[1])
               status    = int(tuples[2], 16)
               rtt       = int(tuples[3])
               hopIP     = ip_address(tuples[4])

               assert hopNumber <= totalHops
               assert ('0x' + tuples[2]) == hex(status)
               # print('\t', hopNumber, status, rtt, hopIP)

               # ------ Generate output -------------------------------------
               if outputType == OT_POSTGRES:
                  label = str(sourceIP) + '-' + str(destinationIP) + '-' + str(timeStamp) + '-' + str(roundNumber).zfill(3) + str(hopNumber).zfill(3)
                  output[label] = '(' + \
                     '\'' + timeStampStr       + '\',' + \
                     '\'' + str(sourceIP)      + '\',' + \
                     '\'' + str(destinationIP) + '\',' + \
                     str(hopNumber) + ',' + \
                     str(totalHops) + ',' + \
                     str(status | statusFlags) + ',' + \
                     str(rtt) + ',' + \
                     '\'' + str(hopIP) + '\',' + \
                     'CAST(X\'' + pathHashStr + '\' AS BIGINT),' + \
                     str(roundNumber) + \
                     ')'

               elif outputType == OT_MONGODB:
                  label = str(sourceIP) + '-' + str(destinationIP) + '-' + str(timeStamp) + '-' + str(roundNumber).zfill(3)
                  assert(hopCheck[label] + 1 == hopNumber)   # Make sure that all hops are in order!
                  hopCheck[label] = hopNumber

                  output[label]['hops'].append(OrderedDict([
                     ( 'status', int(status)),
                     ( 'rtt',    int(rtt)),
                     ( 'hop',    str(hopIP)) ]))

            else:
               raise Exception('Bad input for Traceroute in line ' + str(lineNumber))

         # ====== Error =====================================================
         else:
            raise Exception('Unexpected input in line ' + str(lineNumber))


   # ====== Sort result =====================================================
   resultsList = sorted(output.items(), key=operator.itemgetter(0))

   # ====== Generate output string ==========================================
   if outputType == OT_POSTGRES:
      outputString = ""
      if inputType == IT_PING:
         outputString = 'INSERT INTO Ping VALUES '
      elif inputType == IT_TRACEROUTE:
         outputString = 'INSERT INTO Traceroute VALUES '

      firstItem = True
      for result in resultsList:
         if firstItem:
            outputString = outputString + '\n' + result[1]
            firstItem = False
         else:
            outputString = outputString + ',\n' + result[1]

      outputString = outputString + ';'
      return outputString

   elif outputType == OT_MONGODB:
      outputList = []
      for result in resultsList:
            outputList.append(result[1])
      return [ inputType, outputList ]



inputFile = bz2.open('/tmp/xy/Ping-172.16.0.206-20180111T124131.681518-000000014.results.bz2', 'rt')
#inputFile = bz2.open('/tmp/xy/Traceroute-172.16.0.206-20180111T105853.648711-000000001.results.bz2', 'rt')


outputString = processInput(inputFile, OT_MONGODB)   # OT_POSTGRES OT_MONGODB 
print(outputString)
