#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Mar 19 09:29:29 2021

@author: glbramalho@gmail.com - Geraldo Ramalho
"""

import csv
# import warnings
from numpy.random import random as rnd
from numpy import exp
from colorama import Fore as fg, Back as bg, Style as st

# from datetime import datetime
from CTRL_PID import PID # used for the plant self.STRUCture
from PLANTsim_UTIL import *
from PLANTsim_MEMORY import Memory

################################### PLANT
##
###################################

class Plant:
    def __init__(self, plant={}):
        self.plant = plant
        self.state = None
        self.MV_MAX = 100
        self.ids = {}
        self.debug = []
        self.shutdown = False
        self.print_old_state = []
        self.KEYS = {'unt':['unity'],
                     'sec':['procvar','actuator','sensor'],
                     'as':['actuator','sensor'],
                     'prp':['type','pv','sp','mv','sys','bit','ctrl','noise',
                             'loss','active','enabled','flow','from_pv','sequence',
                             'to_pv','limits','label','inlet','if','word*'],
                     'cst':['switch','relay','controller','memory','transmitter',
                              'mixer'],
                     'var':['aux','level','temperature','ph','oxigen','flow','conductivity','density','humidity','turbidity'],
                }
        self.un={'level':'l','temperature':'`C','ph':'pH','flow':'l/s','oxigen':'%','humidity':'%','turbidity':'%'}

        self.STRUC = {'unity':{ 'enabled':(bool,),
                                'bit':(int,range(0,33)),
                                'sequence':(int,range(0,101)),
                                'label':(str,),
                                'procvar':{'type':(str,self.KEYS['var']),
                                            'pv':(int,range(0,1000)),
                                            'sp':(int,range(0,101)),
                                            'wordr_':(int,range(0,16)),
                                            'wordw_':(int,range(0,16)),
                                            'if':(str,),
                                            'sys':(str,),
                                            'limits':(tuple,),
                                            'noise':(float,),
                                            'loss':(float,),
                                            'bit':(bool,range(0,33)),
                                            'sequence':(bool,range(0,101)),
                                            'label':(str,),
                                            },
                                'actuator':{'type':(str,self.KEYS['cst']),
                                            'active':(bool,),
                                            'bit':(int,range(0,33)),
                                            'ctrl':(str,),
                                            'ctrl_par':(tuple,),
                                            'ctrl_pv':(str,),
                                            'from_pv':(str,),
                                            'to_pv':(str,),
                                            'mv':(str,range(0,101)),
                                            'wordw_':(int,range(0,16)),
                                            'flow':(float,),
                                            'inlet':(bool,),
                                            'label':(str,),
                                            },
                                'sensor':{'type':(str,self.KEYS['cst']),
                                        'active':(bool,),
                                        'bit':(int,range(0,33)),
                                        'from_pv':(str,),
                                        'label':(str,),
                                        }
                    }
                }

    def var(self,*type_var):
        """ returns a list of valid key variables
        """
        if isinstance(type_var,str): type_var = [ type_var]
        ret = []
        for tv in type_var:
            for x in self.KEYS['var']:
                ret.append(x+'_'+tv)
        return ret

    # returns the type of a key
    def key_type(self,key):
        """ returns the type of a key
        """
        for t in self.KEYS:
            if any([x in key or key in x for x in self.KEYS[t]]):
                break
        return t

    def contains_a_key_type(self,key_type,elements):
        """ verify if an element contains a key
        """
        return any([x in elements for x in self.KEYS[key_type]])

    def debug_add(self, *args):
        self.debug.append(args)

    def words_addr(self,uu,rr,dd):
        """ handling wordr_pv and wordw_sp -  used in MEMORY class
        """
        ret = []
        addr = [ x if 'word' in x else None for x in self.r(uu,rr,dd)]
        if any(addr):
            for xx in addr:
                if not xx is None:
                    t,nm = xx.split('_')
                    ret.append([xx,t[-1] in 'wW',nm])
        return ret

    # handling from_pv / from_unity
    def id_expand(self,var,id_unity=None):
        id_pv = var
        if var is not None:
            if '.' in var:
                id_unity,id_pv = var.split('.')
            # else:
                # raise Exception ('*** ID_EXPAND: invalid id!')
        return id_unity,id_pv

    # read/write data from/to plant structure
    def rw(self,sentence,write_value=None):
        """ This functions sets values or returns multiples data formats (value,list, dict)
        uu = unity id
        rr = section
        dd = procvar
        pp = property

        input:      output:
        uu.rr.dd.pp property value
        uu.dd.pp    property value
        uu.pp       list of values of property 'pp'; except for uu = 'unity'
        uu          dict
        uu.rr       dict of unity 'uu' and section 'rr'
        rr          dict of section 'rr' for all unities
        rr.dd       list of property values for section 'rr' for all unities
        """
        names = sentence.split('.')
        # print('+++',names)
        uu,rr,dd,pp,value = [None]*5
        val_list = []
        debug = 0
        if names: #1
            uu = names.pop(0)
            if not names and uu in self.KEYS['sec']:
                rr = uu
                val_list={}
                for uu in self.plant:
                    if self.plant[uu].get(rr):
                        value = self.plant[uu][rr]
                        # print('+++',uu,rr,value)
                        if not val_list.get(uu):
                            val_list[uu]={}
                        if not val_list[uu].get(rr):
                            val_list[uu][rr]={}
                        # print('+++ val_list',val_list)
                        val_list[uu][rr] = value
                return val_list
            elif names and uu in self.KEYS['sec']:
                rr = uu
                pp = names.pop(0)
                for uu in self.plant:
                    if self.plant[uu].get(rr):
                        for dd in self.plant[uu][rr]:
                            value = self.plant[uu][rr][dd].get(pp)
                            # print('+++3',uu,rr,dd,'names',names,'value',value)
                            if value is not None:
                                val_list.append(value)
                return val_list

            value = self.plant.get(uu)
            if names: #2
                rr = names.pop(0)
                if rr in self.KEYS['prp']:
                    pp = rr
                    dd = uu
                    for rr in self.KEYS['unt']:
                        value = self.plant[uu][rr][uu].get(pp)
                        if value is not None: break
                    if value is None:
                        for rr in self.KEYS['sec']:
                            if self.plant[uu].get(rr):
                                for dd in self.plant[uu][rr]:
                                    value = self.plant[uu][rr][dd].get(pp)
                                    if value is not None:
                                        val_list.append(value)
                elif rr in self.KEYS['unt']:
                    value = self.plant[uu].get(rr)
                    dd = uu
                    if names and names[0] == dd:
                        names.pop(0)
                    # print('+++2',uu,rr,dd,'names',names,'value',value)
                elif rr in self.KEYS['sec']:
                    value = self.plant[uu].get(rr)
                else:
                    dd = rr
                    # print('+++2',uu,rr,dd,'names',names)
                    for rr in self.KEYS['sec']:
                        tmp = self.plant[uu].get(rr)
                        if tmp is not None:
                            value = self.plant[uu][rr].get(dd)
                            if value is not None: break
                if names: #3
                    if dd is None:
                        dd = names.pop(0)
                        value = self.plant[uu][rr].get(dd)
                        if not names and rr in self.KEYS['sec'] and dd in self.KEYS['prp']:
                            pp = dd
                            # print('+++3',uu,rr,dd,'names',names,'value',value)
                            for dd in self.plant[uu][rr]:
                                value = self.plant[uu][rr][dd].get(pp)
                                if value is not None:
                                    val_list.append(value)
                    if names: #4
                        pp = names.pop(0)
                        if pp == dd and names:
                            pp = names.pop(0)
                        # print('+++4',uu,rr,dd,pp,'names',names,'value',value)
                        if self.plant[uu].get(rr):
                            if self.plant[uu][rr].get(dd):
                                value = self.plant[uu][rr][dd].get(pp)
        if val_list:
            return val_list
        if write_value is None:
            return value
        else:
            self.plant[uu][rr][dd][pp] = write_value

    def r(self,*sentence):
        """ provides easy access to read the plant structure data
            widely used in this class and by others
        """
        if len(sentence)>0:
            if len(sentence)==1:
                if isinstance(sentence[0],list): sentence = '.'.join(sentence[0])
                else:
                    sentence = str(sentence[0])
            else:
                if sentence[0] in sentence[1]: sentence = sentence[1:] # eliminates unity if already exisits in the id

                sentence = '.'.join(sentence)

            return self.rw(sentence.replace(' ','.'), write_value=None)
        else:
            return self.plant

    def w(self,*sentence):
        """ provides easy access to write the plant structure data
            widely used in this class and by others
        """
        value = None
        if len(sentence)==2:
            value = sentence[1]
            sentence = str(sentence[0])
        else:
            if sentence[0] in sentence[1]: sentence = sentence[1:] # eliminates unity if already exisits in the id
            value = sentence[-1]
            sentence = '.'.join(sentence[:-1])
        return self.rw(sentence.replace(' ','.'),write_value=value)

    # update parameters from file
    def update_params(self):
       # adjust  SYS and CTRL parameters
        """ updates the data in the plant structure
        """
        for uu in self.r():
            for rr in self.r(uu):
                for dd in self.r(uu,rr):
                    if self.r(uu,rr,dd) is not None:

                        if self.r(uu,rr,dd,'sys'):
                            self.r(uu,rr,dd,'sys').pv = self.r(uu,rr,dd,'pv')
                            self.r(uu,rr,dd,'sys').limits = self.r(uu,rr,dd,'limits')
                            self.r(uu,rr,dd,'sys').noise = self.r(uu,rr,dd,'noise')
                            self.r(uu,rr,dd,'sys').loss = self.r(uu,rr,dd,'loss')
                        if  self.r(uu,rr,dd,'ctrl'):
                            ctrl_unity_id,ctrl_pv_id = self.id_expand( self.r(uu,dd,'ctrl_pv'),id_unity=uu ) # ALTERADO from -> to
                            unity_id,pv_id = self.id_expand( self.r(uu,dd,'to_pv'),id_unity=uu ) # ALTERADO from -> to
                            if pv_id is None:
                                unity_id,pv_id = self.id_expand( self.r(uu,dd,'from_pv'),id_unity=uu ) # ALTERADO from -> to
                            if ctrl_pv_id is None:
                                ctrl_unity_id,ctrl_pv_id = unity_id,pv_id

                            setpoint = self.r(ctrl_unity_id,'procvar',ctrl_pv_id,'sp')
                            if not setpoint: setpoint = 0
                            self.r(uu,rr,dd,'ctrl').setpoint = setpoint

                            self.r(uu,rr,dd,'ctrl').output_limits = (0,self.MV_MAX)
                            self.r(uu,rr,dd,'ctrl').sample_time = 0.01
                            self.r(uu,rr,dd,'ctrl').tunings = self.r(uu,rr,dd,'ctrl_par') if self.r(uu,rr,dd,'ctrl_par') else [0,0,0]
                            inlet = self.r(uu,rr,dd,'inlet')
                            if inlet is not None and not inlet:
                                self.r(uu,rr,dd,'ctrl').set_error(lambda e: -e)

    # print plant definition file
    def print_file(self,file_name):
        """ show the plant definition file in colors
        """
        ids = {}
        # load plant self.STRUCture
        with open(file_name,'r') as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=' ')
            uu = ''
            rr = ''
            dd = ''
            spc = ' '*8
            for row in csv_reader:
                while '' in row: row.remove('')
                if not row:
                    print('')
                    continue
                if '#' in row[0]:
                    print(fg.WHITE,' '.join(row))
                    continue
                spc,n,c = ' '*8,0,fg.BLUE+st.BRIGHT
                if self.contains_a_key_type('unt',row):
                    spc,n,c = '',1,fg.BLUE+st.BRIGHT
                if self.contains_a_key_type('sec',row):
                    spc = ' '*4
                print(spc, end='')
                for i,r in enumerate(row):
                    if len(row)>10 and i in [9,17]:
                        print('')
                        print(spc,' '*len(row[0]), end='')
                    color = fg.RESET
                    if i == n:
                        color = c
                        ids[r] = uu
                    if r in self.KEYS['unt'] or r in self.KEYS['sec']:
                        color = fg.YELLOW+st.BRIGHT
                        uu = r
                        r = r.upper()
                    if r in ids or '.' in r or '>' in r or '<' in r or '<=' in r or '>=' in r or '==' in r:
                        color = c
                    if r in ['false','true']:
                        r = r.capitalize()
                    try:
                        if isinstance(eval(r),(int,float,tuple,list)) or callable(eval(r)):
                            color = fg.GREEN+st.BRIGHT
                    except:
                        pass
                    if r in self.KEYS['prp'] or 'word' in r or 'ctrl' in r:
                        color = fg.CYAN
                        prop = r
                    # if r in self.KEYS['cst']:
                    if self.contains_a_key_type('cst',r) or self.contains_a_key_type('var',r):
                        color = fg.GREEN
                        if len(self.STRUC['unity'][uu][prop])>1:
                            valok = r in self.STRUC['unity'][uu][prop][1]
                        else:
                            valok = True
                        if not valok: color = fg.RED+st.BRIGHT
                    print(color, r, st.RESET_ALL, sep='', end=' ')
                print(st.RESET_ALL)

    # load plant definition from file
    def load(self,file_name):
        """ read the plant definition file
            completes missing properties
            creates ids structure - used to eval expressions
            fixes incomplete ids (unity.id)
        """
        # load plant self.STRUCture
        with open(file_name,'r') as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=' ')
            self.plant = {}
            uu = ''
            rr = ''
            dd = ''
            for row in csv_reader:
                while '' in row: row.remove('') # remove empty elements
                if '#' in row: row = row[:row.index('#')] # remove comments
                if not row: continue
                #
                if row[0] in self.STRUC: # 1st. level: unity
                    uu = row[1] # unity id
                    rr = ''
                    dd = ''
                    self.plant[uu] = {}
                    # this is to ensure provisory compatibility with the old structure
                    rr = 'procvar'
                    if self.plant[uu].get('procvar') is None:
                        self.plant[uu]['procvar'] = {}#{'unity':{}}
                        self.plant[uu]['unity'] = {uu:{}}# {'name':uu}
                    #
                    for nm,val in zip(row[2::2],row[3::2]):
                        val = val.replace('false','False')
                        val = val.replace('true','True')
                        valok = True
                        try:
                            val = eval(val)
                        except:
                            pass
                        # print('+++',nm,self.STRUC['unity'][nm])
                        ok = isinstance(val, (self.STRUC['unity'][nm][0], str) )
                        if len(self.STRUC['unity'][nm])>1:
                            valok = val in self.STRUC['unity'][nm][1]
                            valid = self.STRUC['unity'][nm]

                        if (nm in self.STRUC['unity']) or ( 'word' in nm  and nm[:6] in self.STRUC['unity'] ):
                            # do not remove:
                            self.plant[uu]['unity'][uu][nm] = val
                            # self.plant[uu]['procvar']['unity'][nm] = val
                        #
                        if not valok:
                            raise Exception('*** LOAD: value is invalid!',uu,dd,nm,val,' valid =',valid)
                else:
                    if row[0] in self.STRUC['unity']: # 2st. level: procvar,actuator,sensor
                        rr = row[0]
                        dd = ''
                        if self.plant[uu].get(rr) is None:
                            self.plant[uu][rr] = {}
                    else: # 3st. level: variables
                        dd = row[0]
                        if self.plant[uu][rr].get(dd) is None:
                            self.plant[uu][rr][dd] = {}
                    for nm,val in zip(row[1::2],row[2::2]):
                        val = val.replace('false','False')
                        val = val.replace('true','True')
                        valok = True
                        try:
                            val = eval(val)
                        except:
                            pass
                        ok = False
                        if nm in self.STRUC['unity'][rr]:
                            ok = isinstance(val, self.STRUC['unity'][rr][nm][0] ) or callable(val)
                            if len(self.STRUC['unity'][rr][nm])>1:
                                valok = val in self.STRUC['unity'][rr][nm][1] or val in self.var('calc','var')
                                valid = self.STRUC['unity'][rr][nm]
                        if not ok and nm[:6] in self.STRUC['unity'][rr]:
                            ok = isinstance(val, self.STRUC['unity'][rr][nm[:6]][0] ) or callable(val)
                            if len(self.STRUC['unity'][rr][nm[:6]])>1:
                                valok = val in self.STRUC['unity'][rr][nm[:6]][1] or val in self.var('calc','var')
                                valid = self.STRUC['unity'][rr][nm[:6]]
                        if dd:
                            if (nm in self.STRUC['unity'][rr]) or ( 'word' in nm  and nm[:6] in self.STRUC['unity'][rr] ):
                                self.plant[uu][rr][dd][nm] = val
                        if not valok:
                            raise Exception ('*** LOAD: value is invalid!',uu,dd,nm,val,' valid =',valid)

        # complete missing properties and makes adjustments
        ids = {}
        for uu in self.plant:
            ids[uu] = []
            for rr in self.plant[uu]:
                if rr not in self.KEYS['sec']: continue
                for dd in self.plant[uu][rr]:
                    # print('+++ ',uu,rr,dd)
                    if self.r(uu,rr,dd,'label') is None: self.plant[uu][rr][dd]['label']=dd
                    if rr in ['unity']:
                        # if self.r(uu,rr,'enabled') is None: self.plant[uu][rr]['enabled']=False
                        if self.r(uu,rr,dd,'enabled') is None: self.plant[uu][rr][dd]['enabled']=False
                        if self.r(uu,rr,dd,'sequence') is None: self.plant[uu][rr][dd]['sequence']=0
                    if rr in ['procvar']:
                    # if rr in ['procvar'] and dd not in ['unity']:
                        ids[uu].append(dd)
                        if self.r(uu,rr,dd,'type') is None: self.plant[uu][rr][dd]['type']='level_var'
                        if self.r(uu,rr,dd,'type') in self.var('calc'):
                            if self.r(uu,rr,dd,'sys') is None: self.plant[uu][rr][dd]['sys']=flow_meter()
                            if self.r(uu,rr,dd,'pv') is None: self.plant[uu][rr][dd]['pv']=0
                            if self.r(uu,rr,dd,'sp') is None: self.plant[uu][rr][dd]['sp']=0
                            if self.r(uu,rr,dd,'limits') is None: self.plant[uu][rr][dd]['limits']=[0,15]
                            if self.r(uu,rr,dd,'noise') is None: self.plant[uu][rr][dd]['noise']=0.0
                            if self.r(uu,rr,dd,'loss') is None: self.plant[uu][rr][dd]['loss']=0.0
                        if self.r(uu,rr,dd,'type') in self.var('var'):
                            if self.r(uu,rr,dd,'sys') is None: self.plant[uu][rr][dd]['sys']=generic()
                            if self.r(uu,rr,dd,'pv') is None: self.plant[uu][rr][dd]['pv']=0
                            if self.r(uu,rr,dd,'limits') is None: self.plant[uu][rr][dd]['limits']=[0,100]
                            if self.r(uu,rr,dd,'sp') is None: self.plant[uu][rr][dd]['sp']=max(self.plant[uu][rr][dd]['limits'])
                            if self.r(uu,rr,dd,'noise') is None: self.plant[uu][rr][dd]['noise']=0.0
                            if self.r(uu,rr,dd,'loss') is None: self.plant[uu][rr][dd]['loss']=0.0
                            if self.r(uu,rr,dd,'if') is None:
                                self.plant[uu][rr][dd]['if']=None
                            else:
                                self.plant[uu][rr][dd]['limits']=[0,1]
                                self.plant[uu][rr][dd]['noise']=0.0
                                self.plant[uu][rr][dd]['loss']=0.0
                                self.plant[uu][rr][dd]['pv']=0
                                self.plant[uu][rr][dd]['sp']=max(self.plant[uu][rr][dd]['limits'])
                                self.plant[uu][rr][dd]['sys']=generic()

                    if rr in ['actuator','sensor']:
                        ids[uu].append(dd)
                        if self.r(uu,rr,dd,'active') is None: self.plant[uu][rr][dd]['active']=False
                        if rr in ['actuator']:
                            if self.r(uu,rr,dd,'type') is None: self.plant[uu][rr][dd]['type']='relay'
                            if self.r(uu,rr,dd,'type') not in ['memory']:
                                if self.r(uu,rr,dd,'flow') is None: self.plant[uu][rr][dd]['flow']=0.1
                            if self.r(uu,rr,dd,'type') in ['controller']:
                                if self.r(uu,rr,dd,'mv') is None: self.plant[uu][rr][dd]['mv']=0
                                if self.r(uu,rr,dd,'ctrl') is None: self.plant[uu][rr][dd]['ctrl']=PID()
                                # ctrl_par=None means PID is disabled and mv defines the aperture => if self.r(uu,rr,dd,'ctrl_par') is None: self.plant[uu][rr][dd]['ctrl_par']=[0.5,0.0,0.0]
                        if rr in ['sensor']:
                            if self.r(uu,rr,dd,'type') is None: self.plant[uu][rr][dd]['type']='switch'

                        if self.r(uu,rr,dd,'type') in ['controller','relay','transmitter']:
                            from_unity_id,from_pv_id = self.id_expand( self.r(uu,rr,dd,'from_pv'), id_unity=uu)
                            to_unity_id,to_pv_id = self.id_expand( self.r(uu,rr,dd,'to_pv'), id_unity=uu)
                            if to_pv_id is None:
                                to_unity_id,to_pv_id = from_unity_id,from_pv_id
                            from_pv = from_unity_id+'.'+from_pv_id
                            to_pv = to_unity_id+'.'+to_pv_id
                        if self.r(uu,rr,dd,'type') in ['controller','relay','transmitter']:
                            self.plant[uu][rr][dd]['from_pv'] = from_pv
                            self.plant[uu][rr][dd]['to_pv'] = to_pv
                        if self.r(uu,rr,dd,'type') in ['switch']:
                            self.plant[uu][rr][dd]['from_pv'] = from_pv
                        nominal_flow = self.r(uu,rr,dd,'flow')
                        if self.r(uu,rr,dd,'type') in ['controller','relay']:
                            ctrl_pv = self.r(uu,rr,dd,'ctrl_pv')
                            actuator_id = self.r(to_unity_id,to_pv_id,'if')
                            if actuator_id:
                                if ctrl_pv is None:
                                    ctrl_pv =  self.r(to_unity_id,actuator_id,'to_pv')
                                nominal_flow =  self.r(to_unity_id,actuator_id,'flow')
                            if ctrl_pv is None:
                                ctrl_pv = to_pv
                        if self.r(uu,rr,dd,'type') in ['controller']:
                            self.plant[uu][rr][dd]['ctrl_pv'] = ctrl_pv
                        flow = self.r(uu,rr,dd,'flow')
                        if flow is not None:
                            if flow>nominal_flow: self.plant[uu][rr][dd]['flow'] = nominal_flow

        # complete all ids (unity.id)
        for uu in self.plant:
            for rr in self.plant[uu]:
                # if rr not in self.KEYS['sec']: continue
                for dd in self.plant[uu][rr]:
                    for pp in self.plant[uu][rr][dd]:
                        value = self.plant[uu][rr][dd][pp]
                        if pp in ['enabled','active','ctrl_pv','from_pv','to_pv','if'] :
                            for i in ids[uu]:
                                if isinstance(value,str):
                                    if '.'+i not in value and i in value:
                                        new_id = '.'.join([uu,i])
                                        self.plant[uu][rr][dd][pp] = value.replace(i, new_id)

        self.update_params()

        self.update_ids()

        # verify structure
        self.verify()

    def verify(self):
        """ verify plant structure
        TODO verify and finish the code
        """

        # check for inconsistency that may cause odd behavior or exceptions
        warning = []
        pv_id = {}
        for uu in self.plant:
            pv_id[uu]=[]
            for rr in self.plant[uu]:
                for dd in self.plant[uu][rr]:
                    if rr in ['procvar']:
                        if self.r(uu,rr,dd,'sys') is not None:
                            pv_id[uu].append(dd)
                        # prop_type = 'sys'
                        # if self.r(uu,rr,dd,prop_type) is None: prop_type = 'unity'

                    if rr in ['actuator','sensor']:
                        from_unity,vv = self.id_expand( self.r(uu,rr,dd,'from_pv'), id_unity=uu)
                        if vv is None:
                            if self.r(uu,rr,dd,'ctrl') is not None:
                                warning.append([uu,rr,dd,'does not contains a pv id ',None,None])
                        else:
                            if from_unity is not None:
                                if self.r(from_unity) is not None:
                                    if self.r(from_unity,'procvar',vv) is None:
                                        warning.append([uu,rr,dd,'does not contains a valid pv id:',vv,'from',from_unity])
                        prop_type = 'ctrl'
                        if self.r(uu,rr,dd,prop_type) is None: prop_type = 'onoff'

                    # # verify
                    # for pr in DEFAULT[rr][prop_type]:
                    #     if self.r(uu,rr,dd,pr) is None:
                    #         value = DEFAULT[rr][prop_type][pr]
                    #         if value is not None:
                    #             warning.append([uu,rr,dd,'does not contains a "{}" definition. Assuming external control.'.format(pr),None,None])
                    #             # self.w(uu,rr,dd,pr, value)

        # show inconsistency
        if warning:
            # warnings.warn('PLANT.LOAD warnings:')

            print('PLANT.load warnings:')
            for w in warning:
                uu = [x for x in w if x is not None]
                print('  -',' '.join(uu))
                if w[-1] is not None:
                    print('    valid',w[-1],'pv ids are:',', '.join(pv_id[w[-1]]))
                elif w[-2] is not None:
                    print('    valid pv ids are:',', '.join(pv_id[w[0]]))

        invalid_ids = []
        for rr in self.KEYS['sec'][1:]:
            for pp in self.STRUC['unity']['actuator']:
                if 'pv' in pp:
                    ids = self.r(rr,pp)
                    if ids:
                        error = [ x for x in ids if x not in self.ids]
                        if error:
                            invalid_ids.append([rr,pp,error])
        if invalid_ids:
            # warnings.warn('PLANT.LOAD warnings:')
            print('PLANT.load error:')
            print('Section','\t','Property','\t','Value')
            for s,p,v in invalid_ids:
                print(s,'\t',p,'\t',v)
            raise Exception('*** PLANT.load error!')

    def unity(self):
        return self.ids['unity']

    def prop(self):
        return self.ids['procvar']

    def actuator(self):
        return self.ids['actuator']

    def sensor(self):
        return self.ids['sensor']

    def update_ids(self):
        """ update ids structure with the plant values
        """
        self.state = []
        self.ids = {}
        for uu in self.r():
            # uu_enabled = self.r(uu,'unity',uu,'enabled')
            for rr in self.r(uu):
                # if rr not in self.KEYS['sec']: continue
                for dd in self.r(uu,rr):
                    value = self.r(uu,rr,dd,'enabled')
                    # value = uu_enabled
                    if value is None:
                        value = self.r(uu,rr,dd,'active')
                        if value is None:
                            value = self.r(uu,rr,dd,'pv')
                    # args
                    args = self.r(uu,rr,dd)
                    # print('+++',uu,rr,dd)
                    for a in args:
                        self.ids[uu+'.'+dd+'.'+a] = args[a]
                        if a in ['pv','active','enabled']:
                            if uu == dd:
                                self.ids[uu] = args[a]
                            else:
                                self.ids[uu+'.'+dd] = args[a]
                        if a in ['limits']:
                            self.ids[uu+'.'+dd+'.range'] = diff(args[a])

    def eval_exp(self,expression,value=None):
        """ evaluate expressions as the 'eval' python function
            using unity.id as variable names
        """
        exp = expression
        if value is None:
            if exp is None or not isinstance(exp,str):
                return exp
            if exp in self.ids:
                try:
                    return eval(self.ids.get(exp))
                except:
                    return self.ids.get(exp)
            for i in sorted(self.ids, key=len, reverse=True):
                if i in exp:
                    value = self.ids.get(i)
                    if isinstance(value,dict):
                        return value
                    exp = exp.replace(i,str(value))
            try:
                return eval(exp)
            except:
                return exp
                self.debug_add('expression contains an invalid id:',expression)
                self.is_run = False
        else:
            if exp in self.ids:
                self.ids[exp] = value
                return value
            else:
                self.debug_add('attribution error:',expression)
                self.is_run = False

    def is_active(self,value):
        """ used to return the state of a unity, actuator or sensor
        """
        val = value
        n = 5
        while not isinstance(val,bool) and n>0 and val is not None:
            val = self.eval_exp(value)
            value = val
            n -=1
        if isinstance(val,dict):
            val = list(val.values())[0]
        return val

    # show table of PLANT memory
    def show_mem_state(self):
        """ shows the plant memory formated in columns
        """
        bitS = [[]]*32
        bitA = [[]]*32
        word = [[]]*32
        NWS,NWA=16,16 # TODO must read from .ini

        state_tmp = {}
        for uu in self.plant:
            for rr in self.plant[uu]:
                if rr not in self.KEYS['sec']: continue
                for dd in self.plant[uu][rr]:
                    for zz in self.plant[uu][rr][dd]:
                        value = self.plant[uu][rr][dd].get(zz)
                        if value is not None:
                            label = self.plant[uu][rr][dd].get('label')
                            label = ''
                            if label is None: label = ''
                            if zz in 'bit':
                                logic = self.is_active( self.plant[uu][rr][dd].get('active') )
                                nm = 'active'
                                if logic is None:
                                    logic = self.is_active( self.plant[uu][rr][dd].get('enabled') )
                                    # logic = self.is_active( self.plant[uu]['unity'][uu].get('enabled') )
                                    nm = 'enabled'
                                state_tmp['.'.join([uu,rr,dd,nm])] = logic

                                bstr = [' ','*'][logic]
                                bright=st.BRIGHT if logic else ''
                                if rr in ['sensor','procvar']:
                                    from_unity,from_pv = self.id_expand(self.plant[uu][rr][dd].get('from_pv'), id_unity=uu)

                                    val = ''
                                    if from_pv in self.plant[from_unity]['procvar']:
                                        val = self.plant[uu][rr][dd].get(nm)
                                        if val is None:
                                            val = ''
                                        else:
                                            # print('+++ ->',uu,rr,dd,nm,'val=',val)
                                            if uu+'.' in val: val = val.replace(uu+'.','')
                                            val = ' '+val

                                    if not bitS[value]:
                                        bitS[value] = [fg.MAGENTA+st.BRIGHT,bstr,st.RESET_ALL,'.'.join([uu,dd]),label,fg.CYAN+bright,val,st.RESET_ALL]
                                    else:
                                        for x in [',','.'.join([uu,dd]),label,fg.CYAN+bright,val,st.RESET_ALL]:
                                            bitS[value].append(x)
                                if rr in 'actuator':
                                    mv = self.plant[uu][rr][dd].get('mv')
                                    if mv is None:
                                        mv=''
                                    else:
                                        mv = ' %d'%(mv/self.MV_MAX*100)+'%'
                                    if not bitA[value]:
                                        bitA[value] = [fg.MAGENTA+st.BRIGHT,bstr,st.RESET_ALL,'.'.join([uu,dd]),label,fg.GREEN+bright,mv,st.RESET_ALL]
                                    else:
                                        for x in [',','.'.join([uu,dd]),label,fg.GREEN+bright,mv,st.RESET_ALL]:
                                            bitA[value].append(x)

                                    # pv controller
                                    unity_id,pv_id = self.id_expand( self.r(uu,rr,dd,'ctrl_pv'), id_unity=uu)
                                    if pv_id is None:
                                        unity_id,pv_id = self.id_expand( self.r(uu,rr,dd,'to_pv'), id_unity=uu)
                                    if pv_id and pv_id not in bitA[value]: # show only if is differente from previous
                                        bitA[value].append(fg.CYAN)
                                        bitA[value].append('->')
                                        if unity_id == uu:
                                            bitA[value].append(pv_id)
                                        else:
                                            bitA[value].append(unity_id+'.'+pv_id)
                                        bitA[value].append(st.RESET_ALL)
                            if 'word' in zz:
                                value = self.plant[uu][rr][dd][zz]
                                r,nm = zz.split('_')
                                n = self.plant[uu][rr][dd].get(nm)

                                state_tmp['.'.join([uu,rr,dd,nm])] = n

                                if self.state:
                                    bright = st.BRIGHT if state_tmp['.'.join([uu,rr,dd,nm])]!=self.state['.'.join([uu,rr,dd,nm])] else ''

                                limits = self.plant[uu][rr][dd].get('limits')
                                if_actuator = self.plant[uu][rr][dd].get('if')
                                if if_actuator is None: if_actuator=''
                                else: if_actuator = '?'+if_actuator
                                if limits is None: limits = [0,self.MV_MAX]
                                nrange = diff(limits)
                                numbars = 10
                                nbar = int((n)/(nrange)*numbars)
                                barstr = 'o'*nbar+'.'*(numbars-nbar)
                                vstr1 = '%4d '%(n)
                                vstr2 = ('%-'+str(numbars)+'s')%(barstr)
                                no = vstr2.find('.')
                                if no<0: no=len(vstr2)
                                vstr2 = bg.CYAN+fg.CYAN+vstr2[:no]+st.RESET_ALL+fg.WHITE+vstr2[no:]
                                vstr3 = fg.CYAN+st.NORMAL+'%3d '%(nrange)
                                if r[-1] in 'wW':
                                    value += NWS
                                if not word[value]:
                                    word[value] = [fg.CYAN+bright,vstr1,fg.WHITE+st.NORMAL,vstr2,fg.BLUE+st.BRIGHT,vstr3,st.RESET_ALL,'.'.join([uu,dd,nm]),fg.CYAN,if_actuator,st.RESET_ALL,label]
                                else:
                                    for x in [',','.'.join([uu,dd,nm]),fg.CYAN,if_actuator,st.RESET_ALL,label]:
                                        word[value].append(x)
        self.state  = state_tmp

        # print_row prints with color preserving spacing
        def print_row(line=None):
            spc = 0
            if not line:
                print(st.RESET_ALL)
                return 0
            spc = 0
            for s in line:
                if fg.WHITE[0] not in s:
                    spc += len(s)
                print(s,end='')
            return spc

        Nspc=[25,40,45]
        # print_row()
        print_row(fg.RESET+'_'*(sum(Nspc)))
        print_row()
        spc=print_row([fg.RESET,'___',fg.BLACK+bg.WHITE,' Sensor (r/o) ',st.RESET_ALL])
        spc=print_row([fg.RESET,'_'*(Nspc[0]-spc)])
        spc=print_row([fg.RESET,'___',fg.BLACK+bg.CYAN,' Actuator (r/w) ',st.RESET_ALL])
        spc=print_row([fg.RESET,'_'*(Nspc[1]-spc)])
        spc=print_row([fg.RESET,'___',fg.BLUE+bg.WHITE+st.DIM,' Word (0-15 r/o; 16-31 r/w)   ',st.RESET_ALL])
        spc=print_row([fg.RESET,'_'*(Nspc[2]-spc)])

        print_row()
        print_row()
        niw,nb,nw = 0,0,0
        wc=fg.WHITE
        while nb<32:
            spc=print_row([fg.WHITE,'%2d'%(nb%16)]+bitS[nb])
            spc=print_row([' '*(Nspc[0]-spc)])
            spc=print_row([fg.CYAN,'%2d'%(nb%16),st.RESET_ALL]+bitA[nb])
            if nw<(NWS+NWA):
                spc=print_row([' '*(Nspc[1]-spc)])
                spc=print_row([wc,'%2d'%(niw),st.RESET_ALL]+word[nw])
            else:
                print_row([' '])
            print_row()
            nb+=1
            nw+=1
            niw+=1
            if niw>=(NWS):
                niw=0
                wc = fg.CYAN
        print_row(fg.RESET+'_'*(sum(Nspc)))
        print_row()

    def print_process_sequence(self):
        """ show the plant sequenc defined by from_pv/to_pv on the actuators
        """
        act = {}
        tmp = {}
        for uu in self.r():
            for rr in self.r(uu):
                for dd in self.r(uu,rr):
                    if self.r(uu,rr,dd,'type') in ['controller','relay']:
                        from_unity,from_pv = self.id_expand(self.r(uu,rr,dd,'from_pv'))
                        to_unity,to_pv = self.id_expand(self.r(uu,rr,dd,'to_pv'))
                        if_actuator,is_active,ctrl_unity,ctrl_pv = self.linked_actuator(to_unity,to_pv)
                        pv_type = self.r(from_unity,from_pv,'type')
                        if 'level' in pv_type:
                            tmp[from_unity] = ctrl_unity
                            act[from_unity] = [self.r(uu,rr,dd,'active'), uu,to_unity]
        # for t in tmp: print('+++',t,tmp[t])
        unity = []
        for i,x in enumerate(tmp.keys()):
            sequence = self.r(x,'unity','sequence')
            if sequence == 0: break
        x = list(tmp.keys())[i]
        print(bg.WHITE+fg.BLACK,' '*16,end='')
        while x:
            unity.append(x)
            x = tmp.get(x)
            if (x in unity):
                break
        for i,x in enumerate(unity):
            if self.r(x+'.lvl.pv'):
                print(fg.BLACK+st.BRIGHT+x,sep=' ',end='')
            else:
                print(fg.BLACK+st.DIM+x,sep=' ',end='')
            if i<len(unity)-1:
                if act.get(x):
                    print(fg.BLACK+st.DIM,'  | ',sep='',end='')
                else:
                    print(fg.BLACK+st.DIM,'  | ',sep='',end='')
        print('   ', st.RESET_ALL)
        #
        if not self.print_old_state: self.print_old_state = {}
        for ty in self.KEYS['var']:
            line = []
            for x in unity:
                if self.print_old_state.get(x) is None:
                    self.print_old_state[x] = {}
                found = False
                for pv in self.r(x+'.procvar'):
                    if pv not in self.KEYS['unt'] and ty in self.r(x,pv,'type'):
                        found = True
                        break
                if not found:
                        line = line + ['',' '*7]
                else:
                    if self.print_old_state[x].get(ty) is None:
                        self.print_old_state[x][ty] = ''
                    val = self.r(x+'.'+pv+'.pv')
                    if val is not None:
                        strval = '%3d%-3s '%(val,self.un.get(ty))
                    else:
                        strval = ' '*7
                    bright = fg.WHITE
                    if self.print_old_state[x][ty] != strval: bright = fg.RESET
                    self.print_old_state[x][ty] = strval
                    if self.un.get(ty) is not None:
                        line = line + [bright,strval]
            if any([len(x.strip())>0 for x in line]):
                line = [fg.WHITE+'%15s: '%(ty,)] + line
                for l in line:
                    print(l,sep='',end='')
                print('')

    def print_unity(self, unity=['*'], spc_=25, only_enabled=False, only_active=False):
        """ show a unity of the plant structure
        """
        if isinstance(unity,str): unity = [unity]
        strON = '*'
        for uu in unity:
            uu_enabled = self.is_active(self.r(uu,'unity',uu,'enabled'))
            label = self.r(uu,'unity',uu,'label')
            if label is None: label = uu
            print(fg.GREEN+st.BRIGHT+uu+st.RESET_ALL,label.upper().replace('_',' '),'-'*(100-len(label)))
            if not self.r(uu) or (only_enabled and not uu_enabled): continue
            for rr in self.r(uu):
                if rr not in self.KEYS['sec']: continue
                header = []
                data = []
                footer = []
                for dd in self.r(uu,rr):
                    unity_id_from,unity_pv_from = '',''
                    unity_id_to,unity_pv_to = '',''
                    uid_from,uid_to = '',''
                    value = self.r(uu,rr,dd,'enabled')
                    value2=''
                    if value is None:
                        value = self.is_active( self.r(uu,rr,dd,'active') )
                        if value is not None and (only_active and not value): continue
                        from_unity,from_pv = self.id_expand(self.r(uu,rr,dd,'from_pv'), id_unity=uu)
                        to_unity,to_pv = self.id_expand(self.r(uu,rr,dd,'to_pv'), id_unity=uu)
                        ctrl_unity,ctrl_pv = self.id_expand(self.r(uu,rr,dd,'ctrl_pv'), id_unity=uu)
                        inlet = self.r(uu,rr,dd,'inlet')
                        if from_pv is None:
                            from_pv = ''
                        else:
                            inlet = [ inlet, True][ inlet is None]
                            if from_pv:
                                unity_id_from = from_unity
                                unity_pv_from = from_pv
                            if to_pv:
                                unity_id_to = to_unity
                                unity_pv_to = to_pv
                            if ctrl_pv:
                                unity_id_to = ctrl_unity
                                unity_pv_to = ctrl_pv
                            if unity_id_from == unity_id_to or unity_id_from == uu:
                                unity_id_from = ''
                                if unity_pv_from == unity_pv_to:
                                    unity_pv_from = ''
                            if unity_id_to == uu:
                                unity_id_to = ''
                            uid_from = '<'+unity_id_from+'.'+unity_pv_from
                            uid_to = '>'+unity_id_to+'.'+unity_pv_to
                            if uid_from[1]=='.': uid_from=uid_from.replace('.','')
                            if uid_to[1]=='.': uid_to=uid_to.replace('.','')
                        if len(uid_from)<=1:uid_from = ''
                        if len(uid_to)<=1:uid_to = ''

                        if value is None:
                            value = self.r(uu,rr,dd,'pv')
                            if value is not None:
                                value = '%d'%(value)
                                if self.plant[uu][rr][dd].get('limits') is not None:
                                    value2 = '/' + '%d'%(diff(self.r(uu,rr,dd,'limits')))
                        else:
                            value = ['.',strON][value]

                        if self.r(uu,rr,dd,'type') in ['controller'] and self.r(uu,rr,dd,'mv') is not None:
                            value2 = ' '+'%d'%(self.r(uu,rr,dd,'mv'))

                    else:
                        value = ['.',strON][value]
                    label = self.r(uu,rr,dd,'label')
                    if not label: label = ''

                    header.append([dd,uid_to,uid_from])
                    data.append([value,value2])
                    footer.append(label)
                if header:
                    print('\t',end='')
                    if rr in [ 'procvar']: Nspc = 10
                    else: Nspc = spc_
                    for h in header:
                        fuc = fg.WHITE
                        if uu not in uid_from: fuc = fg.GREEN+st.BRIGHT
                        print(fg.RESET,h[0],fg.BLUE+st.BRIGHT,h[1],fuc,h[2],st.RESET_ALL,sep='',end='')
                        l=sum([len(n) for n in h])
                        print(' '*(Nspc-l),end='')
                    print('')
                    print('\t',end='')
                    for d in data:
                        try:
                            n=eval(d[0])
                        except:
                            n=None
                        if d[0] in [strON]:
                            print(fg.MAGENTA+st.BRIGHT,d[0],st.RESET_ALL,sep='',end='')
                        elif isinstance(n,(int,float)):
                            print(fg.CYAN+st.BRIGHT,d[0],fg.CYAN+st.NORMAL,d[1],st.RESET_ALL,sep='',end='')
                        else:
                            print(fg.WHITE,d[0],fg.CYAN+st.NORMAL,d[1],st.RESET_ALL,sep='',end='')
                            # print(fg.WHITE,d[0],st.RESET_ALL,sep='',end='')
                        l=sum([len(n) for n in d])
                        print(' '*(Nspc-l),end='')
                    print('')

    def print_unity_state(self, *unity):
        """ show all unities of the plant structure
        """
        if isinstance(unity,dict): unity = list(unity.keys())
        if not unity:
            unity = list(self.r().keys())
        if isinstance(unity,tuple):
            if len(unity) == 1: unity = unity[0]
            if isinstance(unity,str):
                unity = [''.join(unity)]
            unity = list(unity)
        self.print_unity(unity)

    # local function to search state of last linked device
    def linked_actuator(self,to_unity,to_pv):
        """ search for the destination unity given a unity.id origin
            using the from_pv/to_pv data of the actuators
        """
        is_active = True
        actuator = self.r(to_unity,to_pv,'if')
        last_actuator = actuator
        n = 10 # max hops
        while actuator and n>0:
            is_active = is_active and self.is_active( self.r(to_unity,actuator,'active') )
            last_actuator = actuator
            to_unity,to_pv = self.id_expand( self.r(to_unity,actuator,'to_pv'), id_unity=to_unity)
            actuator = self.r(to_unity,to_pv,'if')
            n -= 1
        if n<=0: self.debug_add('if error:',to_unity,to_pv,actuator)
        return last_actuator,is_active,to_unity,to_pv

    # PLANT proccessing rules
    def process(self,dt):
        """ process the plant structure to evolve in time
            contain all the rules for transfering materials between tanks (from_pv/to_pv)
            transfer properties between tanks
        """
        self.debug = []
        # processa dispositivos da planta
        for uu in self.plant.keys(): # for each unity
            # reset properties of the disabled unity
            if not self.is_active(self.r(uu,'unity',uu,'enabled')):
            # if not self.is_active(self.r(uu+' unity enabled')):
                for yy in self.r(uu,'procvar'): # for each unity procvar
                    if self.r(uu,'procvar',yy,'pv'):# is not None:
                        self.w(uu,yy,'pv', 0)
                        self.r(uu,yy,'sys').pv = 0

            # processa atuadores
            if self.is_active(self.r(uu,'unity',uu,'enabled')) and self.r(uu,'actuator') is not None:
            # if self.is_active(self.r(uu+' unity enabled')) and self.r(uu,'actuator') is not None:
                # loss
                for dd in self.r(uu,'procvar'): # for each procvar
                    if not dd in ['unity'] and 'sys' in self.r(uu,dd):
                        # print('+++ ?',uu,dd)
                        pv_tmp = self.r(uu,dd,'sys').update(dt=dt)
                        self.w(uu,dd,'pv', pv_tmp )

                for dd in self.r(uu,'actuator'): # for each actuator device
                    # to_unity = self.r(uu,dd,'to')
                    inlet = self.r(uu,dd,'inlet')
                    if inlet is None: inlet = True # default

                    # ctrl_pv
                    ctrl_unity,ctrl_pv = self.id_expand( self.r(uu,dd,'ctrl_pv'), id_unity=uu)

                    # from_pv
                    from_unity,from_pv = self.id_expand( self.r(uu,dd,'from_pv'), id_unity=uu)

                    # to_pv
                    to_unity,to_pv = self.id_expand( self.r(uu,dd,'to_pv'), id_unity=uu)

                    # if outlet pv is not specified then default outlet is local unity using same pv
                    if to_pv is None:
                        to_unity = from_unity
                        to_pv = from_pv

                    # if outlet pv is not specified then default outlet is local unity using same pv
                    if ctrl_pv is None:
                        ctrl_unity = to_unity
                        ctrl_pv = to_pv

                    flow = self.r(uu,dd,'flow')
                    if flow is None: flow = 0 # default

                    if from_unity is not None:
                        flow_from = self.r(from_unity,dd,'flow')
                        if flow_from is None: flow_from = 0 # default

                    actuator_type = self.r(uu,dd,'type')

                    mv_from = 0
                    mv_to = 0

                    # actuator is active
                    if self.is_active( self.r(uu,dd,'active') ):
                        if self.r(uu,dd,'type') == 'controller':
                            pv_tmp = self.r(ctrl_unity,ctrl_pv,'pv')
                            mv_tmp = self.r(uu,dd,'mv')
                            ctrl_par = self.r(uu,dd,'ctrl_par')
                            if ctrl_par: # internal actuator PID in auto mode
                                mv = self.r(uu,dd,'ctrl')( pv_tmp ) # match PID and PLC interfaces
                                if mv<0: mv = 0
                                self.w(uu,dd,'mv', mv )
                            else: # a device controller must use wordw_mv to change mv value
                                mv = mv_tmp
                        else:
                            mv = self.MV_MAX
                        if actuator_type in ['transmitter']:
                            pv = self.r(from_unity,from_pv,'pv')
                            pv_tmp = self.r(to_unity,to_pv,'sys').update( dt=dt, var=[pv,0.0])
                            self.w(to_unity,to_pv,'pv', pv_tmp )
                        elif actuator_type in ['controller','relay']:
                            if from_unity == to_unity: # same unity
                                pv_tmp = self.r(from_unity,from_pv,'sys').update( inlet=inlet, dt=dt, mv=mv, var=[flow,0.0])
                                self.w(from_unity,from_pv,'pv', pv_tmp )
                            else: # from a unity to another one
                                mv_from,mv_to = mv,mv
                                flow_from,flow_to = flow,flow
                                if self.r(from_unity,from_pv,'pv') <= min(self.r(from_unity,from_pv,'limits'))+0.1: # CORRIGIR NOISE!!!
                                    mv_from = 0
                                    flow_from = 0
                                    mv_to = 0
                                    flow_to = 0
                                # define state of last linked device, if any
                                if_actuator,is_active,ctrl_to_unity,ctrl_to_pv = self.linked_actuator(to_unity,to_pv)
                                if if_actuator:
                                    to_unity,to_pv = ctrl_to_unity,ctrl_to_pv
                                    if not is_active:
                                        mv_from = 0
                                        flow_from = 0
                                        mv_to = 0
                                        flow_to = 0
                                # from
                                pv_from_tmp = self.r(from_unity,from_pv,'sys').update( inlet=False, dt=dt, mv=mv_from, var=[flow_from,0.0])
                                self.w(from_unity,from_pv,'pv', pv_from_tmp )
                                # to
                                if self.r(to_unity,to_pv,'pv') >= max(self.r(to_unity,to_pv,'limits')): # CORRIGIR NOISE!!!
                                    mv_to = 0
                                    flow_to = 0
                                pv_to_tmp = self.r(to_unity,to_pv,'sys').update( inlet=True, dt=dt, mv=mv_to, var=[flow_to,0.0])
                                self.w(to_unity,to_pv,'pv', pv_to_tmp )
                                if 'filler' in from_unity and 'fc4' in dd:
                                    self.debug_add(from_unity,from_pv,mv_from,pv_from_tmp)
                                    self.debug_add(to_unity,to_pv,mv_to,pv_to_tmp)

                        # search for mixers
                        mixer_active = []
                        for xx in self.r():  # search all unities
                            if self.is_active(self.r(xx,'unity',xx,'enabled')) and self.r(xx,'actuator'): # is unity has actuators
                            # if self.is_active(self.r(xx,'procvar','unity','enabled')) and self.r(xx,'actuator'): # is unity has actuators
                                for yy in self.r(xx,'actuator'):  # search all actuators
                                    if self.r(xx,yy,'type') in ['mixer']: # if actuator is a mixer
                                        if self.is_active( self.r(xx,yy,'active') ): # if mixer is active
                                            mixer_flow = self.r(xx,yy,'flow')
                                            mixer_to,mixer_pv = self.id_expand( self.r(xx,yy,'to_pv'), id_unity=xx)   # mix this procvar
                                            mixer_active.append([mixer_flow,mixer_to,mixer_pv])

                        # transfer properties between unities
                        #
                        mix_proportion = mv_to/self.MV_MAX/10
                        pv_level = None
                        if self.r(to_unity,'procvar'): #and to_unity in self.KEYS['sec']:
                            for lvl in self.r(to_unity,'procvar'):
                                if lvl not in self.KEYS['unt'] and 'level' in self.r(to_unity,lvl,'type'):
                                    pv_level = self.r(to_unity,lvl,'pv')
                                # print('+++',to_unity, lvl, pv_level)
                            if pv_level is not None:
                                full_level = diff(self.r(to_unity,lvl,'limits'))
                                mix_proportion *= 1-pv_level/full_level

                        # if not exists mix_pv in from_pv/to_pv!!!!!!!
                        for p in self.r(from_unity,'procvar'):
                            if p not in ['unity',from_pv]: # pv is not unity or itself
                                pv = self.r(from_unity,p,'pv')
                                # mix_pv = self.r(from_unity,p,'mix_pv') # list of pv_ids to mix with
                                # if not mix_pv: mix_pv = ''
                                if pv is not None:
                                    if self.r(to_unity,p,'sys') and mv_from>0:
                                        dt_transf = dt * mix_proportion
                                        for mixer_flow,mixer_to,mixer_pv in mixer_active:
                                            if mixer_active and mixer_to==to_unity and mixer_pv==p:
                                                dt_transf = dt * mix_proportion * mixer_flow
                                        if pv_level is not None and pv_level<0.1:
                                            dt_transf = pv
                                            # print('level zero',pv)
                                            # raise Exception('ZERO LEVEL',to_unity,pv_level,self.r(to_unity,'lvl','pv'))
                                        self.r(to_unity,p,'sys').mix_pv(pv,dt_transf)
                                        new_pv = self.r(to_unity,p,'sys').pv
                                        self.w(to_unity,p,'pv', new_pv)

        # atualiza sensores e atuadores
        for uu in self.plant.keys():
            if self.is_active(self.r(uu,'unity',uu,'enabled')):
            # if self.is_active(self.r(uu,'unity enabled')):
                for rr in self.r(uu):
                    if rr not in self.KEYS['sec']: continue
                    for dd in self.r(uu,rr).keys():
                        if 'actuator' in rr:
                            if self.r(uu,rr,dd,'ctrl') is not None:
                                unity_id,pv_id = self.id_expand( self.r(uu,dd,'to_pv'),id_unity=uu )
                                if pv_id is None:
                                    unity_id,pv_id = self.id_expand( self.r(uu,dd,'from_pv'),id_unity=uu ) # ALTERADO from -> to
                                setpoint = self.r(uu,pv_id,'sp')
                                if not setpoint: setpoint = 0
                                self.r(uu,rr,dd,'ctrl').setpoint = setpoint
                                if self.r(uu,rr,dd,'ctrl_par') is not None:
                                    ctrl_par = self.r(uu,rr,dd,'ctrl_par')
                                    self.r(uu,rr,dd,'ctrl').Kp, self.r(uu,rr,dd,'ctrl').Ki, self.r(uu,rr,dd,'ctrl').Kd = ctrl_par
                                    # self.r(uu,rr,dd,'ctrl').reset()
        self.update_ids()

if __name__ == '__main__':

    clear_screen()
    cfg = load_config('PLANTsim.ini')
    from pyModbusTCP.server import DataBank as db
    plant = Plant()
    plant.load("PLANTsim_LARI_1.plant")

    # raise Exception('STOP')

    MODBUS_mem = Memory()
    db.set_words(0, [0]*24)
    MODBUS_mem.init(db,plant)
    MODBUS_mem.update(db,plant)

    plant.print_process_sequence()
    plant.show_mem_state()

    # plant.print_unity_state(list(plant.r().keys()))
    # plant.print_unity_state('tk2')
    plant.print_unity_state('tk2','tk3')
    # plant.print_unity_state(['tk3'])

    # print('+++ plant tk3.lvl.sp',plant.r('tk3.lvl.sp'))

    # plant.print_unity_state()

    # plant.print_file("PLANTsim_LARI_1.plant")
    # plant.print_process_sequence()


import wave
import numpy as np
import matplotlib.pyplot as plt
import sys
import scipy.signal.signaltools as sigtool
from scipy.signal import find_peaks

def normalization(sig):
    env = (sigtool.hilbert(sig)) #przeksztalcenie hilberata
    threshold = 200
    square_sig = (env > threshold)
    return square_sig

def wav2morse(filename):
    wav = wave.open(filename, 'r')

    raw = wav.readframes(-1)
    raw = list(np.frombuffer(raw, "Int16"))
    plt.figure('raw')
    plt.title("raw")
    plt.plot(raw)
    # plt.show()

    if wav.getnchannels()==2:
        print("Stereo files are not supported!")
        sys.exit(0)

    thresh = normalization(raw)
    # print(thresh)

    plt.figure('thresh')
    plt.title('thresh')
    plt.plot(thresh)
    # plt.show()

    peaks, _ = find_peaks(thresh)
    print(peaks)

    plt.figure('peaks')
    plt.title('peaks')
    plt.plot(peaks, thresh[peaks], '.')
    # plt.show()

    #####################################
    delta = 0
    start_value = peaks[0]
    prev_value = 0
    spaces = []
    characters = []

    for i in range(len(peaks)):
        delta = peaks[i] - prev_value#peaks[i-1]
        # print(delta)
        if delta > 20:
            spaces.append(delta)
            characters.append(prev_value-start_value)
            start_value=peaks[i]
        prev_value = peaks[i]

    rew_peaks = peaks[::-1]
    start_value = rew_peaks[0]
    prev_value = rew_peaks[0]

    for i in range(len(rew_peaks)):
        delta = abs(rew_peaks[i] - prev_value)
        if delta > 20:
            spaces.append(delta)
            characters.append(abs(prev_value-start_value))
            start_value=peaks[i]
            break
        prev_value = rew_peaks[i]

    print(spaces)
    print(characters)
    print(len(spaces)-len(characters))

    returned_vals = []
    for i in range(len(characters)):
        returned_vals.append(characters[i])
        if spaces[i] > 5000:
            returned_vals.append(0)

    print(returned_vals)

    returned = ''
    for i in range(len(returned_vals)):
        if returned_vals[i] == 0:
            returned +=' '
        elif returned_vals[i] < 500:
            returned +='.'
        else:
            returned += '-'

    print(returned)
    plt.show()
    return returned


import threading
import tkinter as tk
import cv2
import cv2 as cv
import numpy as np
import time as t
import functools

top = tk.Tk()
fps = 0
fps_avg = 0
fps_acc = 0
start_t = t.time()
time = t.time()
debug = False
fullscreen = False
reset_first_img = False
frame_to_show = 0
debug_frame = 0
cue_frame = 0
finished = False
morph_size = 5
morph_size_var = tk.StringVar()
morph_size_var.set(str(morph_size))
testName = 0
threshold_area = 500
threshold_area_max = 5000
threshold_area_var = tk.StringVar()
threshold_area_max_var = tk.StringVar()
threshold_area_var.set(str(threshold_area))
threshold_area_max_var.set(str(threshold_area_max))
width, height = (1280, 740)
offset_x_h = -30
offset_x_h_var = tk.StringVar()
offset_x_h_var.set(str(offset_x_h))
offset_x_l = 25
offset_x_l_var = tk.StringVar()
offset_x_l_var.set(str(offset_x_l))
offset_y_h = 2
offset_y_h_var = tk.StringVar()
offset_y_h_var.set(str(offset_y_h))
offset_y_l = -8
offset_y_l_var = tk.StringVar()
offset_y_l_var.set(str(offset_y_l))

producer_consumer_img = 0
producer_consumer_img_count = 0
producer_consumer_img_lock = threading.Lock()

threshold = 60
threshold_var = tk.StringVar()
threshold_var.set(str(threshold))

que_sliding_window = [(0, 0) for _ in range(5)]
que_sliding_window_index = 0
que_sliding_window_avg = (0, 0)

whiteball_sliding_window = [(0, 0) for _ in range(5)]
whiteball_sliding_window_index = 0
whiteball_sliding_window_avg = (0, 0)

shit_sliding_window = [(0, 0) for _ in range(5)]
shit_sliding_window_index = 0
shit_sliding_window_avg = (0, 0)

shit_v_sliding_window = [(0, 0) for _ in range(5)]
shit_v_sliding_window_index = 0
shit_v_sliding_window_avg = (0, 0)

x_1 = -70
x_1_var = tk.StringVar()
x_1_var.set(x_1)
y_1 = 79
y_1_var = tk.StringVar()
y_1_var.set(y_1)
x_2 = 1260
x_2_var = tk.StringVar()
x_2_var.set(x_2)
y_2 = 71
y_2_var = tk.StringVar()
y_2_var.set(y_2)
x_3 = 1256
x_3_var = tk.StringVar()
x_3_var.set(x_3)
y_3 = 726
y_3_var = tk.StringVar()
y_3_var.set(y_3)
x_4 = -60
x_4_var = tk.StringVar()
x_4_var.set(x_4)
y_4 = 734
y_4_var = tk.StringVar()
y_4_var.set(y_4)

calibrating = False
calibrating_var = tk.BooleanVar()
calibrating_var.set(calibrating)
corner_points = [(x_1, y_1), (x_2, y_2), (x_3, y_3), (x_4, y_4)]

give_help = True
give_help_var = tk.BooleanVar()
give_help_var.set(give_help)

arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_1000)
arucoParams = cv2.aruco.DetectorParameters_create()

def draw_magic_lines_of_helpiness():
    global outer_M, outer_M_inv, M, debug_frame, img2_outer
    # Step 1: Homography
    debug_frame = outer_wrapped
    #
    # Step 2: Find line
    # Step 3: ????
    # Step 4: Profit
    pass

def get_aruco(marker_id, size):
    new_image = np.zeros((size, size, 1), np.uint8)
    cv2.aruco.drawMarker(arucoDict, marker_id, size, new_image)
    new_image = cv2.cvtColor(new_image, cv.COLOR_GRAY2RGB)
    return new_image

def find_image_targets(img, target, threshold=0.8):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    res = cv2.matchTemplate(gray, target, cv2.TM_CCOEFF_NORMED)
    return np.where(res >= threshold)

def squares_on_img(img, targets, w, h):
    for pt in zip(*targets[::-1]):
        cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)

def circle_on_img(img, x, y, r):
    cv2.circle(img, (x, y), r, (0, 255, 0), 2)

def find_diff(img1, img2):
    diff = cv2.absdiff(img1, img2)
    return diff

def find_boxes_from_img(img, output, w, h, last_out):
    global debug_frame, debug, whiteball_sliding_window, whiteball_sliding_window_index, whiteball_sliding_window_avg
    start_time = t.time()
    ret, thresh_gray = cv.threshold(cv.cvtColor(img, cv.COLOR_BGR2GRAY), threshold, 255, cv.THRESH_BINARY)
    tg_copy = thresh_gray.copy()
    # thresh_gray = thresh_gray - cv.cvtColor(last_out, cv.COLOR_BGR2GRAY)
    contours, hier = cv.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    black_canvas = np.zeros((h, w, 3), np.uint8)
    black_canvas[:, :] = (255, 255, 255)  # not so black now
    if not give_help:
        return black_canvas

    for cont in contours:
        area = cv.contourArea(cont)

        if area < threshold_area:
            cv.fillPoly(thresh_gray, pts=[cont], color=0)
            continue

        rect = cv.minAreaRect(cont)
        (x, y), (w, h), angle = rect
        aspect_ratio = max(w, h) / min(w, h)
        if aspect_ratio > 2.5:  # or w > 100 or h > 100:
            cv2.fillPoly(thresh_gray, pts=[cont], color=0)
            continue
    time_p1 = t.time()
    thresh_gray2 = cv2.morphologyEx(thresh_gray, cv.MORPH_CLOSE,
                                    cv.getStructuringElement(cv.MORPH_ELLIPSE, (morph_size, morph_size)))
    contours, hier = cv.findContours(thresh_gray, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
    # contour_cords = []
    for cont in contours:
        area = cv.contourArea(cont)
        if area < threshold_area or area > threshold_area_max:
            cv2.fillPoly(thresh_gray2, pts=[cont], color=0)
            continue
        # rect = cv.minAreaRect(cont)
        # (x, y), (w, h), _ = rect
        # black_canvas = cv.circle(black_canvas, (int(x), int(y)), 25, (255, 255, 255), 5)
        # contour_cords.append(cont)
    time_p2 = t.time()
    pos, radius = find_avg_color(output, thresh_gray2)
    # draw_magic_lines_of_helpiness()
    find_cue(output, tg_copy, pos, black_canvas, radius + 60)
    time_p3 = t.time()

    whiteball_sliding_window[whiteball_sliding_window_index] = pos
    whiteball_sliding_window_index = (whiteball_sliding_window_index + 1) % len(whiteball_sliding_window)
    pos = functools.reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), whiteball_sliding_window)
    whiteball_sliding_window_avg = (pos[0] // len(whiteball_sliding_window), pos[1] // len(whiteball_sliding_window))

    black_canvas = cv.circle(black_canvas, whiteball_sliding_window_avg, radius + 60, (0, 255, 255), 5)

    #black_canvas = cv.putText(black_canvas, "FPS: " + str(fps) + ", AVG: " + str(fps_avg), (0, 500),
    #                          cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255))
    #output = cv.putText(output, "FPS: " + str(fps) + ", AVG: " + str(fps_avg), (0, 500), cv2.FONT_HERSHEY_SIMPLEX, 3,
    #                    (255, 255, 255))
    # backtorgb1 = cv2.cvtColor(thresh_gray, cv2.COLOR_GRAY2RGB)
    # backtorgb = cv2.cvtColor(thresh_gray2, cv2.COLOR_GRAY2RGB)
    #debug_frame = np.vstack([np.hstack([img, backtorgb1]), np.hstack([backtorgb, output])])
    time_p4 = t.time()
    # if time_p4 - start_time > 0.016:
    # print(
    #    f"Time diff p1: {time_p1 - start_time}, p2: {time_p2 - time_p1}, p3: {time_p3 - time_p2}, p4: {time_p4 - time_p3}, total: {time_p4 - start_time}")
    if debug:
        return np.vstack([np.hstack([img, backtorgb1]), np.hstack([backtorgb, output])])
    else:
        return black_canvas

def create_homography_and_wrap(img, src_points, dest_points, height, width):
    M, mask = cv2.findHomography(src_points, dest_points)
    M_back, _ = cv2.findHomography(dest_points, src_points)

    warpedImg = cv2.warpPerspective(img, M, (width, height))
    warpedImg_g = cv2.cvtColor(warpedImg, cv2.COLOR_BGR2GRAY)
    warpedImg_g = cv.blur(warpedImg_g, (3, 3))
    circles = cv2.HoughCircles(warpedImg_g, cv2.HOUGH_GRADIENT, 2,
                               5, param1=100, param2=20, minRadius=7, maxRadius=15)
    circle_points = []
    if circles is not None:
        circles = np.round(circles[0, :]).astype("int")
        for (x, y, r) in circles:
            p_origin_homogenous = np.array((x, y, 1)).reshape(3, 1)
            temp_p = M_back.dot(p_origin_homogenous)
            sum = np.sum(temp_p, 1)
            px = int(round(sum[0] / sum[2]))
            py = int(round(sum[1] / sum[2]))
            circle_points.append((px, py, r))
    return circle_points

def find_avg_color(frame, diff):
    radius = 25
    diff = cv.bitwise_and(frame, frame, mask=diff)
    gray = cv.cvtColor(diff, cv.COLOR_RGB2GRAY)
    gray = cv2.GaussianBlur(gray, (radius, radius), 0)
    (minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(gray)
    return maxLoc, radius

def find_cue(img, diff, pos, frame_to_show, rasmus_the_radius):
    global cue_frame, que_sliding_window, que_sliding_window_index, que_sliding_window_avg
    global shit_sliding_window, shit_sliding_window_avg, shit_sliding_window_index
    global shit_v_sliding_window, shit_v_sliding_window_avg, shit_v_sliding_window_index
    h, w = 200, 200
    x, y = pos

    dst_points = np.float32([(0, 0), (0, w), (h, w), (h, 0)]).reshape(-1, 1, 2)
    src_points = np.float32([(x - h // 2, y - w // 2), (x - h // 2, y + w // 2), (x + h // 2, y + w // 2),
                             (x + h // 2, y - h // 2)]).reshape(-1, 1, 2)
    M, mask = cv2.findHomography(src_points, dst_points)
    M_back = np.linalg.inv(M)
    diff = cv2.warpPerspective(diff, M, (h, w))
    n_img = cv2.warpPerspective(img, M, (h, w))
    contours, _ = cv.findContours(diff, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    max_area, max_cont, max_x, max_y = None, None, None, None
    for cont in contours:
        area = cv2.contourArea(cont)
        if area < 100:
            cv2.fillPoly(diff, pts=[cont], color=0)
            continue
        (x, y), (we, he), angle = cv.minAreaRect(cont)
        if min(we, he) == 0:
            continue
        aspect_ratio = max(we, he) / min(we, he)
        if aspect_ratio < 2:  # or w > 100 or h > 100:
            cv2.fillPoly(diff, pts=[cont], color=0)
        elif max_area == None or area > max_area:  # This is the cue?
            max_x, max_y = x, y
            max_area = area
            max_cont = cont

    if max_area is None:
        return

    try:

        cont = cv2.perspectiveTransform(np.float32(max_cont), M_back)

        rows, cols = frame_to_show.shape[:2]
        [vx, vy, x, y] = cv.fitLine(cont, cv.DIST_L2, 0, 0.01, 0.01)
        lefty = int((-x * vy / vx) + y)
        righty = int(((cols - x) * vy / vx) + y)

        dir = [0, 0]
        if max_x > 100:
            dir[0] = -1
        else:
            dir[0] = 1
        if max_y > 100:
            dir[1] = -1
        else:
            dir[1] = 1
        print(dir)
        pos = (lefty, righty)
        shit = (x,y)
        shit_v = (vx*10+x,vy*10+y)
        que_sliding_window[que_sliding_window_index] = pos
        que_sliding_window_index = (que_sliding_window_index + 1) % len(que_sliding_window)
        pos = functools.reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), que_sliding_window)
        que_sliding_window_avg = (pos[0] // len(que_sliding_window), pos[1] // len(que_sliding_window))

        shit_sliding_window[shit_sliding_window_index] = shit
        shit_sliding_window_index = (shit_sliding_window_index + 1) % len(shit_sliding_window)
        shit = functools.reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), shit_sliding_window)
        shit_sliding_window_avg = (shit[0] / len(shit_sliding_window), shit[1] / len(shit_sliding_window))

        shit_v_sliding_window[shit_v_sliding_window_index] = shit_v
        shit_v_sliding_window_index = (shit_v_sliding_window_index + 1) % len(shit_v_sliding_window)
        shit_v = functools.reduce(lambda a, b: (a[0] + b[0], a[1] + b[1]), shit_v_sliding_window)
        shit_v_sliding_window_avg = (shit_v[0] / len(shit_v_sliding_window), shit_v[1] / len(shit_v_sliding_window))

        #line_segments = draw_bounces(frame_to_show, [(cols - 1, que_sliding_window_avg[1]), (0, que_sliding_window_avg[0])], dir)
        line_segments = draw_bounces(frame_to_show, [(shit_v_sliding_window_avg[0], shit_v_sliding_window_avg[1]), (shit_sliding_window_avg[0], shit_sliding_window_avg[1])], dir)
        for index in range(len(line_segments) - 1):
            draw_line_and_impact(frame_to_show, line_segments[index], line_segments[index + 1])
        cv.line(frame_to_show, (cols - 1, que_sliding_window_avg[1]), (0, que_sliding_window_avg[0]), (0, 255, 255), 8)
        cv.circle(frame_to_show, whiteball_sliding_window_avg, rasmus_the_radius, (255, 255, 255), thickness=-1)

    except cv2.error as e:
        pass
    except OverflowError:
        pass
    cv.line(n_img, (0, 100), (200, 100), (255, 255, 0), 8)
    cv.line(n_img, (100, 0), (100, 200), (255, 255, 0), 8)
    print(max_x, max_y)
    cue_frame = np.vstack(
       [n_img, cv2.cvtColor(diff, cv2.COLOR_GRAY2RGB)])

def draw_bounces(image, points, dir, bounces=3, last_p_index=None):
    global corner_points
    if bounces == 0:
        return []
    current_line = line(points[0], points[1])
    list_o_points = []
    for p_index in range(len(corner_points)):
        p1 = corner_points[p_index]
        p2 = corner_points[(p_index + 1) % len(corner_points)]
        min_x = min(p1[0], p2[0]) - 1
        max_x = max(p1[0], p2[0]) + 1
        min_y = min(p1[1], p2[1]) - 1
        max_y = max(p1[1], p2[1]) + 1

        intersect = intersection(current_line, line(p1, p2))
        #print(p_index, intersect)
        new_dir = [dir[0], dir[1]]
        if intersect and min_x <= int(intersect[0]) <= max_x and min_y <= int(intersect[1]) <= max_y:
            pos1 = (int(intersect[0]), int(intersect[1]))
            if p_index == 0 or p_index == 2:
                pos2 = (int(points[1][0]), int(points[1][1] + (2* (pos1[1] - points[1][1]))))
                lines = [pos1, pos2]
                if dir[1] == 1 and p_index == 2:
                    print("X1")
                    new_dir[1] = -new_dir[1]
                    # draw_line_and_impact(image, pos1, pos2)
                    list_o_points = draw_bounces(image, lines, dir=new_dir, bounces=bounces - 1, last_p_index=p_index) + list_o_points
                    list_o_points.append(pos1)
                elif dir[1] == -1 and p_index == 0:
                    print("X2")
                    new_dir[1] = -new_dir[1]
                    # draw_line_and_impact(image, pos1, pos2)
                    list_o_points = draw_bounces(image, lines, dir=new_dir, bounces=bounces - 1, last_p_index=p_index) + list_o_points
                    list_o_points.append(pos1)
            else:
                # print("HERE", dir[0], p_index)
                eh = points[1][0]

                pos2 = (int(eh + (2 * (pos1[0] - eh))), int(points[1][1]))
                lines = [pos1, pos2]
                if dir[0] == 1 and p_index == 1:
                    new_dir[0] = -new_dir[0]
                    print("Y1", bounces)
                    # draw_line_and_impact(image, pos1, pos2)
                    list_o_points = draw_bounces(image, lines, dir=new_dir, bounces=bounces - 1, last_p_index=p_index) + list_o_points
                    list_o_points.append(pos1)
                elif dir[0] == -1 and p_index == 3:
                    new_dir[0] = -new_dir[0]
                    print("Y2", bounces)
                    #draw_line_and_impact(image, pos1, pos2)

                    b = draw_bounces(image, lines, dir=new_dir, bounces=bounces - 1, last_p_index=p_index)
                    # print(b)
                    list_o_points = b + list_o_points
                    list_o_points.append(pos1)
    return list_o_points

def draw_line_and_impact(image, pos1, pos2):
    cv2.line(image, pos1, pos2, (255, 0, 0), 8)
    cv2.circle(image, pos1, 15, (255, 0, 0), -1)

def line(p1, p2):
    A = (p1[1] - p2[1])
    B = (p2[0] - p1[0])
    C = (p1[0] * p2[1] - p2[0] * p1[1])
    return A, B, -C

def intersection(L1, L2):
    D = L1[0] * L2[1] - L1[1] * L2[0]
    Dx = L1[2] * L2[1] - L1[1] * L2[2]
    Dy = L1[0] * L2[2] - L1[2] * L2[0]
    if D != 0:
        x = Dx / D
        y = Dy / D
        return x, y
    else:
        return False

def img_producer():
    global producer_consumer_img_lock, producer_consumer_img, producer_consumer_img_count, finished
    cap = cv2.VideoCapture(2)
    # cap = cv2.VideoCapture('test_images/tm2.mov')
    cap.set(cv.CAP_PROP_FRAME_WIDTH, 1920)
    cap.set(cv.CAP_PROP_FRAME_HEIGHT, 1080)
    cap.set(cv.CAP_PROP_FPS, 60)
    cap.set(cv.CAP_PROP_FOURCC, cv.VideoWriter_fourcc(*'MJPG'))
    ret, frame = cap.read()
    time_acc = 0
    while not finished and ret:
        start = t.time()

        ret, frame = cap.read()

        time_acc += t.time() - start
        # print(t.time() - start)
        producer_consumer_img_lock.acquire()
        producer_consumer_img = frame
        producer_consumer_img_count += 1
        # print(time_acc / producer_consumer_img_count)
        producer_consumer_img_lock.release()
        # t.sleep(0.035)

def get_image(img_count):
    global producer_consumer_img, producer_consumer_img_count, producer_consumer_img_lock
    img = 0
    img_number = 0
    while True:
        producer_consumer_img_lock.acquire()
        if img_count >= producer_consumer_img_count:
            producer_consumer_img_lock.release()
            continue
        img = producer_consumer_img
        img_number = producer_consumer_img_count
        producer_consumer_img_lock.release()
        break
    return img, img_number

def img_consumer():
    # Load calib values
    global fps, fps_acc, fps_avg, time, frame_to_show, reset_first_img, finished, pik, debug, width, height
    global producer_consumer_img_lock, producer_consumer_img, producer_consumer_img_count
    global outer_M, outer_M_inv
    global M, M_inv
    global img2_outer, outer_wrapped

    # outer_src_points = calibrate_board_corners()
    # outer_dst_points = np.float32([(0, 0), (width, 0), (width, height), (0, height)]).reshape(-1, 1, 2)
    # outer_M, mask = cv2.findHomography(np.float32(outer_src_points).reshape(-1, 1, 2), outer_dst_points, cv2.RANSAC,
    #                                   5.0)
    # outer_M_inv = np.linalg.inv(outer_M)

    src_points, dst_points = calibrate_projector()
    M, mask = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5.0)
    M_inv = np.linalg.inv(M)
    reset_first_img = True
    frame, frame_count = get_image(0)
    img2 = frame
    frame_to_show = frame
    img2 = cv2.warpPerspective(img2, M, (width, height))

    calibrate_corners()

    # img2_outer = cv2.warpPerspective(frame_to_show, outer_M, (1024,512))
    lastOutput = black_canvas = np.zeros((height, width, 3), np.uint8)
    time = t.time()
    while not finished:
        frame, frame_count = get_image(frame_count)
        # outer_wrapped = cv2.warpPerspective(frame, outer_M, (1920, 1080))
        time_s = t.time()
        frame = cv2.warpPerspective(frame, M, (width, height))
        time_p1 = t.time()
        if reset_first_img:
            frame_to_show = np.zeros((height, width, 3), np.uint8)
            frame_to_show[:, :] = (255, 255, 255)  # not so black now
            t.sleep(1)
            frame, frame_count = get_image(frame_count)
            # img2_outer = cv2.warpPerspective(frame, outer_M, (1024, 512))
            frame = cv2.warpPerspective(frame, M, (width, height))
            img2 = frame
            reset_first_img = False
            continue

        diff_img = find_diff(frame, img2)
        frame_to_show = find_boxes_from_img(diff_img, frame, width, height, lastOutput)
        if not debug:
            lastOutput = frame_to_show
        fps = int(1 / float((t.time() - time)))
        fps_acc += 1
        fps_avg = int(fps_acc / (t.time() - start_t))
        time = t.time()

        # print(fps_avg, fps, f"Time spent warping: {time_p1 - time_s}, Time spent on img: {t.time() - time_s}")
    finished = True

def insert_marker_on_img(marker, img, cord, size):
    img[cord[0]:cord[0] + size, cord[1]:cord[1] + size] = marker
    return img

def calibrate_projector():
    global height, width, frame_to_show, debug_frame
    print("I AM HERE")
    size = 100
    initial_offset = 50
    buffer = 10
    sleep_b = 0.4
    ids_to_find = [2, 3, 4, 5]
    aruco_images = [get_aruco(marker_id, size) for marker_id in ids_to_find]

    center = (height // 2, width // 2)
    marker_2 = (center[0] - size - buffer, center[1] - size - buffer)  # Top Left
    marker_3 = (center[0] - size - buffer, center[1] + buffer)  # Top Right
    marker_4 = (center[0] + buffer, center[1] + buffer)  # Bottom Right
    marker_5 = (center[0] + buffer, center[1] - size - buffer)  # Bottom Left
    markers = [
        [marker_2, -initial_offset, -1, False],
        [marker_3, -initial_offset, 1, False],
        [marker_4, initial_offset, -1, False],
        [marker_5, initial_offset, 1, False]
    ]
    img_to_show = np.zeros((height, width, 3), np.uint8)
    img_to_show[:, :] = (255, 255, 255)  # make image white
    for index in range(len(markers)):
        img_to_show = insert_marker_on_img(aruco_images[index], img_to_show, markers[index][0], size)
    frame_to_show = img_to_show
    t.sleep(sleep_b)

    src_points = []
    dst_points = []
    done = False
    frame_count = 0
    while not done:
        frame, frame_count = get_image(frame_count)
        (corners, ids, rejected) = cv2.aruco.detectMarkers(frame, arucoDict, parameters=arucoParams)
        if ids is None:
            t.sleep(.5)
            continue
        correct_ids = [mark_id for mark_id in ids if mark_id in ids_to_find]
        print(ids)
        if len(correct_ids) < 4:
            print("Didnt find all that i wanted trying again")
            t.sleep(.5)
            continue
        print("Found it!")
        for (corner, marker_id) in zip(corners, ids):
            if marker_id not in ids_to_find:
                continue
            corner = corner.reshape((4, 2))
            marker_id = int(marker_id[0])
            marker_y, marker_x = markers[marker_id - 2][0]
            marker_x = int(marker_x)
            marker_y = int(marker_y)
            src_points.append((corner[marker_id - 2], marker_id))
            if marker_id == 2:
                dst_points.append(((marker_x, marker_y), marker_id))
            elif marker_id == 3:
                dst_points.append(((marker_x + size, marker_y), marker_id))
            elif marker_id == 4:
                touple = (marker_x + size, marker_y + size)
                dst_points.append((touple, marker_id))
            elif marker_id == 5:
                dst_points.append(((marker_x, marker_y + size), marker_id))

        done = True

    src_points.sort(key=lambda x: x[1])
    dst_points.sort(key=lambda x: x[1])
    src_points = [item[0] for item in src_points]
    dst_points = [item[0] for item in dst_points]

    return np.float32(src_points).reshape(-1, 1, 2), np.float32(dst_points).reshape(-1, 1, 2)

def calibrate_corners():
    global frame_to_show, corner_points

    while calibrating:
        frame = np.zeros((height, width, 3), np.uint8)
        corner_points = [(x_1, y_1), (x_2, y_2), (x_3, y_3), (x_4, y_4)]
        for i in range(len(corner_points)):
            p1 = corner_points[i]
            p2 = corner_points[(i + 1) % len(corner_points)]
            cv2.line(frame, p1, p2, (255, 255, 255), thickness=1)
        frame_to_show = frame

    # Do something impressive here

def insert_buffer(touple, id, buffer, dir=0):
    x = y = 0
    if dir == 0:
        x = 1
    else:
        y = 1
    if id == 2:
        return touple[0] + (buffer * x), touple[1] + (buffer * y)
    elif id == 3:
        return touple[0] + (buffer * x), touple[1] - (buffer * y)
    elif id == 4:
        return touple[0] - (buffer * x), touple[1] - (buffer * y)
    elif id == 5:
        return touple[0] - (buffer * x), touple[1] + (buffer * y)
    exit(1337)

# True if it is in bounds false otherwise
def check_bounds(x, y, size, width, height):
    if x < 0 or x + size > width:
        return False
    if y < 0 or y + size > height:
        return False
    return True

def calibrate_board_corners():
    cornerDict = {}
    src_points = []
    frame_count = 0
    while len(src_points) < 4:
        frame, frame_count = get_image(frame_count)
        (corners, ids, rejected) = cv2.aruco.detectMarkers(frame, arucoDict, parameters=arucoParams)
        for (markerCorner, id) in zip(corners, ids):
            if str(id) not in cornerDict:
                corners = markerCorner.reshape((4, 2))
                (topLeft, topRight, bottomRight, bottomLeft) = corners
                c = topLeft
                if id[0] == 1:
                    c = bottomLeft
                elif id[0] == 69:
                    c = topLeft
                elif id[0] == 420:
                    c = topRight
                elif id[0] == 666:
                    c = topLeft
                cornerDict[str(id)] = (c, id)
                src_points.append([c, id])
    src_points.sort(key=lambda x: x[1])
    src_points = [item[0] for item in src_points]
    return src_points

threading.Thread(target=img_producer).start()
threading.Thread(target=img_consumer).start()

def change_debug_mode():
    global debug, fps_avg, fps_acc, time
    debug = not debug
    fps_acc = fps_avg = fps
    time = t.perf_counter()

def reset_first_img_func():
    global reset_first_img
    reset_first_img = True

def fullscreen_func():
    global finished
    finished = True

def submit():
    global morph_size, morph_size_var, threshold_area_var, threshold_area, threshold_area_max_var, threshold_area_max, pik
    global offset_y_l, offset_x_h, offset_y_h, offset_x_l, offset_y_l_var, offset_x_h_var, offset_x_l_var, offset_y_h_var
    global threshold
    global x_1, x_1_var
    global y_1, y_1_var
    global x_2, x_2_var
    global y_2, y_2_var
    global x_3, x_3_var
    global y_3, y_3_var
    global x_4, x_4_var
    global y_4, y_4_var
    global calibrating, calibrating_var
    global give_help, give_help_var

    morph_size = int(morph_size_var.get())
    threshold_area = int(threshold_area_var.get())
    threshold_area_max = int(threshold_area_max_var.get())
    offset_x_l = int(offset_x_l_var.get())
    offset_x_h = int(offset_x_h_var.get())
    offset_y_h = int(offset_y_h_var.get())
    offset_y_l = int(offset_y_l_var.get())
    threshold = int(threshold_var.get())
    x_1 = int(x_1_var.get())
    y_1 = int(y_1_var.get())
    x_2 = int(x_2_var.get())
    y_2 = int(y_2_var.get())
    x_3 = int(x_3_var.get())
    y_3 = int(y_3_var.get())
    x_4 = int(x_4_var.get())
    y_4 = int(y_4_var.get())
    calibrating = bool(calibrating_var.get())
    give_help = bool(give_help_var.get())

def create_entry(text, variable, row):
    label = tk.Label(top, text=text)
    entry = tk.Entry(top, textvariable=variable, font=('calibre', 10, 'normal'))
    label.grid(row=row, column=0)
    entry.grid(row=row, column=1)

debug_button = tk.Button(text="DEBUG MODE", command=change_debug_mode)
debug_button.grid(row=0, column=0)
reset_first_img_button = tk.Button(text="RESET DIFF IMAGE", command=reset_first_img_func)
reset_first_img_button.grid(row=0, column=1)
submit_button = tk.Button(text="Submit entries", command=submit)
submit_button.grid(row=0, column=2)
fullscreen_button = tk.Button(text="FULSCREEN", command=fullscreen_func)
fullscreen_button.grid(row=0, column=3)

create_entry("Morph size: ", morph_size_var, 1)
create_entry("threshold min", threshold_area_var, 2)
create_entry("threshold_max", threshold_area_max_var, 3)
create_entry("Min x-offset", offset_x_l_var, 4)
create_entry("Max x-offset", offset_x_h_var, 5)
create_entry("Min y-offset", offset_y_l_var, 6)
create_entry("Max y-offset", offset_y_h_var, 7)
create_entry("Threshold", threshold_var, 8)

create_entry("X1", x_1_var, 9)
create_entry("Y1", y_1_var, 10)
create_entry("X2", x_2_var, 11)
create_entry("Y2", y_2_var, 12)
create_entry("X3", x_3_var, 13)
create_entry("Y3", y_3_var, 14)
create_entry("X4", x_4_var, 15)
create_entry("Y4", y_4_var, 16)
create_entry("CALIBRATING", calibrating_var, 17)
create_entry("Give aids?", give_help_var, 18)

cv2.namedWindow("Window_Show")
cv2.moveWindow("Window_Show", 2048, 0)
# cv2.namedWindow("Debug_Window_Show")
# cv2.moveWindow("Debug_Window_Show", 0, 0)

while not finished:
    cv2.waitKey(1)
    cv.imshow('Window_Show', frame_to_show)
    # frame, _ = get_image(0)
    # cv.imshow("Debug_Window_Show", debug_frame)
    #cv.imshow("Cue_window_show", cue_frame)

    top.update()


import os

TRAIN_PATH = "train/"
TEST_PATH = "test/"

if not os.path.exists(TRAIN_PATH + "0"):
    os.mkdir(TRAIN_PATH + "0")
if not os.path.exists(TRAIN_PATH + "1"):
    os.mkdir(TRAIN_PATH + "1")
if not os.path.exists(TEST_PATH + "0"):
    os.mkdir(TEST_PATH + "0")
if not os.path.exists(TEST_PATH + "1"):
    os.mkdir(TEST_PATH + "1")

train_images = [f for f in os.listdir(TRAIN_PATH) if f.endswith("jpg")]
test_images = [f for f in os.listdir(TEST_PATH) if f.endswith("jpg")]

for image in train_images:
    name, label = image.split("_")
    label = label[0]
    os.rename(TRAIN_PATH + image, TRAIN_PATH + f"{label}/" + name + ".jpg")

for image in test_images:
    name, label = image.split("_")
    label = label[0]
    os.rename(TEST_PATH + image, TEST_PATH + f"{label}/" + name + ".jpg")


import random
from pygame.sprite import Sprite

from game.utils.constants import SCREEN_WIDTH

class PowerUps(Sprite):
    def __init__(self, image, type):
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.y = 0
        self.rect.x = random.randint(120, SCREEN_WIDTH - 120)
        self.type = type
        self.strat_time = 0

    def update(self, game_speed):
        self.rect.y += game_speed

    def draw(self, screen):
        screen.blit(self.image, self.rect)


# -*- coding: utf-8 -*-
#
# Birdsong classificatione in noisy environment with convolutional neural nets in Keras
# Copyright (C) 2017 Báint Czeba, Bálint Pál Tóth (toth.b@tmit.bme.hu)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>. (c) Balint Czeba, Balint Pal Toth
#
# Please cite the following paper if this code was useful for your research:
#
# Bálint Pál Tóth, Bálint Czeba,
# "Convolutional Neural Networks for Large-Scale Bird Song Classification in Noisy Environment",
# In: Working Notes of Conference and Labs of the Evaluation Forum, Évora, Portugália, 2016, p. 8

# this script generates images from the preprocessed spectograms

from scipy import io
import pandas as pd
import numpy as np
import time
import pickle
import os
import h5py

hdf5path = '../birdclef_data/data_top999_nozero.hdf5' # it takes long if we save all the spectorgram, maybe it is better to call processNMostCommon from loadData.py with a small N (eg. =3)
todirrootpath = '../images_bw_3/'

f = h5py.File(hdf5path, 'r')
X = f.get('X')
y = f.get('y')
mediaId = f.get('MediaId')
classId = f.get('ClassId')
print(mediaId[0])

y_class = np.empty((0,1))
for row in y:
    for i in range(len(row)):
        if row[i]==1:
            y_class=np.vstack((y_class,i))

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.ioff()

fig = plt.figure(frameon=False)
ax = fig.add_axes([0, 0, 1, 1])
ax.axis('off')

i=0
lenX=X.shape[0]
img_artist = ax.imshow(np.flipud(X[0][0]), cmap=plt.cm.binary)

if not os.path.exists(todirrootpath):
    os.makedirs(todirrootpath)

for i in range(X.shape[0]):
    directory=os.path.join(todirrootpath,'{}'.format(classId[i][0]))
    if not os.path.exists(directory):
            os.makedirs(directory)
    print('{}/{}'.format(i, lenX))
    img_artist.set_data(np.flipud(X[i][0]))
    fileNumber=0
    while(os.path.isfile(os.path.join(directory, '{}_{}.png'.format(int(mediaId[i][0]), fileNumber)))):
        fileNumber=fileNumber+1;
        print(fileNumber)

    with open(os.path.join(directory, '{}_{}.png'.format(int(mediaId[i][0]), fileNumber)), 'w') as outfile:
        fig.canvas.print_png(outfile)

f.close()


"""
This solution uses a recursive function to go through each path in the tree
and check if any path has a total value equal to the sum
"""

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def has_path_sum(self, root: TreeNode, sum: int) -> bool:
        # check if the node is empty
        if not root:
            return False

        def find_sum(node, total):
            # if it is a leaf node, check if the sum is equal to all the nodes'
            # value on the path
            if not node.left and not node.right:
                return (total + node.val) == sum

            # check if the left or right path has a total sum equal to the
            # input sum
            left, right = False, False
            if node.left:
                left = find_sum(node.left, total + node.val)
            if node.right:
                right = find_sum(node.right, total + node.val)

            return left or right

        return find_sum(root, 0)