#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  =================================================================
#           #     #                 #     #
#           ##    #   ####   #####  ##    #  ######   #####
#           # #   #  #    #  #    # # #   #  #          #
#           #  #  #  #    #  #    # #  #  #  #####      #
#           #   # #  #    #  #####  #   # #  #          #
#           #    ##  #    #  #   #  #    ##  #          #
#           #     #   ####   #    # #     #  ######     #
#
#        ---   The NorNet Testbed for Multi-Homed Systems  ---
#                        https://www.nntb.no
#  =================================================================
#
#  High-Performance Connectivity Tracer (HiPerConTracer)
#  Copyright (C) 2015-2024 by Thomas Dreibholz
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#  Contact: dreibh@simula.no

import os
import sys
import io
import datetime
import bz2
import shutil
import psycopg2
import configparser
import hashlib


# ###### Print log message ##################################################
def log(logstring):
   print('\x1b[32m' + datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S') + ': ' + logstring + '\x1b[0m');


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



# ###### Main program #######################################################
if len(sys.argv) < 3:
   error('Usage: ' + sys.argv[0] + ' database_configuration days_in_past')

configFileName = sys.argv[1]
daysInPast     = int(sys.argv[2])
badFilePath    = None
dbServer       = 'localhost'
dbPort         = 5432
dbUser         = 'importer'
dbPassword     = None
dbName         = 'pingtraceroutedb'


# ====== Get parameters =====================================================
parsedConfigFile = configparser.RawConfigParser()
parsedConfigFile.optionxform = str   # Make it case-sensitive!
try:
   parsedConfigFile.readfp(io.StringIO('[root]\n' + open(configFileName, 'r').read()))
except Exception as e:
    error('Unable to read database configuration file' +  sys.argv[1] + ': ' + str(e))
    sys.exit(1)

for parameterName in parsedConfigFile.options('root'):
   parameterValue = parsedConfigFile.get('root', parameterName)
   if parameterName == 'transactions_path':
      continue
   elif parameterName == 'bad_file_path':
      badFilePath = parameterValue
   elif parameterName == 'dbserver':
      dbServer = parameterValue
   elif parameterName == 'dbport':
      dbPort = parameterValue
   elif parameterName == 'dbuser':
      dbUser = parameterValue
   elif parameterName == 'dbpassword':
      dbPassword = parameterValue
   elif parameterName == 'database':
      dbName = parameterValue
   else:
      error('Unknown parameter ' + parameterName + ' in ' + sys.argv[1] + '!')


# ====== Connect to the database ============================================
try:
   dbConnection = psycopg2.connect(host=str(dbServer), port=str(dbPort),
                                   user=str(dbUser), password=str(dbPassword),
                                   dbname=str(dbName))
   dbConnection.autocommit = False
except:
    log('Unable to connect to the database!')
    sys.exit(1)

dbCursor = dbConnection.cursor()


# ====== Run ================================================================
dbCursor.execute("""
   select t.TimeStamp, t.FromIP, t.ToIP, t.HopNumber, t.HopIP, t.PathHash, t.Status, t.RTT
   from Traceroute t
   where
      t.TimeStamp >= (select max(TimeStamp) from Traceroute) - interval '""" + str(daysInPast + 1) + """ days' and
      t.TimeStamp <= (select max(TimeStamp) from Traceroute) - interval '""" + str(daysInPast) + """ days'
   order by t.TimeStamp asc, t.fromIP, t.toIP, t.HopNumber, t.HopIP;
""")

rows = dbCursor.fetchall()


oldPathHash = 0
pathString  = ""
pathHasStar = False

for row in rows:
   timeStamp = row[0]
   fromIP    = row[1]
   toIP      = row[2]
   hopNumber = row[3]
   hopIP     = row[4]
   pathHash  = row[5]
   status    = row[6]
   rtt       = row[7]

   # ====== One path is finished ============================================
   if hopNumber == 1:
      # ------ Finished a path ----------------------------
      if pathString != "":
         m = hashlib.sha1()
         m.update(pathString.encode('ascii'))
         digest = m.hexdigest()[0:16]

         m = hashlib.sha1()
         m.update(pathString.encode('ascii'))
         digest = m.hexdigest()[0:16]

         a = int(digest[0:8], 16)
         b = int(digest[8:16], 16)
         newPathHash = (a << 32) | b
         if newPathHash > 0x7FFFFFFFFFFFFFFF:
             newPathHash -= 0x10000000000000000

         #print(pathString)
         #print(newPathHash, format(a, 'x'), format(b, 'x'), newPathHash, "OLD:", oldPathHash)

         if (oldPathHash != 0) and (oldPathHash != newPathHash):
            sys.stderr.write('ERROR: Hash values differ!\n')
            sys.exit(1)

         if ((newStatusFlags != oldStatusFlags) or
             (newPathHash != oldPathHash)):
            #print(oldPathHash, newPathHash, oldStatusFlags, newStatusFlags)
            sql = """UPDATE Traceroute SET PathHash = """ + str(newPathHash) + """, Status = (Status & 255) | """ + str(newStatusFlags) + """ WHERE TimeStamp='""" + str(oldTimeStamp) + """' AND FromIP='""" + oldFromIP + """' AND ToIP='""" + oldToIP + """';"""
            print(sql)


      # ------ Start next path ----------------------------
      #print("-----")
      oldTimeStamp   = timeStamp
      oldFromIP      = fromIP
      oldToIP        = toIP
      oldPathHash    = pathHash
      oldStatusFlags = status & ~0xff
      pathString     = fromIP
      newStatusFlags = 0x0000

   # ====== Set status ======================================================
   if (((status & 0xff) >= 200) and ((status & 0xff) <= 254)):
      pathString = pathString + '-*'
      newStatusFlags = newStatusFlags | 0x0100   # Route with * (router did not respond)
   else:
      if (status & 0xff) == 0xff:
         newStatusFlags = newStatusFlags | 0x0200   # Destination has responded
      pathString = pathString + '-' + hopIP


   #print(timeStamp,fromIP,toIP,hopNumber,hopIP,pathHash,status & 0xff,rtt)
