# -*- coding: utf-8 -*-
"""
Created on Sat Feb  3 22:59:08 2024

@author: fulvio
"""


from datetime import datetime, timedelta, timezone
import pytz
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.dates as mdates
import time

def ListPrint(lst):
    for el in lst:
        print(el)

def PrintEvents(events):
    if len(events) != 2 or type(events[1]) != list or (len(events[1]) > 0 and type(events[1][0]) != list):
        print("Error: events must be a list of a color and a list of lists [datetime, message]" )
        return
    print(events[0])
    for event in events[1]:
        for el in event:
            print(el)
        print("")

class LogTimeline:
    def __init__(self, file_name, datetime_range, lines_criteria, event_criteria, discard_criteria, desired_tz, desired_res=60):
        self.file_name = file_name
        self.datetime_range = self._GetDatetimeRange(datetime_range)
        self.lines_criteria = lines_criteria
        self.event_criteria = event_criteria
        self.discard_criteria = discard_criteria
        self.selected_lines = []
        self.log_datetimes = []
        self.time_delta = timedelta(seconds=desired_res)
        self.lines_events = []
        self.lines_vectors = []
        self._TxtSelectLines()
        self.desired_timezone = desired_tz
        print(len(self.selected_lines))
        if len(self.selected_lines) > 0:
            self._LogTimelinePlot()
        else:
            print("No Linese Selected!")

    def _GetDatetimeRange(self, datetime_range):
        dtnow = datetime.now(timezone.utc)
        strtype = type("Last")
        dttype = type(dtnow)
        if isinstance(datetime_range, list) and len(datetime_range) == 2:
            if isinstance(datetime_range[0], datetime) and isinstance(datetime_range[1], datetime):
                return datetime_range
        elif isinstance(datetime_range, str) and len(datetime_range) >= 4:
            if datetime_range == "Last":
                return [dtnow - timedelta(seconds=3600), dtnow]
            else :
                val = eval(datetime_range[4:])
                if isinstance(val, int) or isinstance(val, float):
                    return [dtnow - timedelta(seconds=(3600*eval(datetime_range[4:]))), dtnow]
                else:
                    raise Exception("Invalid data range parameter 1")
        else:
            raise Exception("Invalid data range parameter 2")

    def _TxtSelectLines(self):
        counter = 0
        line_counter = 0
        file_object = open(self.file_name, 'r')
        continue_loop = True
        self.selected_lines = []
        line = ""
        first_line = line
        for crit in self.event_criteria:
            self.lines_events.append([crit[0], []])
        discarded = 0
        line_counter2 = 0
        while continue_loop :
            line_counter += 1
            last_line = line
            line = file_object.readline()
            if line_counter == 1:
                first_line = line
            if line == "" : break
            discard = False
            for crit in self.discard_criteria:
                if self._IsMet(line, crit):
                    discard = True
                    discarded += 1
                    break
            if discard:
                continue
            if self._LogLineInRange(line):
                line_counter2 += 1
                line_vector = self._SplitLogLine(line)
                line_vector.insert(0,line_counter)
                for crit in self.lines_criteria :
                    if self._IsMet(line, crit):
                        # selected.append("{:07d} {}".format(line_counter,line))
                        self.selected_lines.append(line_vector)
                        counter += 1
                        break
                counter = 0
                # crit = [color, criteria_list, predicate(line_vector[4]):string])
                double_break = False
                for crit in self.event_criteria:
                    if line_counter2 == 1:
                        print("crit : ", crit)
                    for subcrit in crit[1]:
                        if line_counter2 == 1:
                            print("subcrit : ", subcrit)
                        if self._IsMet(line, subcrit):
                            final_string = crit[2](line_vector[4])
                            self.lines_events[counter][1].append([self._GetDatetime(self._GetDateString(line_vector[1])), final_string])

                            if crit[0] == 'orange':
                                self.lines_vectors.append(line)
                            double_break = True
                            break
                    #    elif self._IsMet(line, [['FATAL', 'True']]):
                    #        self.lines_vectors.append(["Not Met", subcrit, line])
                    if double_break:
                        double_break = False
                        break
                    counter += 1
        print("TxSelectLines 2 : ", [self._LogLineDatetime(first_line), self._LogLineDatetime(last_line)])
        file_object.close()
        print("TxSelectLines 3 : ", line_counter, "; Discarded : ", discarded)
        return counter

    def _IsMet(self, line, criterium):
        """ 'False' -> not all
            'True'  -> yes all
            'Any'   -> yes any
            'Fany'  -> not any
        """
        met_true = True
        met_false = True
        met_any = True
        met_fany = True
        false_counter = 0
        fany_counter = 0
        true_counter = 0
        for crit in criterium:
            if crit[1] == 'Any':
                met_any = False
            if crit[1] == 'True':
                true_counter += 1
                met_true = False
            if crit[1] == 'False':
                false_counter += 1
                met_false = False

        for crit in criterium:
            if crit[1] == 'True' : # yes all - devono esserci tutti
                if line.count(crit[0]) > 0 :
                    true_counter -= 1
            elif crit[1] == 'False' : # not all- non devono esserci tutti insieme
                if line.count(crit[0]) == 0 :
                    false_counter -= 1
            elif crit[1] == 'Any': # yes any - basta che ce ne sia uno
                if line.count(crit[0]) > 0 :
                    met_any = True
            elif crit[1] == 'Fany': # not any - non deve essercene nessuno
                if line.count(crit[0]) > 0 :
                    met_fany = False
            else:
                raise BaseException("invalid criterium : " + crit[1])

        met_false = false_counter == 0
        met_true = true_counter == 0
        return met_true and met_false and met_any and met_fany

    def _SplitLogLine(self, line):
        subparts = []
        line_index = 0
        line_part = line
        global_index = 0
        subparts.append(global_index)
        splitted_line = []
        while line_index != -1:
            # part_len = len(line_part)
            line_index = line_part.find(chr(27))
            if line_index != -1:
                if line_index == 0:
                    line_part = line_part[2:]
                    global_index += 2
                    line_index = line_part.find('m')
                    if(line_index != -1):
                        global_index += line_index + 1
                        subparts.append(global_index)
                        line_part = line_part[line_index + 1 : ]
                    continue
                if global_index + line_index + 2 < len(line):
                    global_index += line_index + 2
                else :
                    print("Error bad formatted string")
                splitted_line.append(line_part[0:line_index])
                # getting rid of colour formating
                line_part = line_part[line_index + 2 : ]
                line_index = line_part.find('m')
                if(line_index != -1):
                    global_index += line_index + 1
                    subparts.append(global_index)
                    line_part = line_part[line_index + 1 : ]
        splitted_line.append(line_part)
        return splitted_line
    def _GetDateString(self, date_string):
        point = date_string.find('.')
        if point != -1 and len(date_string) >= point + 7:
            return date_string[0:point + 7]
        elif point != -1 and len(date_string) > point:
            return date_string[0:]
        else:
            print("Error bad formatted string :", date_string)
            return ""
    def _GetDatetime(self, date_string) :
        try:
            return datetime.strptime(date_string,"%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc)
        except:
            return datetime.strptime("2020-02-20 20:20:02.20","%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc)
    def _DatetimeInRange(self, date_time, date_range) :
        return date_time >= date_range[0] and date_time <= date_range[1]
    def _DateStringInRange(self, date_string):
        date_datetime = self._GetDatetime(date_string)
        return self._DatetimeInRange(date_datetime, self.datetime_range)
    def _LogLineInRange(self, line):
        date_string = line[0:line.find(' ', 20)]
        if date_string:
            date_string2 = self._GetDateString(date_string)
            if date_string2:
                date_datetime = self._GetDatetime(date_string2)
                return self._DatetimeInRange(date_datetime, self.datetime_range)
            else:
                return False
        else:
            return False
    def _LogLineDatetime(self, line):
        return self._GetDatetime(self._GetDateString(line[0:line.find(' ', 20)]))
    def _LogTimelinePlot(self):
        figsizex = 128
        print("LogTimelinePlot : ", len(self.selected_lines))
        time_delta_seconds = self.time_delta.days*86400 + self.time_delta.seconds
        if time_delta_seconds < 0:
            print("Error time_delta must be positive")

        self.log_datetimes = []
        for el in self.selected_lines:
            self.log_datetimes.append(self._GetDatetime(self._GetDateString(el[1])))
        start_log = self.log_datetimes[0]
        end_log = self.log_datetimes[-1]
        start_log3 = start_log2 = start_log - timedelta(seconds=start_log.hour*3600 + start_log.minute*60 + start_log.second, microseconds=start_log.microsecond)
        end_log2 = end_log - timedelta(seconds=end_log.hour*3600 + end_log.minute*60 + end_log.second, microseconds=end_log.microsecond)
        while start_log2 + self.time_delta < start_log:
            start_log2 += self.time_delta
        while end_log2 + self.time_delta < end_log:
            end_log2 += self.time_delta
        end_log2 += self.time_delta
        numbins = int((end_log2 - start_log2)/self.time_delta)
        counts = [0 for el in range(numbins)]
        xvalues = [start_log2 + self.time_delta/2 + el*self.time_delta for el in range(numbins)]
        for el in self.log_datetimes:
            counts[int((el - start_log2)/self.time_delta)] += 1

        plot_range = end_log - start_log
        full_seconds = plot_range.days*86400 + plot_range.seconds
        tick_seconds = full_seconds/50.
        ticks_available = [0.016666667, 0.033333334, 0.0833333334, 0.166666667, 0.25, 0.5, 1, 2, 5, 10, 15, 20, 30, 60, 120, 240, 480, 720, 1440, 2880, 5760, 11520, 23040]
        valid_tick = int(15*60)
        for val in ticks_available:
            valid_tick = int(val*60)
            if valid_tick > tick_seconds:
                break
        print("full_seconds = ", full_seconds)
        print("valid_tick = ", valid_tick)
        # x, y = LogDensityPlotDelta2(log_datetimes, timedelta(seconds=60))
        # xvalues, counts = plot_pred(self.selected_lines)

        fig, ax = plt.subplots(1, 1, figsize=(figsizex, 8))
        ax.plot(xvalues, counts)
        # quarters = mdates.MinuteLocator( interval=15, tz=self.desired_timezone)
        if valid_tick >= 1 and valid_tick < 60:
            quarters = mdates.SecondLocator(bysecond=range(0,60, valid_tick), tz=self.desired_timezone)
        elif valid_tick >= 60 and valid_tick < 3600:
            quarters = mdates.MinuteLocator(byminute=range(0,60, int(valid_tick/60)), tz=self.desired_timezone)
        elif valid_tick >= 3600 and valid_tick < 86400:
            quarters = mdates.HourLocator(byhour=range(0,24, int(valid_tick/3600)), tz=self.desired_timezone)
        elif valid_tick >= 86400 and valid_tick < 86400*16:
            quarters = mdates.DayLocator(bymonthday=range(0,31, int(valid_tick/86400)))
        ax.xaxis.set_major_locator(quarters)
        ax.xaxis.set_major_formatter(mdates.DateFormatter("%d %H:%M", tz=self.desired_timezone))
        ax.grid(True)
        # ax.label_outer()
        ax.set_ylabel('counts')
        fig.suptitle('Log Lines ~ ' + start_log3.strftime("%Y-%m-%d") + " - " + start_log2.astimezone(self.desired_timezone).tzname())
        fig.autofmt_xdate()
        deltaY = (lambda x : 3*(x[1] - x[0])/10 + x[0])(ax.get_ybound())
        xbounds = ax.get_xbound()
        line_shift = 1000*((xbounds[1] - xbounds[0])*86400)/(figsizex*16)
        print("xbounds : ", xbounds)
        for lines in self.lines_events :
            c = lines[0]
            for line in lines[1] :
                if self._DatetimeInRange(line[0], self.datetime_range) :
                    ax.axvline(line[0], linewidth=1, alpha=0.5, color=c)
                    ax.text(line[0] + timedelta(milliseconds=line_shift), deltaY, line[1], fontsize=14, rotation='vertical',color=c)
        plt.show()




time_range = [datetime(year=2024, month=1, day=7, hour=18, minute=0, tzinfo=timezone.utc), datetime(year=2024, month=1, day=8, hour=6, minute=0, tzinfo=timezone.utc)]
time_range = [datetime(year=2024, month=1, day=7, hour=18, minute=0, tzinfo=pytz.timezone('America/Phoenix')), datetime(year=2024, month=1, day=8, hour=6, minute=0, tzinfo=pytz.timezone('America/Phoenix'))]




# %% ObscontrolServer.log

def pred1(string):
    return "OS START"
messages2 = []
def pred2(msg_string):
    global messages2
    #messages2.append(msg_string)
    # if len(msg_string) > 80:
    #     print("Pred2 : ", msg_string)
    return msg_string
messages = []
def pred3(string):
    global messages
    index = string.rfind('/') + 1
    if index == 0:
        index = string.rfind('__') + 2
    messages.append("pred3 : " + string +  " ~ " + string[index:])
    if len(string[index:]) > 40:
        print("Pred3 : ", string[index:])
    return string[index:]
def pred4(msg_string):
    return msg_string
def pred5(msg_string):
    return msg_string


os_start = [[['31mFATAL', 'True'], ["OS START", 'True']]]
os_errors = [[['31mFATAL', 'Any'], ['31mALERT','Any'],['31mCRITICAL','Any'],['31mERROR','Any'], ["OS START", 'False']]]
os_warnings = [[['33mWARNING','True']]]
os_templates = [[['setContext', 'True'], ['.xml', 'True']]]
os_commands = [[['undef_sequencerI.cpp', 'True'], ['IceCallCounter', 'True']]]


timeline_criteria_new = [[["2024", 'True']],
        [['31mFATAL','True']],
        [['31mALERT','Any'],['31mCRITICAL','Any'],['31mERROR','Any'],['33mWARNING','Any']],
        [['IceCallCounter','True']],
        [['setContext', 'True'], ['.xml', 'True']]
        ]
"Basda::ServiceIsNotOnlineException"
discard_criteria = [
    [['WARNING', 'True'], ['multiStageX.cpp', 'True'], ['emergencyAbort', 'True'], ['ABORT', 'True']],
#    [['WARNING', 'True'], ['Completion was lost', 'True']],
#    [['WARNING', 'True'], ['Worker is not online', 'True']],
#    [['31mERROR', 'True'], ['Basda::ServiceIsNotOnlineException', 'True']],
    [['WARNING', 'True'], ['Basda::ServiceIsBusyException', 'True']],
    [['WARNING', 'True'], ['SharknirDrotServiceWorker.cc:40', 'True']],
    [['WARNING', 'True'], ['multiStageX.cpp', 'True'], ['stop', 'True'], ['STOP', 'True']],
    [['WARNING', 'True'], ['Nice/App/Logger.cc', 'True'], ['Nice::AppLogger::IceLogger::warning', 'True']],
    [['WARNING', 'True'], ['sequencerI.cpp', 'True'], ['emergencyAbortAll', 'True'], ['ABORT triggered', 'True']],
    [['WARNING', 'True'], ['multiWheel.cpp', 'True'], ['Stopping motion of selected wheels', 'True']],
    [['WARNING', 'True'], ['seqISetup.cpp', 'True'], ['Interdiction 1 : pos = ', 'True']],
    [['WARNING', 'True'], ['sequencerI.cpp', 'True'], ['stopMotion', 'True'], ['Stopping motion of specified devices', 'True']],
    [['WARNING', 'True'], ['Ltcs/Iif/ServiceWorker.cc', 'True'], ['ServiceWorkerReadWaitDataWithCompletionProxy', 'True'], ['Completion was lost', 'True']]
    ]

events_criteria = [["r", os_start, pred1], ["r", os_errors, pred4], ['orange', os_warnings, pred2], ['g', os_templates, pred3],  ['gray', os_commands, pred5]]

dt1 = datetime(year=2024, month=2, day=5, hour=0, minute=0, tzinfo=pytz.timezone('America/Phoenix'))
dt2 = datetime(year=2024, month=2, day=5, hour=7, minute=0, tzinfo=timezone.utc)
dt1.astimezone(timezone.utc)
dt2.astimezone(pytz.timezone('America/Phoenix'))
[dt2, dt2.astimezone(pytz.timezone('America/Phoenix'))]
[dt1, dt1.astimezone(timezone.utc)]

lbttz = timezone(timedelta(seconds=(-7*3600)), 'LBT')
itatz = timezone(timedelta(seconds=(1*3600)), 'ITA')
utctz = timezone.utc


# %% Prima notte del secondo run
# time_range = [datetime(year=2023, month=12, day=30, hour=1, minute=0, tzinfo=utctz), datetime(year=2023, month=12, day=30, hour=12, minute=20, tzinfo=utctz)]
# sti = LogTimeline("/opt/tan/var/log/saved/obsControlServer.log-202312-29-30",   time_range   , timeline_criteria_new, events_criteria, discard_criteria, utctz, 20)


time_range = [datetime(year=2024, month=2, day=5, hour=2, minute=0, tzinfo=pytz.timezone('America/Phoenix')), datetime(year=2024, month=2, day=5, hour=3, minute=0, tzinfo=pytz.timezone('America/Phoenix'))]
time_range = [datetime(year=2024, month=2, day=5, hour=2, minute=25, tzinfo=timezone.utc), datetime(year=2024, month=2, day=5, hour=2, minute=35, tzinfo=timezone.utc)]

time_range = [datetime(year=2024, month=2, day=4, hour=16, minute=50, tzinfo=lbttz), datetime(year=2024, month=2, day=5, hour=16, minute=0, tzinfo=lbttz)]
# sti = LogTimeline("/opt/tan/var/log/obsControlServer.log",  "Last12"  , timeline_criteria_new, events_criteria, discard_criteria, utctz, 20)


time_range = [datetime(year=2024, month=2, day=15, hour=0, minute=30, tzinfo=utctz), datetime(year=2024, month=2, day=15, hour=5, minute=0, tzinfo=utctz)]
sti = LogTimeline("/opt/tan/var/log/obsControlServer.log",   time_range   , timeline_criteria_new, events_criteria, discard_criteria, utctz, 20)