#!   /usr/bin/env   python
#    coding: utf8

# Copyright CERN, 2011
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 10/5/2012

# Import system modules
import sys
import time
import os
import math

# Add common modules and libraries location to path
sys.path.append('../../../')
sys.path.append('../../../gnurabbit/python/')
sys.path.append('../../../common/')

# Import common modules
from ptsexcept import *
import rr

# Import specific modules
from fmc_adc_spec import *
from fmc_adc import *

from numpy import *
from pylab import *

import PyQt4
import PyQt4.QtGui
import PyQt4.QtCore
import PyQt4.uic
from PyQt4.QtGui import *
from PyQt4.QtCore import *

#import fmc_adc
#import spec_fmc_adc
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *


# Constants declaration
NB_POINTS = 1000

ADC_NBITS = 16 # ADC chip is 14 bits, but shifted to 16 bits in the firmware
ADC_FS = 10.0

USB_DEVICE = "/dev/ttyUSB0"
RS232_BAUD = 57600

FormClass = PyQt4.uic.loadUiType('fmc_adc_demo.ui')[0]

class MainWindow(QMainWindow, FormClass):
	def __init__(self):
		super(MainWindow, self).__init__()
		self.setupUi(self)

def bit2string(bit):
    if(1==bit):
        return 'YES'
    else:
        return 'NO'

# Converts two's complement hex to signed
def hex2signed(value):
    if(value & 0x8000):
        return -((~value & 0xFFFF) + 1)
    else:
        return value

# Converts digital value to volts
def digital2volt(value, full_scale, nb_bit):
    return float(value) * float(full_scale)/2**nb_bit

def acq_plot():
    if(1 == m.nb_shot.value()):
        trig_pos = fmc.get_trig_pos()
        pre_trig = m.pre_trig_samples.value()
        print('Single shot acq')
        carrier_addr = ((trig_pos-pre_trig+1)<<3)
        print('trig_pos:%.8X (%d) pre_trig:%.8X (%d)')%(trig_pos, trig_pos, pre_trig, pre_trig)
        print('carrier addr: %.8X (%d)')%(carrier_addr, carrier_addr)
        channels_data = carrier.get_data(carrier_addr, (NB_POINTS*8))
        nb_point_disp = NB_POINTS
    else:
        print('Multi-shot acq')
        channels_data = carrier.get_data(0, (NB_POINTS*8*m.nb_shot.value()))
        nb_point_disp = NB_POINTS * m.nb_shot.value()

    channels_data = [hex2signed(item) for item in channels_data]
    channels_data = [digital2volt(item,ADC_FS,ADC_NBITS) for item in channels_data]

    #channels_data = carrier.get_data(0, (NB_POINTS*8))
    print('Nb points:%d')%NB_POINTS
    print('channels data length:%d')%len(channels_data)
    sample = arange(len(channels_data)/4)
    m.mpl_acq.canvas.ax.clear()
    m.mpl_acq.canvas.ax.plot(sample, channels_data[0::4], 'b', label='Channel 1')
    m.mpl_acq.canvas.ax.plot(sample, channels_data[1::4], 'g', label='Channel 2')
    m.mpl_acq.canvas.ax.plot(sample, channels_data[2::4], 'r', label='Channel 3')
    m.mpl_acq.canvas.ax.plot(sample, channels_data[3::4], 'c', label='Channel 4')
    m.mpl_acq.canvas.ax.set_xlim(0, nb_point_disp)
    m.mpl_acq.canvas.ax.set_ylim(-(ADC_FS/2)-(ADC_FS/10), (ADC_FS/2)+(ADC_FS/10))
    m.mpl_acq.canvas.ax.legend()
    m.mpl_acq.canvas.draw()

def update_chan_cfg(channel):
    print('Update channel %d configuration')%channel
    term = bool(ch_term[channel-1].checkState())
    dis  = bool(ch_disconnect[channel-1].checkState())
    in_range = str(ch_range[channel-1].currentText())
    offset = ch_offset[channel-1].value()
    print('term:%d dis:%d in_range:%s offset:%d')%(term,dis,in_range,offset)
    fmc.set_dc_offset(channel, offset)
    if(dis):
        fmc.set_input_range(channel, 'OPEN')
    else:
        fmc.set_input_range(channel, in_range)
        if(term):
            fmc.set_input_term(channel, 'ON')
        else:
            fmc.set_input_term(channel, 'OFF')

def update_trig_cfg():
    print('Update trigger configuration')
    int_thres = m.int_trig_thres.value()
    int_sel = m.int_trig_sel.currentIndex() + 1
    hw_sel = m.hw_trig_sel.currentIndex()
    hw_pol = m.hw_trig_pol.currentIndex()
    hw_en = bool(m.hw_trig_en.checkState())
    sw_en = bool(m.sw_trig_en.checkState())
    delay = m.trig_delay.value()
    print('int_thres:%d int_sel:%d hw_sel:%d hw_pol:%d hw_en:%d sw_en:%d delay:%d')%(int_thres,int_sel,hw_sel,hw_pol,hw_en,sw_en,delay)
    fmc.set_trig_config(hw_sel, hw_pol, hw_en, sw_en, int_sel, int_thres, delay)
    #fmc.print_adc_core_config()
    #carrier.print_csr()

def update_acq_cfg():
    print('Update acquisition configuration')
    nb_shot = m.nb_shot.value()
    decim = m.decim.value()
    pre_trig_samples = m.pre_trig_samples.value()
    post_trig_samples = m.post_trig_samples.value()
    print('nb_shots:%d decim:%d pre-samples:%d post-samples:%d')%(nb_shot,decim,pre_trig_samples,post_trig_samples)
    fmc.set_shots(nb_shot)
    fmc.set_decimation(decim)
    fmc.set_pre_trig_samples(pre_trig_samples)
    fmc.set_post_trig_samples(post_trig_samples)

def update_cont_acq():
    #if(1 == bool(m.cont_acq.checkState())):
    pass

def on_sw_trig():
    print('SOFTWARE TRIGGER')
    fmc.sw_trig_no_wait()

def on_start_acq():
    print('START ACQ')
    fmc.start_acq()

def on_stop_acq():
    print('STOP ACQ')
    fmc.stop_acq()

def on_acq_data():
    print('ACQ DATA')
    #carrier.wait_end_acq()
    acq_plot()

def on_reset_offset():
    print('RESET DC OFFSET')
    fmc.dc_offset_reset()
    for channel in range(4):
        ch_offset[channel].setValue(32768)

def poll_timer_cb():
    m.acq_stat.setText(fmc.get_acq_fsm_state())
    m.acq_stat_2.setText(fmc.get_acq_fsm_state())
    m.serdes_synced.setText(bit2string(fmc.get_serdes_sync_stat()))
    m.nb_samples.setText(str(fmc.get_nb_samples()))
    trig_pos_s = "0x%X"%fmc.get_trig_pos()
    m.trig_pos.setText(trig_pos_s)
    m.ch1_value.setText(str(fmc.get_current_adc_value(1)))
    m.ch2_value.setText(str(fmc.get_current_adc_value(2)))
    m.ch3_value.setText(str(fmc.get_current_adc_value(3)))
    m.ch4_value.setText(str(fmc.get_current_adc_value(4)))
    temp = "%3.3f"%carrier.get_temp()
    temp += u'\u00B0'+'C'
    m.carrier_temp.setText(temp)
    temp = "%3.3f"%fmc.get_temp()
    temp += u'\u00B0'+'C'
    m.mezz_temp.setText(temp)
    trig_tag = fmc.get_utc_trig_tag()
    start_tag = fmc.get_utc_start_tag()
    stop_tag = fmc.get_utc_stop_tag()
    end_tag = fmc.get_utc_end_tag()
    trig_tag_s = "%09d.%010d s"%(trig_tag[2],(trig_tag[3]*8))
    start_tag_s = "%09d.%010d s"%(start_tag[2],(start_tag[3]*8))
    stop_tag_s = "%09d.%010d s"%(stop_tag[2],(stop_tag[3]*8))
    end_tag_s = "%09d.%010d s"%(end_tag[2],(end_tag[3]*8))
    m.trig_tag.setText(trig_tag_s)
    m.start_tag.setText(start_tag_s)
    m.stop_tag.setText(stop_tag_s)
    m.end_tag.setText(end_tag_s)


if __name__ == "__main__":

    app = QApplication(sys.argv)
    m = MainWindow()
    m.show()
    m.setWindowTitle("Fmc Adc 100Ms/s Demo")

    # Load firmware to FPGA
    default_directory = '.'
    path_fpga_loader = '../../../gnurabbit/user/fpga_loader';
    #path_firmware = '../firmwares/spec_fmcadc100m14b4cha_test.bin';
    path_firmware = '../firmwares/spec_fmcadc100m14b4cha.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print firmware_loader + ' ' + bitstream
    os.system( firmware_loader + ' ' + bitstream )
    time.sleep(2);

    # Objects declaration
    spec = rr.Gennum() # bind to the SPEC board
    fmc = CFmcAdc100m(spec)
    gen = Agilent33250A(device=USB_DEVICE, bauds=RS232_BAUD)
    sine = SineWaveform()
    carrier = CFmcAdc100mSpec(spec, 0x1)

    # Enable DMA interrupts (finished, error)
    carrier.set_irq_en_mask(0x3)

    # Set UTC
    current_time = time.time()
    utc_seconds = int(current_time)
    fmc.set_utc_second_cnt(utc_seconds)
    #print('UTC core seconds counter: %d')%fmc.get_utc_second_cnt()
    utc_coarse = int((current_time - utc_seconds)/8E-9)
    fmc.set_utc_coarse_cnt(utc_coarse)
    #print('UTC core coarse counter: %d')%fmc.get_utc_coarse_cnt()

    # Variables from GUI
    ch_range = [m.ch1_range, m.ch2_range, m.ch3_range, m.ch4_range]
    ch_term = [m.ch1_term, m.ch2_term, m.ch3_term, m.ch4_term]
    ch_disconnect = [m.ch1_disconnect, m.ch2_disconnect, m.ch3_disconnect, m.ch4_disconnect]
    ch_offset = [m.ch1_offset, m.ch2_offset, m.ch3_offset, m.ch4_offset]

    # Initialize display fields
    m.acq_stat.setText(fmc.get_acq_fsm_state())
    m.serdes_synced.setText(bit2string(fmc.get_serdes_sync_stat()))
    m.nb_samples.setText(str(fmc.get_nb_samples()))

    m.mpl_acq.canvas.ax.clear()
    m.mpl_acq.canvas.ax.set_xlim(0, NB_POINTS)
    m.mpl_acq.canvas.ax.set_ylim(0, 65535)
    m.mpl_acq.canvas.draw()

    m.statusbar.showMessage("Designed by: mcattin")


    # Initialize board with default GUI values
    update_trig_cfg()
    update_acq_cfg()
    for i in range(4):
        update_chan_cfg(i+1)


    # Connect events to callback functions
    ch_range[0].currentIndexChanged.connect(lambda :update_chan_cfg(1))
    ch_range[1].currentIndexChanged.connect(lambda :update_chan_cfg(2))
    ch_range[2].currentIndexChanged.connect(lambda :update_chan_cfg(3))
    ch_range[3].currentIndexChanged.connect(lambda :update_chan_cfg(4))

    ch_term[0].stateChanged.connect(lambda :update_chan_cfg(1))
    ch_term[1].stateChanged.connect(lambda :update_chan_cfg(2))
    ch_term[2].stateChanged.connect(lambda :update_chan_cfg(3))
    ch_term[3].stateChanged.connect(lambda :update_chan_cfg(4))

    ch_disconnect[0].stateChanged.connect(lambda :update_chan_cfg(1))
    ch_disconnect[1].stateChanged.connect(lambda :update_chan_cfg(2))
    ch_disconnect[2].stateChanged.connect(lambda :update_chan_cfg(3))
    ch_disconnect[3].stateChanged.connect(lambda :update_chan_cfg(4))

    ch_offset[0].valueChanged.connect(lambda :update_chan_cfg(1))
    ch_offset[1].valueChanged.connect(lambda :update_chan_cfg(2))
    ch_offset[2].valueChanged.connect(lambda :update_chan_cfg(3))
    ch_offset[3].valueChanged.connect(lambda :update_chan_cfg(4))

    m.reset_offset.clicked.connect(on_reset_offset)

    m.int_trig_thres.valueChanged.connect(update_trig_cfg)
    m.int_trig_sel.currentIndexChanged.connect(update_trig_cfg)
    m.hw_trig_sel.currentIndexChanged.connect(update_trig_cfg)
    m.hw_trig_pol.currentIndexChanged.connect(update_trig_cfg)
    m.hw_trig_en.stateChanged.connect(update_trig_cfg)
    m.sw_trig_en.stateChanged.connect(update_trig_cfg)
    m.trig_delay.valueChanged.connect(update_trig_cfg)

    m.nb_shot.valueChanged.connect(update_acq_cfg)
    m.decim.valueChanged.connect(update_acq_cfg)
    m.pre_trig_samples.valueChanged.connect(update_acq_cfg)
    m.post_trig_samples.valueChanged.connect(update_acq_cfg)

    m.sw_trig.clicked.connect(on_sw_trig)

    m.start_acq.clicked.connect(on_start_acq)
    m.stop_acq.clicked.connect(on_stop_acq)
    m.acq_data.clicked.connect(on_acq_data)

    #m.cont_acq.stateChanged.connect(update_cont_acq)


    # Setup a polling timer to automatically update status values
    poll_timer = QTimer()
    poll_timer.setInterval(200)
    poll_timer.timeout.connect(poll_timer_cb)
    poll_timer.start()

    # Starts Qt applic
    app.exec_()
