#!/usr/bin/env python
# -*- coding: utf-8 -*-

# @copyright  this code is the property of Ubertone.
# You may use this code for your personal, informational, non-commercial purpose.
# You may not distribute, transmit, display, reproduce, publish, license, create derivative works from, transfer or sell any information, software, products or services based on this code.
# @author Marie Burckbuchler, Stéphane Fischer

import logging
from math import ceil

from Model.peacock_uvp.apf04_gain import convert_dB_m2code, convert_code2dB_m, convert_code2dB, convert_dB2code, APF04_CODE_MAX_APPLIED
#from .ap_exception import ap_protocol_error
from Model.peacock_uvp.apf_type import cast_int16, cast_uint16

#ap_protocol_error(3300, "Warning: v_min has to be in [-Nyquist_Range, 0].")

class ConfigHw ():
	# @brief To instantiate an object of this class, you can give no parameter to get attributes set to zero, or you can give a settings, the ID of the config and the sound_speed to use.
	# TODO (san 21/04/2020) il serait surement plus approprié, plus propre (et plus compact) de stocker la configHW dans un dict 
	#      accompagné d'une liste donnant l'ordre des valeurs en mémoire :
	#      order = ['div_f0', 'n_tir', 'c_prf', 'n_em', 'n_vol', 'c_vol1', 'c_dvol' ...]  
	def __init__(self, _f_sys):
		
		logging.debug("f_sys = %.1e"%_f_sys)
		self.f_sys = _f_sys
		
		self.div_f0 = 0
		self.n_tir = 0
		self.c_prf = 0
		self.n_em = 0
		self.n_vol = 0
		self.c_vol1 = 0
		self.c_dvol = 0
		self.gain_ca0 = 0
		self.gain_ca1 = 0
		self.tr = 0
		self.phi_min = 0
		self.method = 0
		self.reserved1 = 0
		self.reserved2 = 0
		self.n_avg = 0
		self.blind_ca0 = 0
		self.blind_ca1 = 0

		
	def set(self, _config_data, _sound_speed=1480, _gain_blind_zone=None):
		"""
		docstring
		"""
		if type(_config_data) is dict :
			logging.debug ("call from_dict")
			self.from_dict(_config_data, _sound_speed, _gain_blind_zone)
		elif type(_config_data) is list :
			logging.debug ("call from_dict")
			self.from_list(_config_data)
		else :
			logging.info("wrong data type for _config_data")
		
		return self


	# @brief Chargement des paramètres d'une configuration acoustique
	# On rétrocalcule à chaque fois la valeur en paramètre utilisateur par rapport au paramètre hardware (pour prendre en compte les modifications dues à des cast etc, lorsqu'il y a interdépendance entre paramètres.)
	# @param _data : ref sur objet de la classe data donnant accès aux configs et au sound speed et à la fréquency système.
	# @param meas_ultrasound_key : clé de la configuration en cours.
	# appelé uniquement par le constructeur
	def from_dict(self, _config_dict, _sound_speed=1480, _gain_blind_zone=None):
		logging.debug("start import dict")
		self.div_f0 = cast_int16(self.f_sys / _config_dict['f0'] -1)
		f0 = self.f_sys / (self.div_f0+1)

		self.c_prf = cast_int16(f0 / _config_dict['prf'])
		prf = _config_dict['f0']/self.c_prf

		self.n_tir = cast_int16(_config_dict['n_ech'])

		# n_em is equal to 0 only if r_em = 0. If not, n_em is at least equal to 1.
		if _config_dict['r_em'] == 0:
			self.n_em = cast_int16(0)
		else:
			self.n_em = cast_int16(round(2./_sound_speed * f0 *_config_dict['r_em']))
			if self.n_em == 0:
				self.n_em = cast_int16(1)
		r_em = _sound_speed/(2.*f0)*self.n_em

		self.n_vol = cast_int16(_config_dict['n_vol'])

		self.c_vol1 = cast_uint16(2./_sound_speed * f0 * (_config_dict['r_vol1'] - r_em/2.))
		r_vol1 = _sound_speed/(2.*f0)*self.c_vol1 + r_em/2.

		self.c_dvol = cast_int16(2./_sound_speed * f0 *_config_dict['r_dvol'])
		if self.c_dvol < 2: # constraint from APF04 hardware
			self.c_dvol = cast_int16(2)
		r_dvol = _sound_speed/(2.*f0)*self.c_dvol
		
		self.gain_ca1 = cast_int16(convert_dB_m2code(_config_dict['gain_function']['a1'], r_dvol))
		a1 = convert_code2dB_m(self.gain_ca1, r_dvol)

		if _gain_blind_zone :
			self.blind_ca1 = cast_int16(convert_dB_m2code(_gain_blind_zone['a1_max'], r_dvol))
		else :
			self.blind_ca1 = 0
	#	Pour a1 max, on ne rétrocalcule pas dans le const, puisque c'est un const et que cette valeur n'est utile que quelques lignes plus loin dans le calcul du ca0_max.
		a1_max = convert_code2dB_m(self.blind_ca1, r_dvol)
		
		r_ny = _sound_speed*prf/(2*f0)
		self.phi_min = cast_int16(_config_dict['v_min']*65535/(2*r_ny))

		self.gain_ca0 = cast_int16(convert_dB2code(_config_dict['gain_function']['a0'] + r_vol1*a1))
		if _gain_blind_zone :
			self.blind_ca0 = cast_int16(convert_dB2code(_gain_blind_zone['a0_max'] + r_vol1 * a1_max))
		else :
			self.blind_ca0 = APF04_CODE_MAX_APPLIED

		# on a vu plus simple comme écriture ...
		self.tr = cast_int16(int(''.join(ele for ele in _config_dict['tr_out'] if ele.isdigit())))-1
		# TODO ça limite à 9, attention

		if _config_dict['method'] == "ppc_cont":
			self.burst_mode = False
		else:	# Donc method == "corr_ampl"
			self.burst_mode = True
		self.phase_coding = _config_dict['phase_coding']
		self.static_echo_filter = _config_dict['static_echo_filter']
		self.gain_auto = _config_dict['gain_function']['auto']
		
		# Pour retourner choisir le paramètres methode traitement, remplacer la dernière parenthèse par un 2 ou un 0.
		if(self.gain_auto == True):
			logging.debug("gain auto is set")
			# +2048 pour activer l'I2C (pour firmware >C51)
			self.method = cast_int16(512 + (cast_int16(self.static_echo_filter)<<8) + (cast_int16(self.phase_coding)<<2) + cast_int16(self.burst_mode) + (cast_int16(self.burst_mode)<<1))
		else:
			logging.debug("gain is set to manual")
			self.method = cast_int16(0 + (cast_int16(self.static_echo_filter)<<8) + (cast_int16(self.phase_coding)<<2) + cast_int16(self.burst_mode) + (cast_int16(self.burst_mode)<<1))

		self.n_avg = cast_int16(_config_dict['n_profile'])
		
		self.reserved1 = 0
		self.reserved2 = 0
		
	# @brief Chargement des paramètres à partir d'un tableau lu dans le Hardware
	# @param _param_table : tableau des valeurs dans l'ordre indiqué ci-dessous. cf. aussi dt_protocole de l'APF04.
	def from_list(self, _param_table):
		logging.debug("start import list")
		if len(_param_table)==17:
			self.div_f0 = _param_table[0]
			self.n_tir = _param_table[1]
			self.c_prf = _param_table[2]
			self.n_em = _param_table[3]
			self.n_vol = _param_table[4]
			self.c_vol1 = _param_table[5]
			self.c_dvol = _param_table[6]
			self.gain_ca0 = _param_table[7]
			self.gain_ca1 = _param_table[8]
			self.tr = _param_table[9]
			self.phi_min = _param_table[10]
			self.method = _param_table[11]
			self.n_avg = _param_table[14]
			self.blind_ca0 = _param_table[15]
			self.blind_ca1 = _param_table[16]

			# Other useful parameters (coded in method bits array) :
			if (self.method & 0x0001) == 0:
				self.burst_mode = False
			else:
				self.burst_mode = True
			# en self.method & 0x0002, il y a l'indication de méthode traitement.
			if (self.method & 0x0004) == 0:
				self.phase_coding = False
			else:
				self.phase_coding = True
			if (self.method & 0x0100) == 0:
				self.static_echo_filter = False
			else:
				self.static_echo_filter = True
			if (self.method & 0x0200) == 0:
				self.gain_auto = False
			else:
				self.gain_auto = True
		#else :
		#	logging.info("WARNING")
		# TODO raise error
	
	# @brief Update the config with the current config_hw.
	# @param _sound_speed: information of the sound speed
	def to_dict(self, _sound_speed):
		# TODO si div_f0 : pas initialisé -> ERROR
		config = {}
		f0_ = (self.f_sys/(self.div_f0+1))
		config['f0'] = f0_
		config['tr_out'] = 'tr'+str(self.tr+1)
		config['prf'] = f0_/self.c_prf
		config['r_vol1'] = _sound_speed*((self.c_vol1+self.n_em/2.)/f0_)/2.
		config['r_dvol'] = _sound_speed*(self.c_dvol/f0_)/2.
		config['n_vol'] = self.n_vol
		config['r_em'] = _sound_speed*(self.n_em/f0_)/2.
		config['n_ech'] = self.n_tir
		if self.burst_mode:
			config['method'] = "corr_ampl"
		else:
			config['method'] = "ppc_cont"
		# en self.method & 0x0002, il y a l'indication de méthode traitement.
		if self.phase_coding:
			config['phase_coding'] = True
		else:
			config['phase_coding'] = False
		if self.static_echo_filter:
			config['static_echo_filter'] = True
		else:
			config['static_echo_filter'] = False
		config['gain_function'] = {}
		if self.gain_auto:
			config['gain_function']['auto'] = True
		else:
			config['gain_function']['auto'] = False
		config['n_profile'] = self.n_avg
		
		rdvol = _sound_speed*(self.c_dvol/f0_)/2.
		rvol1 = _sound_speed*((self.c_vol1+self.n_em/2.)/f0_)/2.

		a1 = convert_code2dB_m(self.gain_ca1, rdvol)
		config['gain_function']['a0'] = convert_code2dB(self.gain_ca0)-a1*rvol1
		config['gain_function']['a1'] = a1

		config['v_min'] = 2*_sound_speed*config['prf']*self.phi_min/(2*65535*f0_)
		return config

	
	def get_bloc_duration(self):
		#TODO san 27/09/2017 attention ça augmente si n_vol> 100
		return self.n_tir * (self.n_avg) * (self.div_f0 + 1) * self.c_prf / self.f_sys


	# @brief Affichage du paramétrage en cours.
	def print_config_hw(self):
		logging.debug("div_F0 = %s", self.div_f0)
		logging.debug("n_tir = %s", self.n_tir)
		logging.debug("c_PRF = %s", self.c_prf)
		logging.debug("n_Em = %s", self.n_em)
		logging.debug("n_vol = %s", self.n_vol)
		logging.debug("c_vol1 = %s", self.c_vol1)
		logging.debug("c_dvol = %s", self.c_dvol)
		logging.debug("CA0_dac = %s", self.gain_ca0)
		logging.debug("CA1_dac = %s", self.gain_ca1)
		logging.debug("CA0_max_dac = %s", self.blind_ca0)
		logging.debug("CA1_max_dac = %s", self.blind_ca1)
		logging.debug("Cs_Tr = %s", self.tr)
		logging.debug("phi_min = %s", self.phi_min)
		logging.debug("Methode = %s", self.method)
		logging.debug("n_avg = %s", self.n_avg)
		# logging.debug("gain auto : %s", self.gain_auto)
		# logging.debug("static_echo_fiter : %s", self.static_echo_filter)
		# logging.debug("burst_mode : %s", self.burst_mode)
		# logging.debug("phase_coding : %s", self.phase_coding)


	def to_list(self):
		buf=[]
		buf.append(self.div_f0)
		buf.append(self.n_tir)
		buf.append(self.c_prf)
		buf.append(self.n_em)
		buf.append(self.n_vol)
		buf.append(self.c_vol1)
		buf.append(self.c_dvol)
		buf.append(self.gain_ca0)
		buf.append(self.gain_ca1)
		buf.append(self.tr)
		buf.append(self.phi_min)
		buf.append(self.method)
		buf.append(self.reserved1)
		buf.append(self.reserved2)
		buf.append(self.n_avg)
		buf.append(self.blind_ca0)
		buf.append(self.blind_ca1)
		return buf
	

	def __str__(self): 
		return str(self.__dict__)


	def __eq__(self, other):
		if not isinstance(other, type(self)):
			logging.info("NOT IMPLEMENTED")
			return NotImplemented
		
		return ((self.div_f0, self.tr, self.method, self.c_prf, self.phi_min, self.n_tir, self.c_vol1, self.c_dvol, self.n_em, self.n_vol, self.reserved1, self.reserved2, self.n_avg, self.gain_ca0, self.gain_ca1, self.blind_ca0, self.blind_ca1) == (other.div_f0, other.tr, other.method, other.c_prf, other.phi_min, other.n_tir, other.c_vol1, other.c_dvol, other.n_em, other.n_vol, other.reserved1, other.reserved2, other.n_avg, other.gain_ca0, other.gain_ca1, other.blind_ca0, other.blind_ca1))


	def __ne__(self, other):
		return not self == other
