 #
 # This file is part of ROA-Analysis
 #
 # Author: Samir Al-Sheikh (Freie Universitaet, Berlin)
 #         s.al-sheikh@fu-berlin.de
 #
 # MIT License
 #
 # Copyright (c) 2017 The ROA-Analysis authors
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
 # in the Software without restriction, including without limitation the rights
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
 # furnished to do so, subject to the following conditions:
 # 
 # The above copyright notice and this permission notice shall be included in all
 # copies or substantial portions of the Software.
 # 
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 #

import os, re, matplotlib, calendar, types, numpy, pandas, copy, time, warnings, glob
from collections import Counter
from datetime import datetime
from subprocess import Popen
from utils.parse import parse_window, parse_shell, parse_cfg
from utils.util import cc_refactoring
config_file = os.path.dirname(os.path.abspath(__file__)) + '/config.txt'
if parse_cfg(0).ssh_enabled: matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib as mpl

options = 'i:p:c:m:d:t'

## Note: Warnings ignored (Type 3 font error if tex mode is active)
warnings.filterwarnings('ignore')

def get_options():
  return options

def start_bgpreader(interval, project, collectors, archive_an, cfg):
  """ Starts a BGPReader instance for multiple RPKI Cache Server for a given time interval """
  
  ## Check whether LD_LIBRARY_PATH is set to prevent rtrlib linker errors
  rtr_env = os.environ.copy()
  if 'LD_LIBRARY_PATH' not in os.environ: exit("Warning: LD_LIBRARY_PATH is not set, add the rtrlib path\n")

  ## Start BGPReader and log errors to file
  dump_file = archive_an + '/' + cfg.bgp_project + \
              '_' + cfg.bgp_vantage_point + '_' + interval + '.csv'
  if not os.path.exists('LOG/'): os.makedirs('LOG/')
  log_file = 'LOG/' + 'plot_BGP_Impact_log.txt'
  print 'Info: Executing BGPReader...'
  with open(log_file, 'a') as f, open(os.devnull, 'w') as devnull:
    command = cfg.bgpreader_path + ' -t updates -p ' + cfg.bgp_project + ' -c ' +  cfg.bgp_vantage_point + \
      ' -H ' + '\"1,0,0,' + project + ',' + (',' + project + ',').join(collectors) + '\"' + \
      ' -w ' + interval +  ' > \"' + dump_file + '\"'
    print command
    p = Popen(['/bin/bash', '-c', command], env=rtr_env, stdout=devnull, stderr=f)
    p.communicate()
  print 'Info: Finished BGPReader'

  if not os.stat(dump_file).st_size:
    return -1
  return dump_file

def main():
  """ Dumps BGPReader output to file and analyses the different validation status """

  ## Parse shell arguments and config constants and build up file infrastructure
  args = parse_shell(options); cfg = parse_cfg(args)
  if isinstance(args.collector, types.StringTypes):
    exit('Error: Collector number should be minimal two')
  archive_an = cfg.archive_an + 'BGP_Impact/' + '_'.join(args.collector)
  if not os.path.exists(archive_an): os.makedirs(archive_an)

  ## Get dump files and convert argument interval to unix timestamps
  dump_files, dump_diff = ([] for i in range(2))
  for c in args.collector:
    collector_search = cfg.archive_cc + c + '/*/' + cfg.pf + '*' + cfg.sf
    dump_files.append([c, sorted([e.split('/')[-1] for e in glob.iglob(collector_search)])])

  start, end = args.interval.split(',')
  if(start == '0' or end == '-1'): exit('Error: Open interval is not allowed')
  start_unix = calendar.timegm(datetime.strptime(start, '%Y%m%d.%H%M').utctimetuple())
  end_unix = calendar.timegm(datetime.strptime(end, '%Y%m%d.%H%M').utctimetuple())
  inv_unix = str(start_unix) + ',' + str(end_unix)

  ## Start BGPReader and wait for the output
  if(args.mode == 'w'):
    dump_file = start_bgpreader(inv_unix, args.project, args.collector, archive_an, cfg)
    if(dump_file == -1):
      exit('Error: BGPStream caused an error')

    ## Create a output Dump containing only the updates with different validation results
    with open(dump_file, 'r') as f:
      for line in f:
        if 'epoch_filetime' in line:
          dump_diff.append(line); continue
        if(line.split('|')[1] == 'A'):
          c_count = []
          for c in args.collector:
            nf = line.count(c + ',notfound'); iv = line.count(c + ',invalid')
            v = line.count(c + ',valid')
            if c in line: c_count.append([nf, iv, v])
          if(len(set(tuple(c) for c in c_count)) > 1): dump_diff.append(line)
    with open(dump_file[:-4] + '_diff.csv' , 'w') as f: f.write(''.join(dump_diff))

  else:
    dump_file = archive_an + '/' + '-'.join(args.collector) + '_' + cfg.bgp_project + \
                '_' + cfg.bgp_vantage_point + '_' + inv_unix + '.csv'
    print dump_file
    if not os.path.exists(dump_file):
      exit('Error: BGP-Impact file does not exist, rerun with w-mode')

    ## Possible second only plot mode
    nf_diff = pandas.read_csv(archive_an + '/not_found.csv', encoding='utf8', delimiter=',')
    iv_diff = pandas.read_csv(archive_an + '/invalid.csv', encoding='utf8', delimiter=',')
    v_diff = pandas.read_csv(archive_an + '/valid.csv', encoding='utf8', delimiter=',')

    v_diff.columns = ['Timestamp'] + list(v_diff.columns.values[1:])
    v_diff['Timestamp'] = pandas.to_datetime(v_diff['Timestamp'])
    v_diff.set_index('Timestamp', inplace=True)
    v_diff = v_diff.reindex_axis(sorted(v_diff.columns), axis=1)
    iv_diff.columns = ['Timestamp'] + list(iv_diff.columns.values[1:])
    iv_diff['Timestamp'] = pandas.to_datetime(iv_diff['Timestamp'])
    iv_diff.set_index('Timestamp', inplace=True)
    iv_diff = iv_diff.reindex_axis(sorted(iv_diff.columns), axis=1)
    nf_diff.columns = ['Timestamp'] + list(nf_diff.columns.values[1:])
    nf_diff['Timestamp'] = pandas.to_datetime(nf_diff['Timestamp'])
    nf_diff.set_index('Timestamp', inplace=True)
    nf_diff = nf_diff.reindex_axis(sorted(nf_diff.columns), axis=1)

    # Debug Print
    print nf_diff; print iv_diff; print v_diff
 
    plot(nf_diff, iv_diff, v_diff, args.collector, archive_an, dump_file, args.dpi, args.tex)
    return


  ## Open the output file and split it by interval
  df = pandas.read_csv(dump_file, sep='\n', names=['Line'], dtype=str)
  inds = df[df['Line'].str.contains('epoch_filetime')].index.values.tolist() + [df.index[-1]]
  dumps = [(df['Line'][i].split('epoch_filetime: ')[1].split('\n')[0], \
            '\n'.join(df['Line'][i:k].tolist())) for i,k in zip(inds[0::], inds[1::])]
  timestamps = [d[0] for d in dumps]
  del df, inds
 
  ## Count the NFs, IVs, Vs for every collector and set the dataframes
  nf = pandas.DataFrame(columns=args.collector, index=timestamps).fillna(numpy.NaN)
  iv = copy.deepcopy(nf); v = copy.deepcopy(nf)
  for i,y in enumerate(dumps):
    nf_l, iv_l, v_l = ([] for i in range(3))
    for c in args.collector: 
      if not len(re.findall(re.escape(c), dumps[i][1])):
       nf_l.append(numpy.NaN), iv_l.append(numpy.NaN), v_l.append(numpy.NaN) 
      else:
        nf_l.append(len(re.findall(re.escape(c + ',notfound'), dumps[i][1])))
        iv_l.append(len(re.findall(re.escape(c + ',invalid'), dumps[i][1])))
        v_l.append(len(re.findall(re.escape(c + ',valid'), dumps[i][1])))
    nf.loc[dumps[i][0]] = nf_l; iv.loc[dumps[i][0]] = iv_l; v.loc[dumps[i][0]] = v_l

  timestamps = [datetime.strptime(time.strftime('%Y%m%d.%H%M', time.gmtime(float(t))), \
                '%Y%m%d.%H%M') for t in timestamps]
  nf.index = timestamps; iv.index = timestamps; v.index = timestamps;
  nf_diff = nf[args.collector[1:]].sub(nf[args.collector[0]], axis=0)
  iv_diff = iv[args.collector[1:]].sub(iv[args.collector[0]], axis=0)
  v_diff = v[args.collector[1:]].sub(v[args.collector[0]], axis=0)

  nf_diff.columns = cc_refactoring(nf_diff.columns.values)
  nf_diff = nf_diff.reindex_axis(sorted(nf_diff.columns), axis=1)
  iv_diff.columns = cc_refactoring(iv_diff.columns.values)
  iv_diff = iv_diff.reindex_axis(sorted(iv_diff.columns), axis=1)
  v_diff.columns = cc_refactoring(v_diff.columns.values)
  v_diff = v_diff.reindex_axis(sorted(v_diff.columns), axis=1)
 
  ## Debug Print
  print nf_diff; print iv_diff; print v_diff

  ## Export dataframe to csv to enable only plot mode
  nf_diff.to_csv(archive_an + '/not_found.csv', sep=',', encoding='utf-8')
  iv_diff.to_csv(archive_an + '/invalid.csv', sep=',', encoding='utf-8')
  v_diff.to_csv(archive_an + '/valid.csv', sep=',', encoding='utf-8')

  plot(nf_diff, iv_diff, v_diff, args.collector, archive_an, dump_file, args.dpi, args.tex)
  return

def plot(nf_diff, iv_diff, v_diff, collector, archive_an, dump_file, dpi, tex):
  """ Plot the graph for differences for the BGP validation over time """

  ## Set figure properties
  figsize = (20, 10) if not int(tex) else (25, 16)
  mpl.rcParams['figure.figsize'] = figsize; mpl.rcParams['figure.dpi'] = dpi
  mpl.rcParams['figure.facecolor'] = 'w'; mpl.rcParams['figure.edgecolor'] = 'k'
  if int(tex): mpl.rc('font',family='serif',serif='Latin Modern Roman',size=24)

  ## Plotting
  fig, ax = plt.subplots(nrows=len(collector[1:]), ncols=1, sharey=True)
  ax1 = fig.add_subplot(111, frameon=False)
  plt.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
  start, end =  nf_diff.index[0].strftime('%b %d '), nf_diff.index[-2].strftime('until %b %d, %Y')
  collectors = sorted(cc_refactoring(collector)); y = 1.02 if int(tex) else 1
  plt.title('Impact on BGP - Differences for '+ collectors[0].replace('Web', 'W') +' vs. '+ 
               ', '.join([c.replace('Web', 'W').replace('RTR', 'R') for c in collectors[1:]]) + \
               ' - ' + start + end, y=y)

  names = list(nf_diff.columns.values)
  for i,c in enumerate(names):
    fmt = '%Y-%m-%d' if not int(tex) else '%m-%d'
    lab = list(set([pandas.to_datetime(str(l)).strftime(fmt) for l in list(nf_diff.index.values)]))
    ax[i].set_xticklabels(sorted(lab)[::3]); ax[i].grid(axis='y')
    if(i != len(names)-1): ax[i].tick_params(labelbottom='off')
    nf_diff[c].plot(y=names, kind='line', ax=ax[i], label='notfound')
    iv_diff[c].plot(y=names, kind='line', ax=ax[i], label='invalid')
    v_diff[c].plot(y=names, kind='line', ax=ax[i], label='valid')
    ax_offset = 1.02 if int(tex) else 1.01 
    ax[i].set_xlabel(c,rotation=270); ax[i].xaxis.set_label_coords(ax_offset,.90)
    plt.setp(ax[i].xaxis.get_majorticklabels(), rotation=0); ax[i].minorticks_off()
    for t in ax[i].xaxis.get_major_ticks(): t.label1.set_horizontalalignment('center')

  lgd_y = -.8 if int(tex) else -.7
  lgd = ax[i].legend(bbox_to_anchor=(0., lgd_y, 1., lgd_y), loc=8, ncol=3, mode='expand', borderaxespad=0)
  y_offset = -.05 if int(tex) else -.03; ax1.grid(False); ax1.get_yaxis().set_label_coords(y_offset,0.5)
  plt.ylabel('Differences between the validation results of BGP announcements [#prefixes]')
  ax = plt.gca().get_xaxis().set_label_coords(0.5,-.05); plt.xlabel('Timestamp'); fig.tight_layout()

  ## Export Plot
  if not os.path.exists(archive_an + '/Plots/'): os.makedirs(archive_an + '/Plots/')
  dump_pdf = dump_file.split('/')[-1].replace('csv','pdf')
  print '\nPlot: ' + archive_an + '/Plots/' + 'plots_bgp_impact_' + dump_pdf
  fig.savefig(archive_an + '/Plots/' + 'plots_bgp_impact_' + dump_pdf, bbox_inches='tight') 

if __name__ == '__main__':
    main()
