import matplotlib as mt
import matplotlib.pyplot as plt
import datetime
import os
import geoip2.database
import numpy as np
import math

import sys
sys.path.append('./graphs')
from map import Map
from cumulative import Cumulative
from overTime import OverTime
from ratio import Ratio
from stats import Stats
from popularity import Popularity
from users import Users
sys.path.append('./logs')
from apache import Apache
from ftp import Ftp

# paths
relative = os.path.abspath(os.path.dirname(__file__))
OUT = os.path.join(relative, "out") # graph output location
IP_DATABASE = os.path.join(relative, 'MaxMind/GeoLite2-City.mmdb') # maxmind database location

GRAPHS = [Map(), Cumulative(), OverTime(), Ratio(), Popularity(), Stats(), Users()]
LOGS = [
    Apache(["/data2/xshao/http_access_log/gpsmet.umd.edu-website-downloads.log*"]),
    Ftp(['/data2/xshao/ftplog/xferlog', '/data2/xshao/ftplog/xferlog-*'])
]

maxmind = geoip2.database.Reader(IP_DATABASE)
for log in LOGS:
    log.open()
    log.sort(maxmind)

errLog = []
frontier = [log.getNext(maxmind) for log in LOGS] # holds the next line (by time) for each log type
index = -1
done = False
while not done:
    # take the line with the min time
    line = frontier[0]
    frontierIndex = 0
    for j, l in enumerate(frontier):
        if not l:
            continue

        if not line or l.time < line.time:
            frontierIndex = j
            line = l

    if not line: # all frontiers are None, which means we have read every line
        done = True
    
    if not done:
        diff = LOGS[frontierIndex].totalRead
        frontier[frontierIndex] = LOGS[frontierIndex].getNext(maxmind) # refresh frontier
        index += LOGS[frontierIndex].totalRead - diff # .getNext can read many lines, so update index to match the actual number of read lines

        for graph in GRAPHS:
            errLog += graph.process(line.ip, line.time, line.request, line.size, line.location, LOGS[frontierIndex].name)

    # progress bars
    if index > 1000 or index == -1 or done: # index == -1 to draw on first iteration
        index = 0
        printedLines = 0
        len_name = 0
        len_cur = 0
        len_max = 0
        for log in LOGS:
            for file, val in log.progress.items():
                len_name = max(len_name, len(file))
                len_cur = max(len_cur, len(str(val['cur'])))
                len_max = max(len_max, len(str(val['max'])))

        for log in LOGS:
            print(f'{log.name} - {log.totalRead}/{log.totalLines} ({round(100 * log.totalRead / log.totalLines)}%)')
            printedLines += 1

            len_bar = 50
            for file, val in log.progress.items():
                prog = math.floor(len_bar * val['cur'] / val['max'])
                name = file.ljust(len_name)
                bar = f"[{'=' * prog}{'-' * (len_bar - prog)}]"
                count = f"{str(val['cur']).rjust(len_cur)} / {str(val['max']).ljust(len_max)}"
                pct = f"({round(100 * val['cur'] / val['max'])}%)"
                print(f"  └ {name} {bar} {count} {pct}")
                printedLines += 1

        if printedLines != 0:
            print('\033[A' * printedLines + '\r', end='')

print('\n' * printedLines) # assumes above while loop runs at least once for printedLines to be set
maxmind.close()
for log in LOGS:
    log.close()

if len(errLog) != 0:
    print(f'{len(errLog)} errors encounted (printing first 100):')
    print('\n'.join(errLog[:100]))

# formatting stuff for graph
converter = mt.dates.ConciseDateConverter() # https://matplotlib.org/stable/gallery/ticks/date_concise_formatter.html
mt.units.registry[np.datetime64] = converter
mt.units.registry[datetime.date] = converter
mt.units.registry[datetime.datetime] = converter
plt.rcParams['font.size'] = 16
plt.rcParams['axes.xmargin'] = 0
# default axes.prop_cycle + a bunch of other colors to prevent overflow
plt.rcParams['axes.prop_cycle'] = mt.cycler(color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', 'lightblue', 'lavender', 'turquoise', 'darkgreen', 'tan', 'gold'])

for graph in GRAPHS:
    graph.draw(OUT)

for graph in GRAPHS:
    graph.close()
