#!/usr/bin/python3

import re,os,sys,time
import numpy as np
import datetime as dt
import simpleaudio as sa
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as mtick
from collections import deque
from matplotlib.widgets import Button
from matplotlib.widgets import TextBox
from matplotlib.widgets import CheckButtons
from matplotlib.animation import FuncAnimation

def getv(ind,val):
    if len(sys.argv) > ind and len(sys.argv[ind])>0:
        return sys.argv[ind]
    else:
        return val

def getv2(ind,first,second):
    if len(sys.argv) > ind and len(sys.argv[ind])>0:
        a=sys.argv[ind]
        p=a.find(":")
        if p<0:
            if len(a)>0:
                second=float(a)
        else:
            v1=a[:p]
            v2=a[p+1:]
            if len(v1)>0:
                first = float(v1)
            if len(v2)>0:
                second = float(v2)
    return (first,second)

# first parameter - time interval to plot
# negative value - time window in hours for live plot of few last hours
# positive value - time window in days for history plot
# if first parameter contans two numbers separated by ":"
# then first value is how many days to skip from the beginning of the file
# second value - how many days to show 
(timeframe_skip,timeframe) = getv2(1,0,-3)
if (timeframe>0):
    history_plot = True
    timeframe_sec = int(24*3600*(timeframe+timeframe_skip)+0.5) # convert days to seconds
    timedelta_skip = dt.timedelta(seconds=int(24*3600*timeframe_skip+0.5))
else:
    history_plot = False
    timeframe_sec = int(3600*(-timeframe)+0.5) # convert hours to seconds
    timedelta_skip =  dt.timedelta(0)
    if timeframe_sec<300:
        timeframe_sec = 300 # 5 min minumum 

very_last_time = dt.datetime.now()
very_last_day = very_last_time.strftime('%d.%m.%Y')
very_last_day_reversed = very_last_time.strftime('%Y-%m-%d')
last_time = very_last_time-dt.timedelta(seconds=timeframe_sec+600)
last_day = last_time.strftime('%d.%m.%Y')
last_day_reversed = last_time.strftime('%Y-%m-%d')
first_time = None

# second parameter - index of column to read from input file, counting from 0  
# if number is negative, abs(column) is used and value from file is multiplied by -1
column = int(getv(2,5))

prefix = 'E:/DCS_2' if os.name == 'nt' else '/mnt/DCS/DCS_2'
# 3rd parameter - file name
filename = getv(3,prefix+'/DCS2_2019_19_v3_34 '+last_day+' archiv_DCS2/'+last_day+'___magnetometr VES.txt')
filepos = 0

def update_date(opt=True):
    global last_time,last_day,very_last_day,last_day_reversed,very_last_day_reversed,filename,title,ax,filepos
    now = dt.datetime.now()
    today = now.strftime('%d.%m.%Y')
    if today != last_day:
        new_filename=''
        new_time = dt.datetime.strptime(last_day,'%d.%m.%Y') + dt.timedelta(days=1)
        while new_time<now:
            new_day = new_time.strftime('%d.%m.%Y')
            new_day_reversed = new_time.strftime('%Y-%m-%d')
            new_filename = filename.replace(last_day,new_day)
            if not opt: print('Checking',new_filename)
            if os.path.isfile(new_filename):
                break
            new_time += dt.timedelta(days=1)
        if os.path.isfile(new_filename):
            if opt and new_filename != filename and (last_day in title or very_last_day in title or last_day_reversed in title or very_last_day_reversed in title):
                ax.text(0.5, 1.025, title, color='white', bbox={'facecolor': 'white', 'edgecolor': 'white', 'pad': 2}, transform=ax.transAxes, ha='center', size='large')
                title = title.replace(last_day,new_day).replace(very_last_day,new_day)
                title = title.replace(last_day_reversed,new_day_reversed).replace(very_last_day_reversed,new_day_reversed)
                ax.text(0.5, 1.025, title, transform=ax.transAxes, ha='center', size='large')
            last_day = new_day
            very_last_day = new_day
            last_day_reversed = new_day_reversed
            very_last_day_reversed = new_day_reversed
            if new_filename != filename:
                filepos = 0
                filename = new_filename
                if opt: print('Opening ',new_filename)
                return True
        else:
            if not opt and new_time>=now:
                filepos = 0
                filename = new_filename
                return True
    return False

if history_plot or filename.find(last_day) == -1:
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
        with open(filename,'r') as file:
            try:
                line = file.readline()
                last_day = line.split()[0]
                last_time = dt.datetime.strptime(last_day,'%d.%m.%Y')
                last_day_reversed = last_time.strftime('%Y-%m-%d')
                first_time = last_time
                for line in file: pass
                very_last_day = line.split()[0]
                very_last_time =  dt.datetime.strptime(very_last_day,'%d.%m.%Y')
                very_last_day_reversed = very_last_time.strftime('%Y-%m-%d')
            except Exception as e:
                print(e)
else:
    if not os.path.isfile(filename):
        print('Checking',filename)
        while not update_date(False):
            pass

# 4th parameter - lower limit for alarm
try:
    lower_alarm_limit = float(getv(4,(1025 if history_plot else 1028.0)))
except:
    lower_alarm_limit = None
# 5th parameter - upper limit for alarm
try:
    upper_alarm_limit = float(getv(5,(1035 if history_plot else 1028.5)))
except:
    upper_alarm_limit = None

max_limit = 1.2e5

# 6th parameter - plot title
deftitle = 'M13 xx.xx.xxxx'
if 'M15' in filename:
    deftitle = 'M15 xx.xx.xxxx'
title = getv(6,deftitle)
title = title.replace('xx.xx.xxxx',very_last_day)
title = title.replace('xxxx-xx-xx',very_last_day_reversed)

# 7th parameter - options
options = getv(7,'')

save_plot = (options=='save')

if not os.path.isfile(filename):
    print('File "%s" not found' % filename)
    exit(1)
print('%s   %s   %s   "%s"   %s   %s   "%s"'%(sys.argv[0],timeframe,column,filename,lower_alarm_limit,upper_alarm_limit,title))
if not os.access(filename, os.R_OK):
    print('File "%s" is not readable' % filename)
    exit(1)
else:
    print('Opening ',filename)

n_values = 0
mean_value = 0
lower_limit = None
upper_limit = None

if history_plot:
    lower_limit = lower_alarm_limit
    upper_limit = upper_alarm_limit
    lower_alarm_limit = -max_limit
    upper_alarm_limit = max_limit
elif save_plot:
    lower_alarm_limit = -max_limit
    upper_alarm_limit = max_limit

# by default assuming one measurement every 30 seconds
timestep_sec = 30 # in seconds
with open(filename,'r') as file:
    lines = deque([],3)
    for line in file:
        lines.append(line)
        if first_time is None:
            first_time = dt.datetime.strptime(line.split()[0],'%d.%m.%Y')
    if len(lines)>2:
        try:
            t1 = float(re.sub(',','.',lines[0].split()[-1]))
            t2 = float(re.sub(',','.',lines[1].split()[-1]))
            delta = t2-t1
            if delta>0:
                if delta<10:
                    timestep_sec = int(delta+1)
                else:
                    timestep_sec = int((delta+2)/5)*5
            print('Found delta_t',delta,'using timestep_sec',timestep_sec)
        except Exception as e:
            print(e)

DCS1 = (timestep_sec == 100) or ('DCS_1' in filename)

#prepare alarm sound
A_freq = 440
Csh_freq = A_freq * 2 ** (4 / 12)
E_freq = A_freq * 2 ** (7 / 12)
# get timesteps for each sample, T is note duration in seconds
sample_rate = 44100
T = 1
t = np.linspace(0, T, T * sample_rate, False)
# generate sine wave notes
A_note = np.sin(A_freq * t * 2 * np.pi)
Csh_note = np.sin(Csh_freq * t * 2 * np.pi)
E_note = np.sin(E_freq * t * 2 * np.pi)
silence = np.zeros(sample_rate*T)
# concatenate notes
fragment = np.hstack((A_note, Csh_note, E_note, silence))
audio = np.hstack([fragment for _ in range(8)])
# normalize to 16-bit range
audio *= 32767 / np.max(np.abs(audio))
# convert to 16-bit data
audio = audio.astype(np.int16)

play_obj = None

dql = int(timeframe_sec/timestep_sec)+1
data_time = deque([],dql)
data_field = deque([],dql)

def check_length(opt=False):
    global dql,timeframe_sec,first_time,last_day,very_last_day,last_day_reversed,very_last_day_reversed,title
    if len(data_time)>=dql:
        return True
    if len(data_time)>0 and first_time is not None:
        dt = (data_time[-1]-first_time).total_seconds()
        if dt>=timeframe_sec:
            if opt and dt>timeframe_sec:
                data_time.pop()
                data_field.pop()
                if (last_day in title or very_last_day in title or last_day_reversed in title or very_last_day_reversed in title):
                    if len(data_time)>0 or first_time is not None:
                        if len(data_time)>0:
                            new_day = data_time[-1].strftime('%d.%m.%Y')
                            new_day_reversed = data_time[-1].strftime('%Y-%m-%d')
                        else:
                            new_day = first_time.strftime('%d.%m.%Y')
                            new_day_reversed = first_time.strftime('%Y-%m-%d')
                        ax.text(0.5, 1.025, title, color='white', bbox={'facecolor': 'white', 'edgecolor': 'white', 'pad': 2}, transform=ax.transAxes, ha='center', size='large')
                        title = title.replace(last_day,new_day).replace(very_last_day,new_day)
                        title = title.replace(last_day_reversed,new_day_reversed).replace(very_last_day_reversed,new_day_reversed)
                        ax.text(0.5, 1.025, title, transform=ax.transAxes, ha='center', size='large')
            return True
    return False

def read_from_file(print_last=True,print_bad=True):
    global first_time,last_time,filename,column,filepos,DCS1,dql,mean_value,n_values
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
        if history_plot and check_length():
            return False
        if os.path.getsize(filename)>filepos:
            if len(data_time)>0:
                last_time = data_time[-1]
                if first_time is None:
                    first_time = last_time
            with open(filename,'r') as file:
                #print('Old filepos',filepos,os.path.getsize(filename))
                file.seek(filepos)
                line = file.readline()
                lastline = ''
                while line:
                    lastline = line.strip()
                    try:
                        data = line.split()
                        if data[0] != 'Time' and not data[0].startswith('#'):
                            dt_point = dt.datetime.fromtimestamp(float(re.sub(',','.',data[-1]))-2082844800) # -2082844800 is "01-Jan-1904 00:00UTC" - "zero" time in LabView
                            if dt_point > last_time and (dt_point-first_time)>timedelta_skip:
                                try:
                                    if column<0:
                                        F_point = -float(re.sub(',','.',data[-column]))
                                    else:
                                        F_point = float(re.sub(',','.',data[column]))
                                except:
                                    F_point = max_limit
                                if set(data[2:-1])!={'0'} and not (DCS1 and set(data[2:-1])=={'0,000000E+0'}) and (F_point > -max_limit and F_point < max_limit):
                                    if lower_limit is not None and upper_limit is not None and F_point >= lower_limit and F_point <= upper_limit:
                                        mean_value += F_point
                                        n_values += 1
                                    data_time.append(dt_point)
                                    data_field.append(F_point)
                                else:
                                    if print_bad:
                                        print('Bad point:',line.strip())
                        filepos = file.tell() 
                    except Exception as e:
                        print(e)
                    if history_plot and check_length():
                        break
                    line = file.readline()
                if print_last and lastline:
                    if len(data_time)>0:
                        print('Last line:',lastline,' reading delay:',(dt.datetime.now()-data_time[-1]).total_seconds())
                    else:
                        print('Last line:',lastline)
                #print('New filepos',filepos,os.path.getsize(filename))
            if len(data_time)>0: very_last_time = data_time[-1]
            return True
    else:
        print('File "%s" is not readable' % filename)
    return False

fig, ax = plt.subplots()
fig.autofmt_xdate()
plt.minorticks_on()
plt.grid(True, which='both')
#plt.title(title)
ax.text(0.5, 1.025, title, transform=ax.transAxes, ha='center', size='large')

alarm = False
beep_on_alarm = True

def alarm_check():
    global alarm
    global play_obj
    if len(data_field)>0:
        if not alarm and (lower_alarm_limit > data_field[-1] or data_field[-1] > upper_alarm_limit):
            fig.set_facecolor('#FF0000')
            alarm = True
            if play_obj is not None and play_obj.is_playing():
                play_obj.stop()
            if beep_on_alarm:
                try:
                    play_obj = sa.play_buffer(audio, 1, 2, sample_rate)
                except:
                    play_obj = None
            #print('enable alarm')
            fig.canvas.draw_idle()
            fig.canvas.flush_events()
            return
        if alarm and (not beep_on_alarm or (lower_alarm_limit < data_field[-1] and data_field[-1] < upper_alarm_limit)):
            fig.set_facecolor('#FFFFFF')
            alarm = False
            if play_obj is not None and play_obj.is_playing():
                play_obj.stop()
            #print('disable alarm')
            fig.canvas.draw_idle()
            fig.canvas.flush_events()
            return
        if alarm and beep_on_alarm and play_obj is not None and not play_obj.is_playing():
            try:
                play_obj = sa.play_buffer(audio, 1, 2, sample_rate)
            except:
                play_obj = None

def plot(ax):
    alarm_check()
    ln, = ax.plot(data_time,data_field,'b.-')
    return ln

read_from_file(False,False)
if not (history_plot and check_length()):
    while update_date():
        read_from_file(False,False)
        if history_plot and check_length():
            break
if history_plot:
    check_length(True)

ln = plot(ax)

if history_plot:
    if lower_limit is not None and upper_limit is not None:
        if n_values>0:
            mean_value /= n_values
            ax.text(0.5, 0.93, 'Mean = %.2f' % mean_value, bbox={'facecolor': 'white', 'edgecolor': 'white', 'pad': 5}, transform=ax.transAxes, ha='center', size='large')
else:
    def update_mean():
        if len(data_field)>0:
            tb_last.set_val('{:7.2f}'.format(data_field[-1]))
            val = [data_field[-x-1] for x in range(min(10,len(data_field)))]
            aver = sum(val)/float(len(val))
            tb_mean.set_val('{:7.2f}'.format(aver))

    ax_last = fig.add_axes([0.06, 0.05, 0.1, 0.05])
    tb_last = TextBox(ax_last, 'Last')
    tb_last.set_active(0)
    ax_mean = fig.add_axes([0.3, 0.05, 0.1, 0.05])
    tb_mean = TextBox(ax_mean, 'Mean(10)')
    tb_mean.set_active(0)
    update_mean()

if save_plot and len(data_field)>0 and (not history_plot or lower_limit is None or upper_limit is None):
    max_=max(data_field)
    ax_max_ = fig.add_axes([0.48, 0.05, 0.1, 0.05])
    tb_max_ = TextBox(ax_max_, 'Max')
    if (max_<10):
        tb_max_.set_val('{:7.3f}'.format(max_))
    else:
        tb_max_.set_val('{:7.2f}'.format(max_))
    min_=min(data_field)
    ax_min_ = fig.add_axes([0.63, 0.05, 0.1, 0.05])
    tb_min_ = TextBox(ax_min_, 'Min')
    if (min_<10):
        tb_min_.set_val('{:7.3f}'.format(min_))
    else:
        tb_min_.set_val('{:7.2f}'.format(min_))
    maxmin=max_-min_
    ax_maxmin = fig.add_axes([0.8, 0.05, 0.1, 0.05])
    tb_maxmin = TextBox(ax_maxmin, 'Delta')
    if (maxmin<10):
        tb_maxmin.set_val('{:7.3f}'.format(maxmin))
    else:
        tb_maxmin.set_val('{:7.2f}'.format(maxmin))

def init():
    global timeframe_sec,history_plot,lower_limit,upper_limit
    #print('init')
    fmt='%Y-%m-%d %H:%M'
    if history_plot:
        if timeframe_sec<=24*3600:
            fmt='%H:%M'
    else:
        if timeframe_sec<=24*3600:
            fmt='%H:%M'
        elif timeframe_sec<=10*24*3600:
            fmt='%d %H:%M'
    #print('X-axis time format "%s"' % fmt)
    ax.xaxis.set_major_formatter(mdates.DateFormatter(fmt))
    if timeframe_sec<24*3600:
        ax.xaxis.set_minor_locator(mtick.AutoMinorLocator(5))
    if len(data_field)>0 and max(data_field)-min(data_field)>1:
        ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2f'))
    ax.tick_params(which='both',direction='in')
    ax.tick_params(which='major',width=2)
    if lower_limit is not None and upper_limit is not None:
        print('Y-axis limits: [%s, %s]' % (lower_limit,upper_limit))
        ax.set_ylim(lower_limit,upper_limit)
    return ln,#fig.get_children()[0]

if save_plot:
    init()
    name = title+'.png'
    name=name.replace(' ','_')
    print('Creating',name)
    plt.savefig(name)
    exit(0)
elif history_plot:
    init()
else:
    def update(c):
        update_date()
        if read_from_file():
            alarm_check()
            ln.set_data(data_time,data_field)
            update_mean()
            ax.relim()
            ax.autoscale_view()
        return ln,#fig.get_children()[0]
    
    ani = FuncAnimation(fig, update, frames=None,
                        interval=timestep_sec*200, # miliseconds, updating 5 times faster than new data appear
                        init_func=init, blit=False,
                        repeat=False, cache_frame_data=False
                        )
    
    def submit_lower(val):
        global lower_alarm_limit
        try:
            lower_alarm_limit = float(val)
        except:
            tb_lower.set_val(str(lower_alarm_limit))
        alarm_check()
    ax_lower = fig.add_axes([0.55, 0.05, 0.1, 0.05])
    tb_lower = TextBox(ax_lower, 'Lower lim')
    tb_lower.on_submit(submit_lower)
    tb_lower.set_val(str(lower_alarm_limit))
    
    def submit_upper(val):
        global upper_alarm_limit
        try:
            upper_alarm_limit = float(val)
        except:
            tb_upper.set_val(str(upper_alarm_limit))
        alarm_check()
    ax_upper = fig.add_axes([0.8, 0.05, 0.1, 0.05])
    tb_upper = TextBox(ax_upper, 'Upper lim')
    tb_upper.on_submit(submit_upper)
    tb_upper.set_val(str(upper_alarm_limit))
    
    ax_check = fig.add_axes([0.74, 0.89, 0.25, 0.1])
    check = CheckButtons(
        ax=ax_check,
        labels=['Beep on alarm'],
        actives=[beep_on_alarm]
    )
    def callback(label):
        global beep_on_alarm
        beep_on_alarm = not beep_on_alarm
        alarm_check()
    check.on_clicked(callback)
    
    def callback_autozoom(e):
        ax.set_autoscale_on(True)
        ax.relim()
        ax.autoscale_view()
    ax_autozoom = fig.add_axes([0.1, 0.89, 0.12, 0.05])
    btn_autozoom = Button(ax_autozoom, 'Autozoom')
    btn_autozoom.on_clicked(callback_autozoom)

plt.show()
