#!/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

# first parameter - time window in hours
timeframe = float(getv(1,3))
if timeframe<0.1:
    timeframe = 0.1
timeframe_sec = int(3600*timeframe+0.5)

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

# second parameter - index of column to read from input file, counting from 0  
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,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_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):
                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)
                ax.text(0.5, 1.025, title, transform=ax.transAxes, ha='center', size='large')
            last_day = new_day
            very_last_day = new_day
            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 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')
                for line in file: pass
                very_last_day = line.split()[0]
                very_last_time =  dt.datetime.strptime(very_last_day,'%d.%m.%Y')
            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
lower_alarm_limit = float(getv(4,1028))
# 5th parameter - upper limit for alarm
upper_alarm_limit = float(getv(5,1028.5))

# 6th parameter - plot title
title = getv(6,'M13 '+very_last_day)
title=title.replace('xx.xx.xxxx',very_last_day)

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

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)

# 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 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

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

def read_from_file():
    global last_time,filename,column,filepos,DCS1
    if os.path.isfile(filename) and os.access(filename, os.R_OK):
        if os.path.getsize(filename)>filepos:
            if len(data_time)>0: last_time = data_time[-1]
            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()
                        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:
                            if set(data[2:-1])!={'0'} and not (DCS1 and set(data[2:-1])=={'0,000000E+0'}) :
                                F_point = float(re.sub(',','.',data[column]))
                                data_time.append(dt_point)
                                data_field.append(F_point)
                            else:
                                print('Bad point:',line.strip())
                        filepos = file.tell() 
                    except Exception as e:
                        print(e)
                    line = file.readline()
                if lastline: print('Last line:',lastline,' reading delay:',(dt.datetime.now()-data_time[-1]).total_seconds())
                #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 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()
while update_date():
    read_from_file()
ln = plot(ax)

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()

def init():
    print('init')
    global timeframe
    if timeframe>30*24:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m %H:%M'))
    elif timeframe>24:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%d %H:%M'))
    else:
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
    ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2f'))
    ax.tick_params(which='both',direction='in')
    ax.tick_params(which='major',width=2)
    ax.xaxis.set_minor_locator(mtick.AutoMinorLocator(5))
    return ln,#fig.get_children()[0]

if options=='save':
    init()
    name = title+'.png'
    plt.savefig(name.replace(' ','_'))
    exit(0)

#if len(data_time)>0:
#    plt.draw()
#    delta = dt.datetime.now()-data_time[-1]
#    delta = dt.timedelta(seconds=timestep_sec+1) - delta
#    print('Sleeping %.2f seconds' % delta.total_seconds())
#    plt.pause(delta.total_seconds())
#    read_from_file()

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, #milliseconds, 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()
