#!   /usr/bin/env   python
#    coding: utf8
# Copyright CERN, 2013
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org

# Import system modules
import sys
import time
import os
import random as rdm

# 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 *
from ctypes import *
from calibr_box import *
from cp210x_eeprom import *
from find_usb_tty import *
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *


"""
test28: Long term test

1) Clear DDR memory
2) Configure acquisition with random params
3) Configure waveform generator to generate a sine wave within the acquisition params
4) Perform acquisition
5) Fit acquired waveform to a sine wave and check that it's within boundaries
"""


################################################################################
# Constants declaration
################################################################################
TEST_NB = 28

# Gateware
FMC_ADC_BITSTREAM = '../firmwares/spec-fmc-adc-v1.0.bin'
EXPECTED_BITSTREAM_TYPE = 0x0

# Calibration box
BOX_USB_VENDOR_ID = 0x10c4 # Cygnal Integrated Products, Inc.
BOX_USB_PRODUCT_ID = 0xea60 # CP210x Composite Device
BOX_SET_SLEEP = 0.01

# Agilent AWG
AWG_USB_VENDOR_ID = 0x0403 # Future Technology Devices International, Ltd
AWG_USB_PRODUCT_ID = 0x6001 # FT232 USB-Serial (UART) IC
AWG_BAUD = 57600
AWG_SET_SLEEP = 0.3

# fmc-adc mezzanine
NB_CHANNELS = 4
SSR_SET_SLEEP = 0.05
DAC_SET_SLEEP = 0.01 # in [s]
SAMPLE_WIDTH = 2 # bytes
MEMORY_SIZE = 2^28/(NB_CHANNELS * SAMPLE_WIDTH) # samples
MULTISHOT_MAX_SIZE = 2^11 # samples
ACQ_TIMEOUT = 10
SAMP_FREQ = 100E6 # Hz

# Acquisition
ACQ_MIN_PERIODS = 2

# Input waveform
SINE_MIN_FREQ = 10E3 # Hz
SINE_MAX_FREQ = 1E6  # Hz
SINE_MIN_AMPL =  0.05 # Vpeak
SINE_MAX_AMPL =  5.0  # Vpeak


################################################################################
# Functions declaration
################################################################################

def clear_ddr(carrier, verbose=False):
    # dma item size = 4096 bytes
    # max linked items = 128
    # 2^25/(4096*128) = 64
    item_size = 4096
    max_items = 128
    pattern = 0x0
    carrier.set_irq_en_mask(0x1)
    if verbose:
        print("Clearing DDR memory")
    for i in range(64):
        carrier.put_data(i*item_size*max_items, pattern, item_size*max_items)
        if verbose:
            print("DMA #%d"%i)

def set_awg(awg, freq, ampl, offset, verbose=False):
    # Set sine params
    sine = SineWaveform()
    sine.frequency = freq # Hz
    sine.amplitude = ampl # Vpeak
    sine.dc = offset      # V
    if verbose:
        print('AWG Sine frequency:%3.3fMHz amplitude:%2.3fVp offset:%2.3fV')%(sine.frequency/1E6, sine.amplitude, sine.dc)
    # Set AWG
    awg.connect()
    awg.play(sine)
    awg.output = True

def get_best_input_range(in_ampl, term):
    # If the 50 ohms termination is enabled, input amplitude is reduced by a factor 2
    if term == 'ON':
        in_ampl = in_ampl/2.0
    in_range = '100mV'
    if in_ampl > 0.05:
        in_range = '1V'
    if in_ampl > 0.5:
        in_range = '10V'
    return in_range


def set_acq():
    # Set acquisition
    fmc.set_pre_trig_samples(PRE_TRIG_SAMPLES)
    fmc.set_post_trig_samples(POST_TRIG_SAMPLES)
    fmc.set_shots(NB_SHOTS)


    set_trig_config(hw_sel, hw_pol, hw_en, sw_en, channel, int_thres, delay)
    set_decimation(factor)
    set_input_term(channel, state)
    set_dc_offset(channel, value)
    set_input_range(channel, in_range)




def hex2signed(value):
    if(value & 0x8000):
        return -((~value & 0xFFFF) + 1)
    else:
        return value


def acq_config(fmc):
    print "Initialise FMC board\n"
    # Set trigger
    fmc.set_soft_trig()
    # Set acquisition
    fmc.set_pre_trig_samples(PRE_TRIG_SAMPLES)
    fmc.set_post_trig_samples(POST_TRIG_SAMPLES)
    fmc.set_shots(NB_SHOTS)


def acq_channels(fmc, carrier):
    # Make sure no acquisition is running
    fmc.stop_acq()
    # Start acquisition
    fmc.start_acq()
    time.sleep(0.01)
    # Trigger
    fmc.sw_trig()
    # Wait end of acquisition
    timeout = 0
    while('IDLE' != fmc.get_acq_fsm_state()):
        time.sleep(.1)
        timeout += 1
        if(ACQ_TIMEOUT < timeout):
            print "Acquisition timeout. Missing trigger?."
            print "Acq FSm state: %s"%fmc.get_acq_fsm_state()
            return 1
    # Retrieve data trough DMA
    trig_pos = fmc.get_trig_pos()
    print("Trigger position: 0x%8X" % trig_pos)
    #carrier.print_irq_controller_regs()
    # Enable "DMA done" interrupt
    carrier.set_irq_en_mask(0x1)
    #carrier.print_irq_controller_regs()
    raw_input("hit any key to start dma.")
    # Read ACQ_LENGTH samples after the trigger for all channels
    #channels_data = carrier.get_data((trig_pos<<3), ACQ_LENGTH*8)
    channels_data = carrier.get_data(0, ACQ_LENGTH*8)
    #for carrier_addr in range(0,10*4096, 4096):
    #    channels_data = carrier.get_data(carrier_addr, 0x1000)
    # Disable "DMA done" iinterrupt
    carrier.set_irq_en_mask(0x0)
    channels_data = [hex2signed(item) for item in channels_data]
    return channels_data


def acq_mean(acq_data):
    mean_d = []
    for channel in range(1,NB_CHANNELS+1):
        # calculate mean value for each channel
        mean_d.append(mean(acq_data[channel-1::4]))
    return mean_d

def plot_all(data, ylimit):
    sample = arange(len(data)/4)
    clf()
    plot(sample, data[0::4], 'b', label='Channel 1')
    plot(sample, data[1::4], 'g', label='Channel 2')
    plot(sample, data[2::4], 'c', label='Channel 3')
    plot(sample, data[3::4], 'm', label='Channel 4')
    ylim(-ylimit-(ylimit/10.0), ylimit+(ylimit/10.0))
    xlim(0, len(sample))
    grid(which='both')
    legend()
    draw()
    show()
    return 0


def main (default_directory='.'):


    start_test_time = time.time()
    print "================================================================================"
    print "Test%02d start\n" % TEST_NB

    # SPEC object declaration
    print "Loading hardware access library and opening device.\n"
    spec = rr.Gennum()

    # Load FMC ADC firmware
    bitstream_path = os.path.join(default_directory, FMC_ADC_BITSTREAM)
    #print "Loading FMC ADC firmware: %s\n" % bitstream_path
    #spec.load_firmware(bitstream_path)
    #time.sleep(2)

    # Carrier object declaration (SPEC board specific part)
    # Used to check that the firmware is loaded.
    try:
        carrier = CFmcAdc100mSpec(spec, EXPECTED_BITSTREAM_TYPE)
    except FmcAdc100mSpecOperationError as e:
        raise PtsCritical("Carrier init failed, test stopped: %s" % e)

    # Mezzanine object declaration (FmcAdc100m14b4cha board specific part)
    try:
        fmc = CFmcAdc100m(spec)
    except FmcAdc100mOperationError as e:
        raise PtsCritical("Mezzanine init failed, test stopped: %s" % e)


    try:

        # Others objects declaration
        usb_tty = CttyUSB()
        awg_tty = usb_tty.find_usb_tty(AWG_USB_VENDOR_ID, AWG_USB_PRODUCT_ID)
        box_tty = usb_tty.find_usb_tty(BOX_USB_VENDOR_ID, BOX_USB_PRODUCT_ID)
        awg = Agilent33250A(device=awg_tty[0], bauds=AWG_BAUD)
        box = CCalibr_box(box_tty[0])

        print("Clearing DDR memory...")
        t1 = time.time()
        #clear_ddr(carrier, False)
        t2 = time.time()
        print "elapsed time: %.2f seconds\n" % (t2-t1)

        print("Building input sine wave...")
        sine_freq = SINE_MIN_FREQ + ((SINE_MAX_FREQ-SINE_MIN_FREQ)*rdm.random())
        sine_ampl = SINE_MIN_AMPL + ((SINE_MAX_AMPL-SINE_MIN_AMPL)*rdm.random())
        sine_off = (SINE_MAX_AMPL-sine_ampl)*rdm.random()
        print("Input sine wave => Frequency: %3.3fMHz, Amplitude: %2.3fVp, Offset: %2.3fV"%(sine_freq/1E6, sine_ampl, sine_off))

        print("Setting AWG...")
        set_awg(awg, sine_freq, sine_ampl, sine_off)

        print("Choosing input termination...")
        adc_term = rdm.choice(['ON', 'OFF'])
        print("Input termination: %s"%adc_term)

        print("Finding best adc input range...")
        adc_range = get_best_input_range(sine_ampl, adc_term)
        print("Selected input range: %s"%adc_range)

        # min_periods = 2
        # samp_freq = 100E6
        # sine_freq = x
        # => min_samples = 2*samp_freq/sine_freq
        acq_min_samples =  ACQ_MIN_PERIODS*SAMP_FREQ/sine_freq
        print("Minimum number of samples: %d"%acq_min_samples)

        #acq_samples = acq_min_samples + 

        # acq_max_nb_shots = 


        #data = carrier.get_data(0, 50000*8)
        #plot_all(data, max(data))


        sys.exit()

        # Configuration
        acq_config(fmc)

        error = 0

        # All inputs in calibration mode
        for i in range(1, NB_CHANNELS+1):
            fmc.set_input_range(i, "CAL")

        # Disable test pattern (just in case)
        fmc.testpat_dis()


        # Acquire and read channels
        print("Acquire and read all channels.")
        acq_data = acq_channels(fmc, carrier)
        print("Read done.")
        acq_data_mean = acq_mean(acq_data)
        for i in range(len(acq_data_mean)):
            print "ADC channel %d mean value:%d" % (i+1, acq_data_mean[i])

        # Reset offset DACs
        fmc.dc_offset_reset()
        time.sleep(DAC_SET_SLEEP)

    except(FmcAdc100mSpecOperationError, FmcAdc100mOperationError) as e:
        raise PtsError("Test failed: %s" % e)


    print ""
    print "==> End of test%02d" % TEST_NB
    print "================================================================================"
    end_test_time = time.time()
    print "Test%02d elapsed time: %.2f seconds\n" % (TEST_NB, end_test_time-start_test_time)

    # Check if an error occured during offset DAC test
    if(error != 0):
        raise PtsError('An error occured during offset DAC test, check log for details.')


if __name__ == '__main__' :
    main()
