#!/usr/bin/python3

print('---------------------------------------------------');
print('You entered NuPhy.py, merger of calc-react and srimbuster');
print("""
Three MODES:
  nuphy.py  react  -i h2,c12  -o h1  -e 19  -a 11
  nuphy.py  srim  -i h1 -m si -e 28 -w 5500um -n 100
  nuphy.py  srim  -i he4 -m c  -e 5.804 -w 100ug -n 100
STORE in file:
  nuphy.py  srim  -i he4 -m c  -e 5.804 -w 100ug -n 100 -S ~/srim_h1h2
 LIST:
  nuphy.py  store -S ~/srim_h1h2
 PLOT
  nuphy.py  store -S ~/srim_h1h2,0,1
  nuphy.py  store -S ~/srim_h1h2,0,1 -f yz
  nuphy.py  store -S ~/srim_h1h2,0,1 -f x
  nuphy.py  store -S ~/srim_h1h2,0,1 -f cos
  nuphy.py  store -S ~/srim_h1h2,0,1 -f cosy
  nuphy.py  store -S ~/srim_h1h2,0,1 -f 0.100
 RRATE:
  nuphy.py  rrate -n 10nA  -t 10ug          -m c   -xs 0.1
  nuphy.py  rrate -n 10uA  -t 10um   -d 1.8 -m c   -xs 0.1
  nuphy.py  rrate -n 1uA   -t 100ug         -m h2  -xs 0.1
  ...  ... current Nb/t;  rho_S;  Na/Mm ;  100mb=> R
""")
print('---------------------------------------------------');
#time.sleep(1)
#http://stackoverflow.com/questions/7427101/dead-simple-argparse-example-wanted-1-argument-3-results
import argparse

import numpy as np
import pandas as pd
import NuPhyPy.db.ReadNubase  as db
import NuPhyPy.Reactions.Kinematics as kin

import re
################# SRIM
#import NuPhyPy.srim as sr
import NuPhyPy.srim.srim as sr

from NuPhyPy.db.ReadNubase import gas,densities,elements,molarweights
#import NuPhyPy.db.ReadNubase as db
#from NuPhyPy.db.ReadNubase import gas,densities,elements

import NuPhyPy.srim.compounds as srcomp

######################################
# look for compounds/elements/isotopes : return tabulated density
#
######################################
def material_density( matnam ):
    #print('i... testing if ',matnam,'is in compounds ')
    #print( 'D...  keys=',srcomp.material_text.keys()  )
    mm_amu=1.0  # NONSENSE
    if matnam.title() in srcomp.material_text.keys():
        print('F... FOUND in compounds')
        dens=srcomp.get_density( matnam.title()  )
        print('i... FOUND density from compounds=',dens)  # mm_amu ???
        # mm_amu determined probably somewhere in SRIM.... # i dont need
    elif matnam.title() in elements:
        print('F...  ',matnam,' FOUND in elements ')
        CC=matnam.title()
        #print(CC,type(CC))
        zzz=elements.index(  matnam.title() )
        #print(zzz)
        dens=densities[ elements.index( matnam.title() ) ]
        mm_amu=molarweights[  elements.index( matnam.title() ) ]  # Mm for mix of isotopes
        print('i... element ',matnam.title() ,'found, density is set to:', dens)
    else:
        print('i... element NOT found, maybe it is an isotope?')
        isotope=db.isotope( matnam.title() )
        dens=isotope.isodensity
        mm_amu=isotope.amu   # This is for cross sections  Mm pure
        print('i...  isotope: {} found;  density is set to: {:.4f} g/cm3'.format(isotope.name,dens) )
    return dens,mm_amu

#
#################### PARSE

parser=argparse.ArgumentParser(description="""NuPhy.py

10um of Si:

./nuphy.py srim  -i he4 -m si -e 5.804 -n 100 -t 10um

with density:
./nuphy.py srim  -i he4 -m si -e 5.804 -n 100 -t 10um  -d 1.2

gas:
./nuphy.py srim  -i he4 -m air -e 5.804 -n 100 -t 10000um  -P 150 -T 293

store:
./nuphy.py store

""");

parser.add_argument('mode' ,help='react; srim; store')
parser.add_argument('-i','--incomming',  default="h2,c12" , help='REACT')
parser.add_argument('-o','--outgoing',  default="h2,c12"  , help='REACT')
parser.add_argument('-a','--angle',  default=0 , help='REACT')

parser.add_argument('-e','--energy', default=5.804 , help='REACT+SRIM')

parser.add_argument('-t','--thickness',  default="100ug" , help='SRIM')
parser.add_argument('-m','--material',   default="c12",help='SRIM')
parser.add_argument('-n','--number',  default=100 , help='SRIM')
parser.add_argument('-d','--density',  default="0" , help='SRIM')
parser.add_argument('-P','--Pressure',  default=1013.25e+3 ,type=float, help='pressure in Pascals e.g.1013250')
parser.add_argument('-T','--Temperature',  default=273.15 , type=float,help='SRIM')

parser.add_argument('-s','--silent', action="store_true",   help='SRIM')

parser.add_argument('-xs','--xsection', default=1.0,   help='XS in barns')

parser.add_argument('-S','--Store', default="",   help='Determine the STORE file; When reading- tell spectrum # -S file,0,1')

parser.add_argument('-f','--fwhm', default=0.,   help='with STORE:  convolute with a detector resolution (FWHM) in [MeV]; BUT ALSO  -f yz ... plot Y vs. Z scatter;  -f cos ... plot radians scatter; -f view ... view')

#parser.add_argument('-t','--thickness',  default="4" , help='SRIM')
args=parser.parse_args()

############################################
#
#  reaction
#
##########################################
if args.mode=='react':
    print(args.mode,' incomming=',args.incomming, 'outgoing=',args.outgoing)
    ip=args.incomming.split(',')
    op=args.outgoing.split(',')
    nu1=db.isotope( ip[0] )
    nu2=db.isotope( ip[1] )
    nu3=db.isotope( op[0] )
    if len(op)==1:
        print( 'i...',nu1.A +  nu2.A - nu3.A )
        print('i...', nu1.Z +  nu2.Z - nu3.Z )
        nu4=db.isotope( nu1.A +  nu2.A - nu3.A, nu1.Z +  nu2.Z - nu3.Z )
    else:
        nu4=db.isotope( op[1] )
    TKE=float( args.energy)
    th=float( args.angle)
    res=kin.react( nu1,nu2,nu3,nu4, TKE=TKE, theta=th,silent=0)

###############################################################  SRIM
#
#  srim    -  first functions -
#
###############################

##################
#  question on GAS = we use 2x
#################
def isitGas( material ):
    if material.title() in srcomp.material_gas:
        return 1
    if material.title() in elements:
        eZ=elements.index(material.title())
        if gas[ eZ ]==1:
            return 1
    try:
        isotope=db.isotope( material , silent=True  )
    except:
        return 0
    if isotope==None:
        eZ=isotope.Z
        if gas[ eZ ]==1:
            return 1
    return 0

#####################################
# get_thick_rho  ......  I extract the part
#         return   thick [ug/cm2] and rho g/cm3
#####################################
def get_thick_rho( material,  thick, rho ):   #convert properly um and find rho
    print('D... in  get_thick_rho ::: DUPLICITE CODE:', material, thick, rho, '-------------')
    rho=float(rho)
    if rho==0:
        rho,mm_amu=material_density(material) # compound/element/isot = ALL
    # GASEOUS DENSITY
    # 1/ if compound - rho from function
    # 2/ element:
    rho2=rho # to keep if solid phase
    #========test gas phase =========
    if material.title() in srcomp.material_gas:
        """
        trim assumes the target as STP before 1982
        T=273.15 K
        p=1013.25 kPa
        SRIMHelp/helpTR23.rtf
        """
        R=1013.25e+3/273.15/rho
        rho2=args.Pressure/R/args.Temperature
        print('i...rho at STD (0deg,1013kPa)=',rho,' NEW now=',rho2)
    elif material.title() in elements:
        #print("D... rho - elements")
        eZ=elements.index(material.title())
        print("D... rho - elements  eZ={:d}".format(eZ))
        #print(gas)
        if gas[ eZ ]==1:
            R=1013.25e+3/273.15/rho
            rho2=args.Pressure/R/args.Temperature
            print('i...rho at STD (0deg,1013kPa)=',rho,' NEW now=',rho2)
    else: # could be also a gaseous  isotope
        print('D... maybe gaseous isotope?')
        try:
            isotope=db.isotope( material , silent=True )
        except:
            isotope=None
        if isotope==None:
            print('D... not a gaseous isotope')
        else:
            eZ=isotope.Z
            if gas[ eZ ]==1:
                R=1013.25e+3/273.15/rho
                rho2=args.Pressure/R/args.Temperature
                print('i...rho at STD (0deg,1013kPa)=',rho,' NEW now=',rho2)
    rho=rho2
    print("D... rho WAS deduced")
    # THICKNESS TO mgcm2:    ##### MY UNIT WILL BE mg/cm2
    thickok=False
    if thick.find('ug')>0:
        thick=float(thick.split('ug')[0])/1000
        thickok=True
    elif thick.find('mg')>0:
        thick=float( thick.split('mg')[0] )
        thickok=True
    elif thick.find('um')>0:
        print('!... um ! I need rho=',rho)
        thick=float(thick.split('um')[0])
        thick=sr.get_mgcm2( thick,  rho ) # um in, mgcm2 out
        thickok=True
    elif thick.find('mm')>0:
        print('!... mm ! I need rho=',rho)
        thick=float(thick.split('mm')[0])
        thick=sr.get_mgcm2( thick*1000,  rho ) # um in, mgcm2 out
        thickok=True
    if not(thickok):
        print('!...  thicknesses must be in ug,mg or um,mm')
        quit()
    print('i... {} thickness {:.6f} mg/cm2 for rho={:.3f} ... {:.0f} A = {:.2f}um'.format( material.title(),thick,
                                                rho,1000*thick/rho/1e-2  ,   1000*thick/rho/1e+2 ) )
    return thick, rho, mm_amu

######################################
#PREPARE TRIMIN
#  prepare single layer, return one TRIM.IN line
########################################
def prepare_trimin( material,  thick,  rho  ):
    '''
    Here I prepare materials:  single layers
    '''
    print('D... preparing TRIMIN:', material, thick, rho, '-------------')
    #print('D... PV/T density:')
    rho=float(rho)
    if rho==0:
        rho,mm_amu=material_density(material) # compound/element/isot = ALL
    # GASEOUS DENSITY
    # 1/ if compound - rho from function
    # 2/ element:
    rho2=rho # to keep if solid phase
    if material.title() in srcomp.material_gas:
        """
        trim assumes the target as STP before 1982
        T=273.15 K
        p=1013.25 kPa
        SRIMHelp/helpTR23.rtf
        """
        R=1013.25e+3/273.15/rho
        rho2=args.Pressure/R/args.Temperature
        print('i...rho at STD (0deg,1013kPa)=',rho,' NEW now=',rho2)
    elif material.title() in elements:
        print("D... rho - elements")
        eZ=elements.index(material.title())
        print("D... rho - elements  eZ=",eZ)
        if gas[ eZ ]==1:
            R=1013.25e+3/273.15/rho
            rho2=args.Pressure/R/args.Temperature
            print('i...rho at STD (0deg,1013kPa)=',rho,' now=',rho2)
    else: # could be also a gaseous  isotope
        print('D... maybe gaseous isotope?')
        try:
            isotope=db.isotope( material , silent=True )
        except:
            isotope=None
        if isotope==None:
            print('D... not a gaseous isotope')
        else:
            eZ=isotope.Z
            if gas[ eZ ]==1:
                R=1013.25e+3/273.15/rho
                rho2=args.Pressure/R/args.Temperature
                print('i...rho at STD (0deg,1013kPa)=',rho,' now=',rho2)
    rho=rho2
    print("D... rho deduced")
    # THICKNESS TO mgcm2:    ##### MY UNIT WILL BE mg/cm2
    thickok=False
    if thick.find('ug')>0:
        thick=float(thick.split('ug')[0])/1000
        thickok=True
    elif thick.find('mg')>0:
        thick=float( thick.split('mg')[0] )
        thickok=True
    elif thick.find('um')>0:
        print('!... um ! I need rho=',rho)
        thick=float(thick.split('um')[0])
        thick=sr.get_mgcm2( thick,  rho ) # um in, mgcm2 out
        thickok=True
    elif thick.find('mm')>0:
        print('!... mm ! I need rho=',rho)
        thick=float(thick.split('mm')[0])
        thick=sr.get_mgcm2( thick*1000,  rho ) # um in, mgcm2 out
        thickok=True
    if not(thickok):
        print('!...  thicknesses must be in ug,mg or um, mm')
        quit()
    print('i... {} thickness {:.6f} mg/cm2 for rho={:.3f} ... {:.0f} A = {:.2f}um'.format( material.title(),thick,
                                                rho,1000*thick/rho/1e-2  ,   1000*thick/rho/1e+2 ) )

    # AT THIN MOMENT I HAVE A GOOD rho an thick in mgcm2

    #print('D... goto TRIMIN MULTI')
    TRIMIN=sr.PrepSrimFile( ion=n0, energy=Eini, angle=0., number=number,
                            mater=material, thick=thick, dens=rho  )

    return TRIMIN
############# END OF PREPARE TRIMIN

###############################
#  SRIM MODE
#        here i try to prepare also multilayer  materials
#
##############################

if args.mode=='srim':
    print('i...',args.mode)
    ipath=sr.CheckSrimFiles()

    n0=db.isotope(args.incomming )
    Eini=float( args.energy )
    number=int(args.number)
    rho=args.density
    thick=args.thickness
    material=args.material.title()  # this will get complicated with layers
    nmats=len(material.split(','))
    print("D... counting number of layers",nmats)
    if nmats>1:
        print('!... ',nmats,'materials - TEST REGIME:')
        if nmats!=len(thick.split(',')):
            print('!... NOT THE SAME NUMBER OF THICKNESSES')
            quit()
        if nmats!=len(rho.split(',')):
            if float(rho)!=0.:
                print('!... NOT THE SAME NUMBER OF densities')
                quit()
            else:
                rho=','.join( map(str,[0]*nmats) )
        print('i... PREPARING ANALYSIS  mat, thick, rho:')
        TRILIST=[]
        for imat in range(nmats):
            print(imat,'... =========', material.split(',')[imat].title(), thick.split(',')[imat],  rho.split(',')[imat],
                  "================================"  )
            TRILIST.append( prepare_trimin(  material.split(',')[imat].title(), thick.split(',')[imat],  rho.split(',')[imat]   )   )
        print('i... I GOT ALL TRIM.IN files. Now somebody must merge....')
        ############################   MERGING  LAYERS ##############
        print('D--------------')
        for xi,xii in enumerate(TRILIST):  print( xi, xii,'\n')          # PRINT
        TRIMIN="==> SRIM-2013.00 This file controls TRIM Calculations.\r\n"
        #TRIMIN= TRIMIN + TRILIST[0].split("\n")[0].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[1].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[2].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[3].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[4].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[5].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[6].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[7].rstrip()+'\r\n'   # target material  num ele and layers
        #TRIMIN=TRIMIN+"            8 \r\n"

        #print(TRIMIN)
        #quit()
        #############################################################
#        with open('TRIM.IN.ALL','w') as f:
#            for imat in range(nmats):
#                f.write(TRILIST[imat])
        line8=[]      # Target material+1
        TRILISTTOT=[] # totallist for Atom
        lineLay=[]    # Layer Layer long line
        layerList=[]
        for imat in range(nmats):
            listOfLines=TRILIST[imat].split('\n')
            if listOfLines[7].find('Target material')<0:
                print('!... Target material line not detected...quit')
                quit()
            # define line8 (7: Target material)
            line8.append(   re.findall(r'".+"|[\w\.]+',  listOfLines[8] )  )
            TRILISTTOT.extend(  listOfLines  )
            # lineLay...
            liLa=[ i for i in listOfLines if re.match('^Layer\s+Layer\s+Name',i)  ]
            lineLay.extend(liLa)  # line with columns for layers.
            #---- duplicate: but i find #line of Layer Layer +2
            for j,v in enumerate( listOfLines ):
                if v.find('Layer')>=0 and v.find('Density')>0:
                    print(imat,'LAYER LINE= #',j+2+1) # starts with 0
                    layerList.append( re.findall(r'".+"|[\w\.\-]+', listOfLines[j+2] ) )
        #print(line8)
        layname='...'.join( [ i[0].strip("\"").rstrip() for i in line8]  )
        layname='"'+layname+'"'
        nelems=sum( map(int, [i[1] for i in line8] ) )
        nlayers=sum( map(int, [i[2] for i in line8] ) )
        print(layname,nelems,nlayers)
        TRIMIN=TRIMIN+"{} {} {}\r\n".format(layname,nelems,nlayers)
        #TRIMIN= TRIMIN + TRILIST[0].split("\n")[8].rstrip()+'\r\n'  # He 4  into C  - nedavat
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[9].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[10].rstrip()+'\r\n'
        TRIMIN= TRIMIN + TRILIST[0].split("\n")[11].rstrip()+'\r\n'

        # NOW I need to get all Atom .. = ... = lines
        #print(TRILISTTOT)
        #regex = re.compile('^Atom\s\d+\s=\s')
        atoms=[i for i in TRILISTTOT if re.match('^Atom\s\d+\s=\s',i) ]
        if len(atoms)!=nelems:
            print('!... Atoms lines and # elements differ!')
            quit()
        #        atoms=[m.group(1) for l in TRILISTTOT for m in [regex.search(l)] if m]
        #atoms=re.findall(r'Atom\s\d+\s=\s', TRILISTTOT)
        for i,v in enumerate(atoms):
            atoms[i]=re.sub( 'Atom\s(\d+)\s','Atom '+str(i+1)+' ' , v ).rstrip()
        print( '\n'.join( atoms ) )
        TRIMIN=TRIMIN+ '\r\n'.join(atoms)+'\r\n'
        # NOW I need Layer Layer
        preLayer=re.sub(r'^(.+Density).+$',r'\1', lineLay[0] )
        for i,v in enumerate(lineLay):
            lineLay[i]=re.sub('^.+?Density ','', v).strip()
        print( preLayer,'  '.join(lineLay) )
        TRIMIN=TRIMIN+preLayer+' '+'  '.join(lineLay)+'\r\n'
        # NOW I need the same with next "stoich stoich stoich...."
        lineStoich="Numb.   Description                (Ang) (g/cm3)   "+" Stoich "*nelems
        print(lineStoich)
        TRIMIN=TRIMIN+lineStoich+'\r\n'
        # NOW I need 1   "Layer 1";  2  "Layer 2"
        zeroes=0
        for i,v in enumerate(layerList):
            zstring=' 0   '*zeroes
            zpost=' 0   '*(nelems-zeroes -  int(line8[i][1]) )
            prepart='  '.join(v[1:4])
            postpart='  '.join(v[4:])
            print( "{} {} {} {} {}".format(i+1,prepart,zstring,postpart, zpost) )
            TRIMIN=TRIMIN+ "{} {} {} {} {}\r\n".format(i+1,prepart,zstring,postpart, zpost)
            zeroes=zeroes+int(line8[i][1])
            # 16 fields;
        # NOW I need to copy lines with GAS.....
        lineGas="0  Target layer phases (0=Solid, 1=Gas)"
        print(lineGas)
        TRIMIN=TRIMIN+lineGas+'\r\n'
        for i,v in enumerate(layerList):
            ###print( 'material====',v )
            print(  isitGas(v[1]),' '   , end=' ')
            TRIMIN=TRIMIN+ str(isitGas(v[1]) )+' '
        print()
        TRIMIN=TRIMIN+'\r\n'
        ######## RAGG
        lineBragg="Target Compound Corrections (Bragg)"
        print(lineBragg)  # i want to put all lines  12 ( i think)
        TRIMIN=TRIMIN+lineBragg+'\r\n'
        for imat in range(nmats):
            listOfLines=TRILIST[imat].split('\n')
            for jmat in range( len(listOfLines) ):
                if listOfLines[jmat].find('Target Compound Corrections')>=0:
                    #print( listOfLines[jmat].rstrip() )
                    print( listOfLines[jmat+1].rstrip()  , end=' ')
                    TRIMIN=TRIMIN+ listOfLines[jmat+1].rstrip() +' '
                    break
            if jmat==len(listOfLines)-1:
                print('!... Bragg line not found')
                quit()
        print()
        TRIMIN=TRIMIN+'\r\n'
        #Individual target atom displacement energies (eV)
        lineAtomDisp="Individual target atom displacement energies (eV)"
        print(lineAtomDisp)
        TRIMIN=TRIMIN+lineAtomDisp+'\r\n'
        for imat in range(nmats):
            listOfLines=TRILIST[imat].split('\n')
            for jmat in range( len(listOfLines) ):
                if listOfLines[jmat].find('Individual target atom displacement energies')>=0:
                    #print( listOfLines[jmat].rstrip() )
                    print( listOfLines[jmat+1].rstrip()  , end=' ')
                    TRIMIN=TRIMIN+ listOfLines[jmat+1].rstrip() +' '
                    break
            if jmat==len(listOfLines)-1:
                print('!... Displacement line not found')
                quit()
        print()
        TRIMIN=TRIMIN+'\r\n'

        #Individual target atom displacement energies (eV)
        lineAtomDisp="Individual target lattice binding energies (eV)"
        print(lineAtomDisp)
        TRIMIN=TRIMIN+lineAtomDisp+'\r\n'
        for imat in range(nmats):
            listOfLines=TRILIST[imat].split('\n')
            for jmat in range( len(listOfLines) ):
                if listOfLines[jmat].find('Individual target atom lattice binding energies')>=0:
                    #print( listOfLines[jmat].rstrip() )
                    print( listOfLines[jmat+1].rstrip()  , end=' ')
                    TRIMIN=TRIMIN+ listOfLines[jmat+1].rstrip() +' '
                    break
            if jmat==len(listOfLines)-1:
                print('!... Lattice binding line not found')
                quit()
        print()
        TRIMIN=TRIMIN+'\r\n'
        #Individual target atom displacement energies (eV)
        lineAtomDisp="Individual target atom surface binding energies (eV)"
        print(lineAtomDisp)
        TRIMIN=TRIMIN+lineAtomDisp+'\r\n'
        for imat in range(nmats):
            listOfLines=TRILIST[imat].split('\n')
            for jmat in range( len(listOfLines) ):
                if listOfLines[jmat].find('Individual target atom surface binding energies')>=0:
                    #print( listOfLines[jmat].rstrip() )
                    print( listOfLines[jmat+1].rstrip()  , end=' ')
                    TRIMIN=TRIMIN+ listOfLines[jmat+1].rstrip() +' '
                    break
            if jmat==len(listOfLines)-1:
                print('!... Surface binding line not found')
                quit()
        print()
        TRIMIN=TRIMIN+'\r\n'
        print('Stopping Power Version (1=2011, 0=2011)')
        TRIMIN=TRIMIN+'Stopping Power Version (1=2011, 0=2011)\r\n'
        print(' 0 ')
        TRIMIN=TRIMIN+' 0\r\n'

        print('\n\n\n',TRIMIN,"\n\n\n")
        #quit()

    else:
        print("D... one material - preparing TRIMIN")
        TRIMIN=prepare_trimin(  material, thick,  rho   )
    #########################################
    # PREPARE FILE
    ##########################################
#    print('D... goto TRIMIN')
#    TRIMIN=sr.PrepSrimFile( ion=n0, energy=Eini, angle=0., number=number,
#                            mater=material, thick=thick, dens=rho  )

    # RUN ############################
    if args.silent:
        tmpp=sr.run_srim(ipath, TRIMIN,  silent=True, nmax=number)
    else:
        tmpp=sr.run_srim(ipath, TRIMIN,  silent=False, nmax=number)
    print(tmpp[-5:])
    if 'e' in tmpp:
        deint=tmpp['e'].max()-tmpp['e'].min()
        sigma=tmpp['e'].std()
        mean=tmpp['e'].mean()
        median=tmpp['e'].median()
        print()
        print( "{:.3f} MeV (median {:.3f}) +- {:.3f}  hi-lo={:.3f}  Eloss={:.3} MeV".format( mean, median,  sigma , deint ,  Eini-mean)   )
    else:
        #print(tmpp)
        print()
        print('{:.3f} +- {:.4f} um implanted depth'.format( tmpp['x'].mean(), tmpp['x'].std() )  )
        mean=0.0
        median=0.0
        deint=0.0
        sigma=0.0
        #print( tmpp['e'].max(),tmpp['e'].min(), de  )
    #plt.hist( tmpp['e'], 20 , facecolor='red', alpha=0.25)
    #    print("R...    E mean +- std")
    #    print(tmpp['e'].mean(), '  ' ,tmpp['e'].std() )
    #   print(tmpp['e'].mean(), '  ' ,tmpp['e'].std() )

    ### MAYBE - I WILL NUMBER ALREADY FROM HERE
    if args.Store!="":
        store = pd.HDFStore( args.Store )
        existing=len(store.keys())
        print('D... already existing is', existing )
        #print(store)
        if material.title() in srcomp.material_gas:
            pt='P{}_T{}'.format( args.Pressure, args.Temperature )
        else:
            pt=""

        fname='n{:03d}_{}_in_{:_<6s}_w{:_<6s}_r{:3.1f}_{}_n{:04d}_e{:06.3f}_ef{:06.3f}'.format( existing,
                            args.incomming, args.material, args.thickness, float(args.density),
                             pt, int(args.number), float(args.energy), mean )
        fname=fname.replace('.','_')
        store[fname] = tmpp
        print(store)
        store.close()

# RUN SRIM ENDED

###############################################################
#
#   Reaction Rate
#   rate=Nb/t; Nt/A ; sigma==cross section==xs
#####################################
if args.mode=='rrate':
    Nb=args.number
    if Nb[-2:]=='nA':   Nb=float(Nb[:-2])*1e-9/1.602176e-19
    elif Nb[-2:]=='uA': Nb=float(Nb[:-2])*1e-6/1.602176e-19
    elif Nb[-2:]=='mA': Nb=float(Nb[:-2])*1e-3/1.602176e-19
    else: Nb=float(Nb)

    sigma=  float(args.xsection)  # give it in barns= cm2

    rho_S,just_rho,Mm_amu=get_thick_rho( args.material , args.thickness, args.density )
    rho_S = rho_S  /1e+3 # mg -> g/cm2
    Na=6.24+23   # i think - Avogadro at/ mol   or 1e+26/kmol
    Mm=Mm_amu    # molar weight -  isotopic should be

    rrate=Nb * rho_S *Na /Mm *sigma
    print("Beam {:.4g} cps; XS={:.4g} barns; rho_S={:.4g} mg/cm2  Mm={:.3f} g/mol".format( Nb, sigma, 1000*rho_S, Mm) )
    print("Reaction rate: {:.4g} cps".format(rrate))
    print("Reaction rate: {:.4g} cps/sr".format(rrate/4/3.1415926 ))

###############################################################  STORE
#
#   (UN) store  and   plot
#
#####################################
if args.mode=='store':
    import matplotlib.pyplot as plt
    if args.Store!="":
        stor=args.Store.split(',')
        store = pd.HDFStore( stor[0] )
        if len(stor)>1:
            plots=[]
            #========= i want to plot dE histogram with convolution fwhm
            for inx in range( len(stor)-1 ):
                dfname=sorted(store.keys())[ int(stor[inx+1])]
                print('o... openning: ', dfname )
                df=store[dfname]
                plotme=False
                #==================  if fwhm .... CONVOLUTE;  yz ... scatter;  cosy cosz ...angles
                if args.fwhm=="x": #======= implantation plot
                    plt.hist( df['x'], 20, ec='k',alpha=0.3,label=dfname+' implant [um]')
                    plotme=True
                if args.fwhm=="y": #======= implantation plot
                    plt.hist( df['y'], 20, ec='k',alpha=0.3,label=dfname+' implant [um]')
                    plotme=True
                if args.fwhm=="z": #======= implantation plot
                    plt.hist( df['z'], 20, ec='k',alpha=0.3,label=dfname+' implant [um]')
                    plotme=True

                    #plt.scatter( df['z']/1e+4, df['y']/1e+4,alpha=0.3,label=dfname+' [um]' )
                if args.fwhm=="yz": #======= scatter plot  Y Z
                    plt.scatter( df['z'], df['y'],alpha=0.3,label=dfname+' [um]' )
                    plotme=True
                if args.fwhm=="cos" and  'e' in df.keys(): #======= scatter plot cosy cosz
                    plt.scatter( df['cosz'], df['cosy'], alpha=0.3, label=dfname+' [rad]' )
                    plotme=True

                if args.fwhm=="cosy" and  'e' in df.keys(): #======= scatter plot cosy cosz
                    plt.hist( df['cosy'], 20, ec='k',alpha=0.3,label=dfname+' implant [rad]')
                    plotme=True
                if args.fwhm=="cosz" and  'e' in df.keys(): #======= scatter plot cosy cosz
                    plt.hist( df['cosz'], 20, ec='k',alpha=0.3,label=dfname+' implant [rad]')
                    plotme=True
                if args.fwhm=="view": #======= VIEW DATA
                    print(df)
                try:
                    floatfwh=float(args.fwhm)
                except:
                    floatfwh=-1
                if floatfwh>=0.: #====== GENERATE GAUSS ======== fwhm=2.355sigma
                    if 'e' in df.keys():
                        print("...... MEAN ENERGY MODE")
                        print('i...  mean before convolution: {:.3f} {:.4f}'.format(df['e'].mean(),df['e'].std() ))
                        df['fwhm']=np.random.normal( 0.0, float(args.fwhm)/2.355 ,  len(df) )
                        df['e']=df['e']+df['fwhm']
                        print('i...  mean with   convolution: {:.3f} {:.4f}'.format(df['e'].mean(),df['e'].std() ))
                        plt.hist( df['e'], 20, ec='k',alpha=0.3,label=dfname)
                        plotme=True
                    elif floatfwh>0:
                        print("...... MEAN ENERGY MODE FOR IMPLANT")
                        df['fwhm']=np.random.normal( 0.0, float(args.fwhm)/2.355 ,  len(df) )
                        ENE=dfname.split('_')[-4]+'.'+dfname.split('_')[-3]
                        ENE=float(ENE[1:])
                        df['e']=ENE+df['fwhm']
                        print('i...  mean with   convolution: {:.3f} {:.4f}'.format(df['e'].mean(),df['e'].std() ))
                        plt.hist( df['e'], 20, ec='k',alpha=0.3,label=dfname)
                        plotme=True

                    else:
                        print("....... IMPLANT MODE")
                        plt.hist( df['x'], 20, ec='k',alpha=0.3,label=dfname+' [um]')
                        plotme=True

            if plotme:
                plt.legend( loc=4 , fontsize="x-small" )
                plt.show()
        else:
            for i,v in enumerate(sorted(store.keys())):print("{:03d} {}".format(i,v) )

        store.close()
    else:
        print('!... filename missing: use -S; open an item by specifying  line number after comma')


import pickle

import requests
from bs4 import BeautifulSoup

from beer import Beer
from beerid import beerid
from storeid import storeid

if __name__ == '__main__':
    beerstore_url = 'https://www.thebeerstore.ca/beers/'
    stores = list()
    beers = list()
    out_msg: str

    # Search and select store
    with open('data/store_ids.pkl', 'rb') as infile:
        stores = pickle.load(infile)

    search_store_str = input('Search for preferred store: ')
    searched_stores = dict()

    counter = 1
    store: storeid
    for store in stores:
        if search_store_str.lower() in store.name.lower() or search_store_str.lower() in store.address.lower():
            searched_stores.update({counter: store})
            print(f'{counter}. {store.address}')
            counter += 1

    selected_store_index = int(input('Select a store by number: '))
    selected_store: storeid = searched_stores.get(selected_store_index)

    print(f'Store {selected_store.name}, at {selected_store.address} selected!\n')
    ##############################################################

    # Search and select beer
    with open('data/beer_ids.pkl', 'rb') as infile:
        beers = pickle.load(infile)

    searched_beer_str = input('Search for a beer: ')
    searched_beers = dict()

    counter = 1
    beerval: beerid
    for beerval in beers:
        if searched_beer_str.lower() in beerval.name.lower() or searched_beer_str.lower() in beerval.brewer.lower():
            searched_beers.update({counter: beerval})
            print(f'{counter}. {beerval.name}')
            counter += 1

    searched_beers_index = int(input('Select a beer by number: '))
    print(searched_beers_index)
    selected_beer: beerid = searched_beers.get(searched_beers_index)

    print(f'Beer {selected_beer.name} selected!\n')
    ##############################################################

    headers = {
        'User-Agent': '',
        'Cookie': f'beer_home_store={selected_store.id}',
    }

    request = requests.get(beerstore_url + selected_beer.link, headers=headers)
    status = request.status_code
    soup = BeautifulSoup(request.content, 'html.parser')

    beer_name = soup.find('div', class_='desc').find('h1', class_='capitalize').contents[0]

    html_beer_formats = soup.findAll('div', class_='total_cans')
    html_all_beer = []
    all_beer = []

    for beer_type in html_beer_formats:
        for beer in beer_type.findAll('li', class_='d-column d-row option _cart'):
            html_all_beer.append(beer)

    for beer in html_all_beer:
        str_quan_type = str(beer.find('div', class_='col_1 first col_same').contents[2]).strip().split(" ")
        quantity = int(str_quan_type[0])
        type = str_quan_type[2]
        serving = str_quan_type[3]

        stock = 0
        if (len(beer.find('div', class_='col_2 second col_same').contents) == 3):
            stock = int(str(beer.find('div', class_='col_2 second col_same').contents[2]).strip())
        elif (beer.find('div', class_='col_2 second col_same').find('span', class_='outStock')):
            stock = 0
        else:
            stock = 'Packup'

        sale_price = float(str(beer.find('span', class_='sale_price').contents[0]).strip()[1::])
        on_sale = True if beer.find('span', class_='sale_badge') else False

        all_beer.append(Beer(beer_name, quantity, type, serving, sale_price, stock, on_sale))

    all_beer.sort(reverse=True)

    # for beer in all_beer:
    #     print(f'\n{beer}')

    bestOption: Beer
    beer: Beer
    for beer in all_beer:
        if (beer.inStock()):
            bestOption = beer
            break

    try:
        bestOption
    except NameError:
        out_msg = f'There was no {beer_name} in stock, looks like you\'ll have to try another beer.'
    else:
        out_msg = (
            'Best Option:\n'
            f'{bestOption}'
        )

    print(out_msg)


import torch
import math
import torch.nn as nn
import torch.nn.functional as F

from ntu_diar.scr.dataio.dataio import length_to_mask

class TDNNBlock(nn.Module):
    def __init__(
        self,
        in_channels,
        out_channels,
        kernel_size,
        dilation,
        activation=nn.ReLU,
        groups=1,
    ):
        super(TDNNBlock, self).__init__()
        self.conv = Conv1d(
            in_channels=in_channels,
            out_channels=out_channels,
            kernel_size=kernel_size,
            dilation=dilation,
            groups=groups,
        )
        self.activation = activation()
        self.norm = BatchNorm1d(input_size=out_channels)

    def forward(self, x):
        """ Processes the input tensor x and returns an output tensor."""
        return self.norm(self.activation(self.conv(x)))

class SERes2NetBlock(nn.Module):
    """An implementation of building block in ECAPA-TDNN, i.e.,
    TDNN-Res2Net-TDNN-SEBlock.
    Arguments
    ----------
    out_channels: int
        The number of output channels.
    res2net_scale: int
        The scale of the Res2Net block.
    kernel_size: int
        The kernel size of the TDNN blocks.
    dilation: int
        The dilation of the Res2Net block.
    activation : torch class
        A class for constructing the activation layers.
    groups: int
    Number of blocked connections from input channels to output channels.
    Example
    -------
    >>> x = torch.rand(8, 120, 64).transpose(1, 2)
    >>> conv = SERes2NetBlock(64, 64, res2net_scale=4)
    >>> out = conv(x).transpose(1, 2)
    >>> out.shape
    torch.Size([8, 120, 64])
    """

    def __init__(
        self,
        in_channels,
        out_channels,
        res2net_scale=8,
        se_channels=128,
        kernel_size=1,
        dilation=1,
        activation=nn.ReLU,
        groups=1,
    ):
        super().__init__()
        self.out_channels = out_channels
        self.tdnn1 = TDNNBlock(
            in_channels,
            out_channels,
            kernel_size=1,
            dilation=1,
            activation=activation,
            groups=groups,
        )
        self.res2net_block = Res2NetBlock(
            out_channels, out_channels, res2net_scale, kernel_size, dilation
        )
        self.tdnn2 = TDNNBlock(
            out_channels,
            out_channels,
            kernel_size=1,
            dilation=1,
            activation=activation,
            groups=groups,
        )
        self.se_block = SEBlock(out_channels, se_channels, out_channels)

        self.shortcut = None
        if in_channels != out_channels:
            self.shortcut = Conv1d(
                in_channels=in_channels,
                out_channels=out_channels,
                kernel_size=1,
            )

    def forward(self, x, lengths=None):
        """ Processes the input tensor x and returns an output tensor."""
        residual = x
        if self.shortcut:
            residual = self.shortcut(x)

        x = self.tdnn1(x)
        x = self.res2net_block(x)
        x = self.tdnn2(x)
        x = self.se_block(x, lengths)

        return x + residual

class AttentiveStatisticsPooling(nn.Module):
    """This class implements an attentive statistic pooling layer for each channel.
    It returns the concatenated mean and std of the input tensor.
    Arguments
    ---------
    channels: int
        The number of input channels.
    attention_channels: int
        The number of attention channels.
    Example
    -------
    >>> inp_tensor = torch.rand([8, 120, 64]).transpose(1, 2)
    >>> asp_layer = AttentiveStatisticsPooling(64)
    >>> lengths = torch.rand((8,))
    >>> out_tensor = asp_layer(inp_tensor, lengths).transpose(1, 2)
    >>> out_tensor.shape
    torch.Size([8, 1, 128])
    """

    def __init__(self, channels, attention_channels=128, global_context=True):
        super().__init__()

        self.eps = 1e-12
        self.global_context = global_context
        if global_context:
            self.tdnn = TDNNBlock(channels * 3, attention_channels, 1, 1)
        else:
            self.tdnn = TDNNBlock(channels, attention_channels, 1, 1)
        self.tanh = nn.Tanh()
        self.conv = Conv1d(
            in_channels=attention_channels, out_channels=channels, kernel_size=1
        )

    def forward(self, x, lengths=None):
        """Calculates mean and std for a batch (input tensor).
        Arguments
        ---------
        x : torch.Tensor
            Tensor of shape [N, C, L].
        """
        L = x.shape[-1]

        def _compute_statistics(x, m, dim=2, eps=self.eps):
            mean = (m * x).sum(dim)
            std = torch.sqrt(
                (m * (x - mean.unsqueeze(dim)).pow(2)).sum(dim).clamp(eps)
            )
            return mean, std

        if lengths is None:
            lengths = torch.ones(x.shape[0], device=x.device)

        # Make binary mask of shape [N, 1, L]
        mask = length_to_mask(lengths * L, max_len=L, device=x.device)
        mask = mask.unsqueeze(1)

        # Expand the temporal context of the pooling layer by allowing the
        # self-attention to look at global properties of the utterance.
        if self.global_context:
            # torch.std is unstable for backward computation
            # https://github.com/pytorch/pytorch/issues/4320
            total = mask.sum(dim=2, keepdim=True).float()
            mean, std = _compute_statistics(x, mask / total)
            mean = mean.unsqueeze(2).repeat(1, 1, L)
            std = std.unsqueeze(2).repeat(1, 1, L)
            attn = torch.cat([x, mean, std], dim=1)
        else:
            attn = x

        # Apply layers
        attn = self.conv(self.tanh(self.tdnn(attn)))

        # Filter out zero-paddings
        attn = attn.masked_fill(mask == 0, float("-inf"))

        attn = F.softmax(attn, dim=2)
        mean, std = _compute_statistics(x, attn)
        # Append mean and std of the batch
        pooled_stats = torch.cat((mean, std), dim=1)
        pooled_stats = pooled_stats.unsqueeze(2)

        return pooled_stats

class _BatchNorm1d(nn.Module):
    def __init__(
        self,
        input_shape=None,
        input_size=None,
        eps=1e-05,
        momentum=0.1,
        affine=True,
        track_running_stats=True,
        combine_batch_time=False,
        skip_transpose=False,
    ):
        super().__init__()
        self.combine_batch_time = combine_batch_time
        self.skip_transpose = skip_transpose

        if input_size is None and skip_transpose:
            input_size = input_shape[1]
        elif input_size is None:
            input_size = input_shape[-1]

        self.norm = nn.BatchNorm1d(
            input_size,
            eps=eps,
            momentum=momentum,
            affine=affine,
            track_running_stats=track_running_stats,
        )

    def forward(self, x):
        shape_or = x.shape
        if self.combine_batch_time:
            if x.ndim == 3:
                x = x.reshape(shape_or[0] * shape_or[1], shape_or[2])
            else:
                x = x.reshape(
                    shape_or[0] * shape_or[1], shape_or[3], shape_or[2]
                )

        elif not self.skip_transpose:
            x = x.transpose(-1, 1)

        x_n = self.norm(x)

        if self.combine_batch_time:
            x_n = x_n.reshape(shape_or)
        elif not self.skip_transpose:
            x_n = x_n.transpose(1, -1)

        return x_n

class BatchNorm1d(_BatchNorm1d):

    def __init__(self, *args, **kwargs):
        super().__init__(skip_transpose=True, *args, **kwargs)

class _Conv1d(nn.Module):

    def __init__(
        self,
        out_channels,
        kernel_size,
        input_shape=None,
        in_channels=None,
        stride=1,
        dilation=1,
        padding="same",
        groups=1,
        bias=True,
        padding_mode="reflect",
        skip_transpose=False,
        weight_norm=False,
    ):
        super().__init__()
        self.kernel_size = kernel_size
        self.stride = stride
        self.dilation = dilation
        self.padding = padding
        self.padding_mode = padding_mode
        self.unsqueeze = False
        self.skip_transpose = skip_transpose

        if input_shape is None and in_channels is None:
            raise ValueError("Must provide one of input_shape or in_channels")

        if in_channels is None:
            in_channels = self._check_input_shape(input_shape)

        self.in_channels = in_channels

        self.conv = nn.Conv1d(
            in_channels,
            out_channels,
            self.kernel_size,
            stride=self.stride,
            dilation=self.dilation,
            padding=0,
            groups=groups,
            bias=bias,
        )

        if weight_norm:
            self.conv = nn.utils.weight_norm(self.conv)

    def forward(self, x):

        if not self.skip_transpose:
            x = x.transpose(1, -1)

        if self.unsqueeze:
            x = x.unsqueeze(1)

        if self.padding == "same":
            x = self._manage_padding(
                x, self.kernel_size, self.dilation, self.stride
            )

        elif self.padding == "causal":
            num_pad = (self.kernel_size - 1) * self.dilation
            x = F.pad(x, (num_pad, 0))

        elif self.padding == "valid":
            pass

        else:
            raise ValueError(
                "Padding must be 'same', 'valid' or 'causal'. Got "
                + self.padding
            )

        wx = self.conv(x)

        if self.unsqueeze:
            wx = wx.squeeze(1)

        if not self.skip_transpose:
            wx = wx.transpose(1, -1)

        return wx

    def _manage_padding(
        self, x, kernel_size: int, dilation: int, stride: int,
    ):

        # Detecting input shape
        L_in = self.in_channels

        # Time padding
        padding = get_padding_elem(L_in, stride, kernel_size, dilation)

        # Applying padding
        x = F.pad(x, padding, mode=self.padding_mode)

        return x

    def _check_input_shape(self, shape):

        if len(shape) == 2:
            self.unsqueeze = True
            in_channels = 1
        elif self.skip_transpose:
            in_channels = shape[1]
        elif len(shape) == 3:
            in_channels = shape[2]
        else:
            raise ValueError(
                "conv1d expects 2d, 3d inputs. Got " + str(len(shape))
            )

        # Kernel size must be odd
        if self.kernel_size % 2 == 0:
            raise ValueError(
                "The field kernel size must be an odd number. Got %s."
                % (self.kernel_size)
            )
        return in_channels

    def remove_weight_norm(self):
        """Removes weight normalization at inference if used during training.
        """
        self.conv = nn.utils.remove_weight_norm(self.conv)

class Conv1d(_Conv1d):
    """1D convolution. Skip transpose is used to improve efficiency."""

    def __init__(self, *args, **kwargs):
        super().__init__(skip_transpose=True, *args, **kwargs)

class Res2NetBlock(nn.Module):
    def __init__(
        self, in_channels, out_channels, scale=8, kernel_size=3, dilation=1
    ):
        super(Res2NetBlock, self).__init__()
        assert in_channels % scale == 0
        assert out_channels % scale == 0

        in_channel = in_channels // scale
        hidden_channel = out_channels // scale

        self.blocks = nn.ModuleList(
            [
                TDNNBlock(
                    in_channel,
                    hidden_channel,
                    kernel_size=kernel_size,
                    dilation=dilation,
                )
                for i in range(scale - 1)
            ]
        )
        self.scale = scale

    def forward(self, x):
        """ Processes the input tensor x and returns an output tensor."""
        y = []
        for i, x_i in enumerate(torch.chunk(x, self.scale, dim=1)):
            if i == 0:
                y_i = x_i
            elif i == 1:
                y_i = self.blocks[i - 1](x_i)
            else:
                y_i = self.blocks[i - 1](x_i + y_i)
            y.append(y_i)
        y = torch.cat(y, dim=1)
        return y

class SEBlock(nn.Module):

    def __init__(self, in_channels, se_channels, out_channels):
        super(SEBlock, self).__init__()

        self.conv1 = Conv1d(
            in_channels=in_channels, out_channels=se_channels, kernel_size=1
        )
        self.relu = torch.nn.ReLU(inplace=True)
        self.conv2 = Conv1d(
            in_channels=se_channels, out_channels=out_channels, kernel_size=1
        )
        self.sigmoid = torch.nn.Sigmoid()

    def forward(self, x, lengths=None):
        """ Processes the input tensor x and returns an output tensor."""
        L = x.shape[-1]
        if lengths is not None:
            mask = length_to_mask(lengths * L, max_len=L, device=x.device)
            mask = mask.unsqueeze(1)
            total = mask.sum(dim=2, keepdim=True)
            s = (x * mask).sum(dim=2, keepdim=True) / total
        else:
            s = x.mean(dim=2, keepdim=True)

        s = self.relu(self.conv1(s))
        s = self.sigmoid(self.conv2(s))

        return s * x

class Linear(torch.nn.Module):

    def __init__(
        self,
        n_neurons,
        input_shape=None,
        input_size=None,
        bias=True,
        combine_dims=False,
    ):
        super().__init__()
        self.combine_dims = combine_dims

        if input_shape is None and input_size is None:
            raise ValueError("Expected one of input_shape or input_size")

        if input_size is None:
            input_size = input_shape[-1]
            if len(input_shape) == 4 and self.combine_dims:
                input_size = input_shape[2] * input_shape[3]

        # Weights are initialized following pytorch approach
        self.w = nn.Linear(input_size, n_neurons, bias=bias)

    def forward(self, x):
        if x.ndim == 4 and self.combine_dims:
            x = x.reshape(x.shape[0], x.shape[1], x.shape[2] * x.shape[3])

        wx = self.w(x)

        return wx

def get_padding_elem(L_in: int, stride: int, kernel_size: int, dilation: int):
    if stride > 1:
        padding = [math.floor(kernel_size / 2), math.floor(kernel_size / 2)]

    else:
        L_out = (
            math.floor((L_in - dilation * (kernel_size - 1) - 1) / stride) + 1
        )
        padding = [
            math.floor((L_in - L_out) / 2),
            math.floor((L_in - L_out) / 2),
        ]
    return padding


import os
import copy
import logging

from typing import Optional

from lxml import etree  # type: ignore
from scielo_v3_manager.pid_manager import Manager
from scielo_v3_manager.v3_gen import generates

from prodtools.utils import fs_utils
from prodtools.utils import xml_utils

LOGGER = logging.getLogger(__name__)

class PidManagerExceedsIntentTimesError(Exception):
    ...

def update_xml_file(file_path, pids_to_append_in_xml):
    if not pids_to_append_in_xml:
        # nada para atualizar
        return None
    if not file_path:
        LOGGER.debug("Could not find XML path")
        return None
    try:
        tree = xml_utils.get_xml_object(file_path)
    except xml_utils.etree.XMLSyntaxError:
        LOGGER.info("%s is not a valid XML", file_path)
    else:
        # atualizar XML com os `pids_to_append_in_xml`
        _tree = add_article_id_to_etree(tree, pids_to_append_in_xml)
        write_etree_to_file(_tree, file_path)

def add_article_id_to_received_documents(
    pid_manager_info: dict,
    issn_id: str,
    year_and_order: str,
    received_docs: dict,
    documents_in_isis: dict,
    file_paths: dict,
    update_article_with_aop_status: callable,
) -> None:

    exceptions = []
    registered_v3_items = {}
    total = len(received_docs)
    times = 0
    MAX_TRIES = total
    results = {}
    while True:
        try:
            pid_manager = Manager(
                pid_manager_info['name'], pid_manager_info['timeout'])
            _add_article_id_to_received_documents(
                pid_manager,
                issn_id,
                year_and_order,
                received_docs,
                documents_in_isis,
                file_paths,
                update_article_with_aop_status,
                registered_v3_items,
                results,
            )
        except Exception as e:
            exceptions.append(str(e))
        finally:
            done = len([v for v in registered_v3_items.values() if v])
            if done == total:
                break
            times += 1
            if times > MAX_TRIES or len(exceptions) > 0:
                raise PidManagerExceedsIntentTimesError(
                    "Pid Manager failed to set v3 to %i documents. "
                    "Tried %i times. "
                    "Done for %i documents. %s %s" %
                    (total, times, done, results, "\n".join(exceptions))
                )

def _add_article_id_to_received_documents(
    pid_manager: Manager,
    issn_id: str,
    year_and_order: str,
    received_docs: dict,
    documents_in_isis: dict,
    file_paths: dict,
    update_article_with_aop_status: callable,
    registered_v3_items: dict,
    results: dict,
) -> None:
    """Atualiza article-id (scielo-v2 e scielo-v3) dos documentos recebidos.

    Params:
        pid_manager (PIDVersionsManager): instância de PIDVersionsManager para gerir pid da versão 3
        issn_id (str): ISSN do periódico
        year_and_order (str): Ano e ordem da issue processada
        received_docs (dict): Pacote de documentos recebidos para processar
        documents_in_isis (dict): Documentos já registrados na base isis (acron/volnum)
        file_paths (dict): arquivos do received_docs
        update_article_with_aop_status (callable): Função que recupera o AOP PID e modifica o
            artigo com este dado

    Returns:
        None
    """
    for xml_name, article in received_docs.items():
        if registered_v3_items.get(xml_name):
            continue
        file_path = file_paths.get(xml_name)
        if not file_path:
            LOGGER.debug("Could not find XML path for '%s' xml.", xml_name)

        pids_to_append_in_xml = []

        # Obtém v2 do XML
        pid_v2 = article.get_scielo_pid("v2")
        if pid_v2 is None:
            # se v2 não está presente no XML, gerar a partir dos metadados
            pid_v2 = get_scielo_pid_v2(issn_id, year_and_order, article.order)
            # anotar para ser inserido no XML
            pids_to_append_in_xml.append((pid_v2, "scielo-v2"))

        # Obtém v3 do XML
        pid_v3 = article.get_scielo_pid("v3")

        # Obtém previous do XML
        prev_pid = article.previous_article_pid
        if not prev_pid:
            # Não há previous do XML
            # Obtém previous_pid registrado na base ahead do artigo
            if update_article_with_aop_status:
                update_article_with_aop_status(article)
            # acessa previous_pid com `article.registered_aop_pid`
            prev_pid = article.registered_aop_pid
            if prev_pid:
                pids_to_append_in_xml.append((prev_pid, "previous-pid"))

        # consulta / registra / atualiza os dados na base pid_manager
        result = pid_manager.manage(
            v2=pid_v2, v3=pid_v3, aop=prev_pid,
            filename=os.path.basename(file_path),
            doi=article.doi,
            status="",
            generate_v3=generates)
        results[xml_name] = result

        record = result.get("saved") or result.get("registered") or {}
        v3 = record.get("v3")
        if v3:
            registered_v3_items[xml_name] = v3

        if pid_v3 is None:
            # se v3 não está no presente no XML
            article.registered_scielo_id = v3
            # anotar para ser inserido no XML
            pids_to_append_in_xml.append((v3, "scielo-v3"))

        # atualiza aop pid, se aplicável
        if record and not prev_pid:
            recovered_aop_pid = record.get("aop")
            if not recovered_aop_pid:
                if record.get("v2") and pid_v2 and pid_v2 != record.get("v2"):
                    recovered_aop_pid = record.get("v2")
            if recovered_aop_pid:
                pids_to_append_in_xml.append(
                    (recovered_aop_pid, "previous-pid"))

        # atualizar o XML com pids_to_append_in_xml
        update_xml_file(file_path, pids_to_append_in_xml)

def get_scielo_pid_v2(issn_id, year_and_order, order_in_issue):
    year = year_and_order[:4]
    order_in_year = year_and_order[4:].zfill(4)
    return "".join(("S", issn_id, year, order_in_year, order_in_issue))

def add_article_id_to_etree(
    tree: etree.ElementTree, pid_and_specific_use_items: list
) -> Optional[etree.ElementTree]:
    """Adiciona os pids v2 e v3 a árvore lxml de um documento"""
    if (
        tree is None
        or pid_and_specific_use_items is None
        or len(pid_and_specific_use_items) == 0
    ):
        return None

    _tree = copy.deepcopy(tree)

    article_meta = _tree.find(".//article-meta")

    if article_meta is None:
        LOGGER.debug(
            "Could not insert articles ids because the article-meta isn't found"
        )
        return None

    for id_value, specific_use in pid_and_specific_use_items:
        article_id = etree.Element("article-id")
        article_id.text = id_value
        article_id.set("specific-use", specific_use)
        article_id.set("pub-id-type", "publisher-id")
        article_meta.insert(0, article_id)

    return _tree

def write_etree_to_file(tree: etree.ElementTree, path: str) -> None:
    """Escreve uma árvore lxml em um arquivo de destino. Também
    garante que as entidades não serão modificadas por meio da função
    xml_utils.tostring(etree)."""

    if tree is None or path is None:
        return None

    fs_utils.write_file(path, xml_utils.tostring(tree))


from flask import Flask, jsonify, request
from flask_socketio import SocketIO, emit
from pymongo import MongoClient
from bson.objectid import ObjectId
import subprocess
import sys
import os

# def run_cli():
#     cli_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cli.py')
#     subprocess.Popen([sys.executable, cli_script])

app = Flask(__name__)
socketio = SocketIO(app)

# Connect to MongoDB
client = MongoClient('mongodb://localhost:27017/')
db = client['my_database']
collection = db['users']

@app.route('/')
def index():
    return "Welcome to the Firebase!"

@app.route('/users', methods=['GET', 'POST'])
def users():
    if request.method == 'GET':
        cursor = collection.find()
        users = [user for user in cursor]
        return jsonify(users)
    elif request.method == 'POST':
        user_data = request.json
        result = collection.insert_one(user_data)
        socketio.emit('data_updated', {'event': 'created', 'data': user_data})
        return jsonify({"success": True, "message": "User created", "_id": str(result.inserted_id)})

@app.route('/users/<int:user_id>', methods=['PUT', 'PATCH', 'DELETE'])
def user(user_id):
    print(f"Request received: method={request.method}, user_id={user_id}")
    #oid = ObjectId(user_id)
    oid = user_id
    if request.method == 'PUT':
        user_data = request.json
        result = collection.replace_one({'_id': oid}, user_data)
        if result.matched_count == 0:
            socketio.emit('data_updated', {'event': 'created', 'data': user_data},)
            message = "User created"
        else:
            socketio.emit('data_updated', {'event': 'updated', 'data': user_data})
            message = "User updated"
        return jsonify({"success": True, "message": message})
    elif request.method == 'PATCH':
        update_data = request.json
        result = collection.update_one({'_id': oid}, {'$set': update_data})
        print(f"Query filter: {{'_id': {user_id}}}")  # Debug print
        print(f"Result: {result.raw_result}")  # Debug print

        if result.modified_count == 1:
            #socketio.emit('data_updated', {'event': 'updated', 'data': update_data}, broadcast=True)
            socketio.emit('data_updated', {'event': 'updated', 'data': update_data})
            return jsonify({"success": True, "message": "User updated"})
        else:
            return jsonify({"success": False, "message": "User not found"})
    elif request.method == 'DELETE':
        result = collection.delete_one({'_id': oid})
        if result.deleted_count == 1:
            #socketio.emit('data_updated', {'event': 'deleted', 'user_id': user_id}, broadcast=True)
            socketio.emit('data_updated', {'event': 'deleted', 'user_id': user_id})
            return jsonify({"success": True, "message": "User deleted"})
        else:
            return jsonify({"success": False, "message": "User not found"})

if __name__ == '__main__':
    # run_cli()
    socketio.run(app, debug=True)


'''
@ Youssef Dahab
@ 2016 / 06 / 16
'''
"""
 Pygame base template for opening a window

 Sample Python/Pygame Programs
 Simpson College Computer Science
 http://programarcadegames.com/
 http://simpson.edu/computer-science/

 Explanation video: http://youtu.be/vRB_983kUMc

Font/Text:

http://programarcadegames.com/python_examples/f.php?file=move_game_controller.py
"""

import pygame
import random
import time

# Define beginning colours and values.
PI = 3.141592654
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 140, 0)

''' Subprogram for point system.'''
def point_System(player_1_score, player_2_score):
     player_1_score = player_1_score
     player_2_score = player_2_score

     # Check if player 2 scored a goal.
     if(ball.rect.x <= 40 and ball.rect.y >= 310 and ball.rect.y <= 490):
          player_2_score = player_2_score + 1

     # Check if player 1 scored a goal.
     elif (ball.rect.x >= 1240 and ball.rect.y >= 310 and ball.rect.y <= 490):
          player_1_score = player_1_score + 1

     return (player_1_score, player_2_score)

''' Subprogram to draw the soccer field.'''
def draw_soccer_field():

     # Draw the soccer field.
     pygame.draw.rect(screen, GREEN, [0, 0, 1300, 800], 0)
     pygame.draw.rect(screen, WHITE, [50, 20, 1200, 760], 5)
     pygame.draw.line(screen, WHITE, [650, 20], [650, 780], 5)
     pygame.draw.circle(screen, WHITE, [650, 400], 100, 5)

     # Draw the nets.
     pygame.draw.rect(screen, RED, [0, 300, 50, 200], 5)
     pygame.draw.rect(screen, BLUE, [1250, 300, 50, 200], 5)

# Create class for soccer players.
class Player(pygame.sprite.Sprite):
     def __init__(self, colour, collision_list):

          # Call the parent class (Sprite) constructor.
          pygame.sprite.Sprite.__init__(self)

          # Attributes for Player class.
          self.collide_list = collision_list
          self.radius = 0
          self.change_of_x = 0
          self.change_of_y = 0
          self.colour = colour
          self.width = 40
          self.height = 40
          self.image = pygame.Surface([self.width,self.height])
          self.image.fill(WHITE)
          self.image.set_colorkey(WHITE)

          # Draw the players.
          pygame.draw.ellipse(self.image, self.colour, [0, 0, self.width, self.height])
          self.rect = self.image.get_rect()
          self.rect.x = 0
          self.rect.y = 0

     # Method to move the player.
     def move(self):
          self.rect.x += self.change_of_x
          self.rect.y += self.change_of_y

          # Setting boundries for the players.
          if(self.rect.x <=0 or self.rect.x >=1260):
               self.change_of_x = 0

          elif(self.rect.y <=0 or self.rect.y >=760):
               self.change_of_y = 0

     # Method to check for colliding.
     def check_colliding(self):
          collide_list = pygame.sprite.spritecollide(self, self.collide_list, False)

          # If player collides with other player, they bounce off each other.
          if collide_list:
               self.change_of_x = self.change_of_x * -1
               self.change_of_y = self.change_of_y * -1

     # Method to update the players.
     def update(self):
          self.check_colliding()
          self.move()

# Inheritance of Player class.
class Soccer_ball(Player):
     def __init__(self, collision_list):

          # Call the parent class (Sprite) constructor.
          Player.__init__(self, BLACK, collision_list)

          # Attributes for Soccer ball class.
          self.ball_x = 640
          self.ball_y = 400
          self.ball_change_x = 15
          self.ball_change_y = 5
          self.collide_list = collision_list
          self.width = 20
          self.height = 20
          self.image = pygame.Surface([self.width,self.height])
          self.image.fill(WHITE)
          self.image.set_colorkey(WHITE)

          # Draw the soccer ball.
          pygame.draw.ellipse(self.image, self.colour, [0, 0, self.width, self.height])
          self.rect = self.image.get_rect()
          self.rect.x = 630
          self.rect.y = 380

     # Method to move the ball
     def move(self):
          self.rect.x += self.ball_change_x
          self.rect.y += self.ball_change_y

          # Boundries for soccer ball.
          if(self.rect.x <=0 and self.rect.y >= 490):
               self.ball_change_x = self.ball_change_x * -1

          elif(self.rect.x <= 0 and self.rect.y <= 310):
               self.ball_change_x = self.ball_change_x * -1

          elif(self.rect.x >=1280 and self.rect.y >= 490):
               self.ball_change_x = self.ball_change_x * -1

          elif(self.rect.x >=1280 and self.rect.y <= 310):
               self.ball_change_x = self.ball_change_x * -1

          elif(self.rect.x >=0 and self.rect.y <=0):
               self.ball_change_y = self.ball_change_y * -1

          elif(self.rect.x >=0 and self.rect.y >= 780):
               self.ball_change_y = self.ball_change_y * -1

     # Method to check for collision with soccer ball.
     def check_colliding(self):
          collide_list = pygame.sprite.spritecollide(self, self.collide_list, False)

          # If ball collides with a player, it bounces off the player.
          if collide_list:
               self.ball_change_x = self.ball_change_x * -1
               self.ball_change_y = random.randrange(-3, 3)

     # Method to update the soccer ball.
     def update(self):
          self.check_colliding()
          self.move()

# Creating lists for collisions.
ball_collision_list = pygame.sprite.Group()
player1_collision_list = pygame.sprite.Group()
player2_collision_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()

# Creating players.
player_1 = Player(RED, player1_collision_list)
player_2 = Player(BLUE, player2_collision_list)

# Creating stats for Player 1.
player_1.rect.x = 50
player_1.rect.y = 400
player_1.radius = 20
player_1.change_of_x = 0
player_1.change_of_y = 0

# Creating stats for Player 2.
player_2.rect.x = 1200
player_2.rect.y = 400
player_2.radius = 20
player_2.change_of_x = 0
player_2.change_of_y = 0

# Creating soccer ball.
ball = Soccer_ball(ball_collision_list)

# Creating lists to check for collisions.
all_sprites_list.add(player_1)
all_sprites_list.add(player_2)
all_sprites_list.add(ball)
player1_collision_list.add(player_2)
player2_collision_list.add(player_1)
ball_collision_list.add(player_1)
ball_collision_list.add(player_2)

# Initializing pygame.
pygame.init()

# Set the width and height of the screen.
size = (1300, 800)
screen = pygame.display.set_mode(size)

pygame.display.set_caption("SOCCER BATTLE!")

# Select the font to use, size, bold, italics.
font = pygame.font.SysFont('Calibri', 50, True, False)

# Initial score for point system.
player_1_score = 0
player_2_score = 0

# Create an empty list for sparks.
spark_list = []
spark_color = [YELLOW, RED, ORANGE, YELLOW, ORANGE]

# Loop to create random sparks and add them to a list.
for i in range(100):
     x = random.randrange(0, 1300)
     y = random.randrange(0, 800)
     spark_list.append([x, y])

# Background image.
screen.fill(WHITE)

# Used to manage how fast the screen updates.
clock = pygame.time.Clock()

# Loops until user(s) click close button.
done = False

# Help with displaying instructions.
instructions_read = False

# Main program loop.
while not done:

     # Main event loop.
     for event in pygame.event.get():
          if event.type == pygame.QUIT:
               done = True

          # Player 1 and player 2 pressing down keys.
          elif event.type == pygame.KEYDOWN:

               # Player 2 pressing down arrow keys.
               if event.key == pygame.K_UP:
                    player_2.change_of_y = -3

               if event.key == pygame.K_DOWN:
                    player_2.change_of_y = 3

               if event.key == pygame.K_RIGHT:
                    player_2.change_of_x = 3

               if event.key == pygame.K_LEFT:
                    player_2.change_of_x = -3

               # Player 1 pressing down WASD keys.
               if event.key == pygame.K_w:
                    player_1.change_of_y = -3

               if event.key == pygame.K_s:
                    player_1.change_of_y = 3

               if event.key == pygame.K_d:
                    player_1.change_of_x = 3

               if event.key == pygame.K_a:
                    player_1.change_of_x = -3

          # Player 1 and player 2 lets go of keys.
          elif event.type == pygame.KEYUP:

               # Player 2 lets go of arrow keys.
               if event.key == pygame.K_UP:
                    player_2.change_of_y = 0

               if event.key == pygame.K_DOWN:
                    player_2.change_of_y = 0

               if event.key == pygame.K_RIGHT:
                    player_2.change_of_x = 0

               if event.key == pygame.K_LEFT:
                    player_2.change_of_x = 0

            # Player 1 lets go of WASD keys.
               if event.key == pygame.K_w:
                    player_1.change_of_y = 0

               if event.key == pygame.K_s:
                    player_1.change_of_y = 0

               if event.key == pygame.K_d:
                    player_1.change_of_x = 0

               if event.key == pygame.K_a:
                    player_1.change_of_x = 0

     # Generate the soccer field.
     draw_soccer_field()

     # Displaying initial score of soccer game.
     text = font.render(str(player_1_score) + "-" + str(player_2_score), True, BLACK)
     screen.blit(text, [620, 25])

     # Check if player 1 or player 2 scored a goal.
     if(ball.rect.x <= 40 and ball.rect.y >= 310 and ball.rect.y <= 490 or ball.rect.x >= 1240 and ball.rect.y >= 310 and ball.rect.y <= 490):
          (player_1_score, player_2_score) = point_System(player_1_score, player_2_score)
          text = font.render(str(player_1_score) + "-" + str(player_2_score), True, BLACK)

          # Display score on screen.
          screen.blit(text, [620, 25])

          # Reset soccer ball and players.
          ball.rect.x = 650
          ball.rect.y = 400
          player_1.rect.x = 50
          player_1.rect.y = 400
          player_2.rect.x = 1200
          player_2.rect.y = 400
          ball.change_x = 0
          ball.change_y = 0
          all_sprites_list.update()
          all_sprites_list.draw(screen)

          # Continue soccer game.
          time.sleep(1)
          ball.change_x = random.randint(-15, 15)
          ball.change_y = random.randint(-3, 3)

     elif(player_1_score >= 3 or player_2_score >= 3):

          # Reset soccer ball and players to end game.
          ball.rect.x = 650
          ball.rect.y = 400
          player_1.rect.x = 50
          player_1.rect.y = 400
          player_2.rect.x = 1200
          player_2.rect.y = 400
          ball.change_x = 0
          ball.change_y = 0

     # Check if player 1 won the game.
     if(player_1_score >= 3):
          text = font.render("Congrats Player 1, YOU WIN!!!", True, RED)
          screen.blit(text, [400, 400])

          # Process each spark in the list
          for i in range(len(spark_list)):

               # Draw the spark
               pygame.draw.arc(screen,spark_color[random.randint(0,len(spark_color)-1)],[spark_list[i][0],spark_list[i][1], 50, 50], PI/2, PI, 5)
               pygame.draw.arc(screen,spark_color[random.randint(0,len(spark_color)-1)],[spark_list[i][0],spark_list[i][1], 50, 50], 3*PI/2, 2*PI, 5)

               # Move the sparks down 1 pixel.
               spark_list[i][1] += 1

     # Check if player 2 won the game.
     elif(player_2_score >= 3):
          text = font.render("Congrats Player 2, YOU WIN!!!", True, BLUE)
          screen.blit(text, [400, 400])

           # Process each spark in the list
          for i in range(len(spark_list)):

               # Draw the spark
               pygame.draw.arc(screen,spark_color[random.randint(0,len(spark_color)-1)],[spark_list[i][0],spark_list[i][1], 50, 50], PI/2, PI, 5)
               pygame.draw.arc(screen,spark_color[random.randint(0,len(spark_color)-1)],[spark_list[i][0],spark_list[i][1], 50, 50], 3*PI/2, 2*PI, 5)

               # Move the sparks down 1 pixel.
               spark_list[i][1] += 1

     # Displays instructions long enough for players to read them.
     while not instructions_read:
          text = font.render("First to 3 is the winner.", True, BLACK)
          screen.blit(text, [500, 400])
          pygame.display.flip()
          time.sleep(3)
          instructions_read = True

     # Generating players in game.
     all_sprites_list.update()
     all_sprites_list.draw(screen)

     # Update the screen with what we've drawn.
     pygame.display.flip()

     # Limit to 60 frames per second.
     clock.tick(60)

# Close the window and quit.
pygame.quit()


import asyncio
import time

def main():
    start_time = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(
        request(),
        another_request()
    ))
    print(f"Execution time {time.time() - start_time}")

def main1():
    start_time = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(run_tasks())

    print(f"Execution time {time.time() - start_time}")

async def run_tasks():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(request())
        task2 = tg.create_task(another_request())
    print(task1.result(), task2.result())

def main2():
    start_time = time.time()
    loop = asyncio.get_event_loop()
    tasks = loop.run_until_complete(task())
    print(tasks)
    print(f"Execution time {time.time() - start_time}")

async def task():
    await request()  # place order
    await another_request()  # add bonuses
    return 'something'

async def request():
    await asyncio.sleep(1)
    print("Request")
    return "request data"

async def another_request():
    await asyncio.sleep(1)
    print("Another request")
    return "another request data"

if __name__ == "__main__":
    main()
    main1()
    main2()


#!/usr/bin/env python3

from numpy import empty
import rosbag
import rospy
import datetime

from file_info import FileInfo

class RosBagHandler:
  position_topics = {'/gps':'DriX',
    '/project11/mesobot/nav/position':'Mesobot',
    '/project11/nui/nav/position':'nui',
    '/project11/nautilus/position':'Nautilus',
    '/mothership_gps':'Mothership'
  }

  def __init__(self):
    pass

  def needsProcessing(self, file: FileInfo):
    if file.local_path.suffix == '.bag':
      if file.has_meta_value(self, 'start_time') and not file.is_modified():
        return False
      if file.meta is not None and 'RosBagIndexHandler' in file.meta:
        if 'indexed' in file.meta['RosBagIndexHandler'] and not file.meta['RosBagIndexHandler']['indexed']:
          return False
      if file.has_meta_value(self, 'message_count') and file.get_meta_value(self, 'message_count') == 0:
        return False
      return True
    return False

  def process(self, file: FileInfo) -> FileInfo:
    try:
      bag = rosbag.Bag(file.source_path())
    except Exception as e:
      print("error opening bag file",file.local_path,e)
      print(type(e))
      return
    try:
      file.update_meta_value(self, 'message_count', bag.get_message_count())
      if bag.get_message_count() == 0:
        return
      file.update_meta_value(self, 'start_time', bag.get_start_time())
      file.update_meta_value(self, 'end_time', bag.get_end_time())
      tt = bag.get_type_and_topic_info()
      topics = []
      for t in RosBagHandler.position_topics:
        if t in tt.topics:
          topics.append(t)
      if len(topics) == 0:
        return
    except Exception as e:
      print("error getting times from bag file",file.local_path,e)
      print(type(e))
      return

    tracks = {}
    last_report_times = {}
    interval = rospy.Duration(secs=1.0)
    try:
      for topic, msg, t in bag.read_messages(topics=topics):
        vehicle = RosBagHandler.position_topics[topic]
        if not vehicle in tracks:
          tracks[vehicle] = []
          last_report_times[vehicle] = None
        if topic in ('/gps','/mothership_gps'):
          if msg.fix_quality > 0:
            if last_report_times[vehicle] is None or msg.header.stamp - last_report_times[vehicle] >= interval:
              fix = {'timestamp': msg.header.stamp.to_sec()}
              fix['latitude'] = msg.latitude
              fix['longitude'] = msg.longitude
              fix['altitude'] = 0.0
              tracks[vehicle].append(fix)
              last_report_times[vehicle] = msg.header.stamp
        elif topic in ('/project11/mesobot/nav/position','/project11/nui/nav/position'):
          if last_report_times[vehicle] is None or msg.header.stamp - last_report_times[vehicle] >= interval:
              fix = {'timestamp': msg.header.stamp.to_sec()}
              fix['latitude'] = msg.pose.position.latitude
              fix['longitude'] = msg.pose.position.longitude
              fix['altitude'] = msg.pose.position.altitude
              tracks[vehicle].append(fix)
              last_report_times[vehicle] = msg.header.stamp
        # elif topic in ('/project11/nui/nav/position',):
        #   if last_report_times[vehicle] is None or msg.header.stamp - last_report_times[vehicle] >= interval:
        #       fix = {'timestamp': msg.header.stamp.to_sec()}
        #       fix['latitude'] = msg.position.latitude
        #       fix['longitude'] = msg.position.longitude
        #       fix['altitude'] = msg.position.altitude
        #       tracks[vehicle].append(fix)
        #       last_report_times[vehicle] = msg.header.stamp
        else:
          if msg.status.status >= 0:
            if last_report_times[vehicle] is None or msg.header.stamp - last_report_times[vehicle] >= interval:
              fix = {'timestamp': msg.header.stamp.to_sec()}
              fix['latitude'] = msg.latitude
              fix['longitude'] = msg.longitude
              fix['altitude'] = msg.altitude
              tracks[vehicle].append(fix)
              last_report_times[vehicle] = msg.header.stamp

    except Exception as e:
      print("error extracting nav from bag file",file.local_path, e)

    bounds = {}
    tracks_for_meta = {}
    for v in tracks:
      if len(tracks[v]):
        min_lat = max_lat = tracks[v][0]['latitude']
        min_lon = max_lon = tracks[v][0]['longitude']
        for fix in tracks[v]:
          min_lat = min(min_lat, fix['latitude'])
          max_lat = max(max_lat, fix['latitude'])
          min_lon = min(min_lon, fix['longitude'])
          max_lon = max(max_lon, fix['longitude'])
        bounds[v] = {'min': {'latitude': min_lat, 'longitude': min_lon}, 'max': {'latitude': max_lat, 'longitude': max_lon}}
        tracks_for_meta[v] = tracks[v]

    if len(bounds):
      file.update_meta_value(self, 'bounds', bounds)
      file.update_meta_value(self, 'tracks', tracks_for_meta)


# -*- coding: utf-8 -*-

# Looks for musical samples in arbitrary audio
# Adapted from: https://github.com/kylemcdonald/AudioNotebooks/blob/master/Multisamples%20to%20Samples.ipynb
# Usage:
    # python audio_to_samples.py -plot 1
    # python audio_to_samples.py -save 1
    # python audio_to_samples.py -in "../audio/downloads/birds/*.mp3" -saved 1
    # python audio_to_samples.py -in "../data/usergen/saved_birds.json" -plot 1

import argparse
import csv
import glob
import json
import librosa
from multiprocessing import Pool
from multiprocessing.dummy import Pool as ThreadPool
import os
from os.path import join
import numpy as np
from pprint import pprint
import sys
from utils import getAudioSamples, readFiles

# input
parser = argparse.ArgumentParser()
parser.add_argument('-in', dest="INPUT_FILES", default="../audio/sample/*.mp3", help="Input file pattern")
parser.add_argument('-adir', dest="INPUT_AUDIO_DIR", default="../audio/downloads/birds/%s.mp3", help="Input audio directory (if INPUT_FILES is .json file)")
parser.add_argument('-samples', dest="SAMPLES", default=8, type=int, help="Max samples to produce, -1 for all")
parser.add_argument('-min', dest="MIN_DUR", default=0.05, type=float, help="Minimum sample duration in seconds")
parser.add_argument('-max', dest="MAX_DUR", default=1.00, type=float, help="Maximum sample duration in seconds")
parser.add_argument('-amp', dest="AMP_THESHOLD", default=-1, type=float, help="Amplitude theshold, -1 for default")
parser.add_argument('-save', dest="SAVE", default=0, type=int, help="Save files?")
parser.add_argument('-plot', dest="PLOT", default=0, type=int, help="Show plot?")
parser.add_argument('-saved', dest="SAVE_DATA", default=0, type=int, help="Save data files?")
parser.add_argument('-plotdir', dest="PLOT_DIR", default="../data/output/plot/%s.png", help="Output dir for plot images")
parser.add_argument('-dir', dest="SAMPLE_DIR", default="../audio/output/birds", help="Output dir")
parser.add_argument('-out', dest="OUTPUT_FILE", default="../data/output/birds_audio_samples.csv", help="CSV output file")
parser.add_argument('-overwrite', dest="OVERWRITE", default=0, type=int, help="Overwrite existing audio/data?")
args = parser.parse_args()

# Parse arguments
INPUT_FILES = args.INPUT_FILES
INPUT_AUDIO_DIR = args.INPUT_AUDIO_DIR
SAMPLES = args.SAMPLES
MIN_DUR = args.MIN_DUR
MAX_DUR = args.MAX_DUR
AMP_THESHOLD = args.AMP_THESHOLD
SAVE = args.SAVE > 0
PLOT = args.PLOT > 0
SAVE_DATA = args.SAVE_DATA > 0
PLOT_DIR = args.PLOT_DIR
SAMPLE_DIR = args.SAMPLE_DIR
OUTPUT_FILE = args.OUTPUT_FILE
OVERWRITE = args.OVERWRITE > 0

if SAMPLES <= 0:
    SAMPLES = None

# Audio config
FFT = 2048
HOP_LEN = FFT/4

# Read files
fileGroups, files = readFiles(INPUT_FILES, INPUT_AUDIO_DIR)
fileCount = len(files)
hasGroups = len(fileGroups) > 1
print("Found %s files" % fileCount)

# Make sure output dirs exist
outDirs = [SAMPLE_DIR, os.path.dirname(OUTPUT_FILE)]
if PLOT:
    outDirs.append(os.path.dirname(PLOT_DIR))
for outDir in outDirs:
    if not os.path.exists(outDir):
        os.makedirs(outDir)

progress = 0
# files = files[:1]

def makeSamples(fn):
    global progress
    global hasGroups

    sampleData = []
    basename = os.path.splitext(os.path.basename(fn))[0]
    path, groupName = os.path.split(os.path.dirname(fn))
    groupName = "" if not hasGroups else groupName
    plotfilename = PLOT_DIR % basename

    if SAVE_DATA or SAVE or PLOT and (OVERWRITE or not os.path.isfile(plotfilename)):

        sampleData, ysamples, y, sr = getAudioSamples(fn, min_dur=MIN_DUR, max_dur=MAX_DUR, fft=FFT, hop_length=HOP_LEN, amp_threshold=AMP_THESHOLD, plot=PLOT, plotfilename=plotfilename, groupName=groupName)

        # if too many samples, take the ones with the most power
        if SAMPLES is not None and len(sampleData) > SAMPLES and (SAVE or SAVE_DATA):
            sampleData = sorted(sampleData, key=lambda k: k['power'], reverse=True)
            sampleData = sampleData[:SAMPLES]

        if SAVE:
            for d in sampleData:
                ysample = ysamples[d["index"]]
                outFn = join(SAMPLE_DIR, d["filename"])
                if OVERWRITE or not os.path.isfile(outFn):
                    sample = np.copy(ysample)
                    sample /= np.abs(sample).max()
                    librosa.output.write_wav(outFn, sample, sr)

        # sort chronologically
        if SAVE or SAVE_DATA:
            sampleData = sorted(sampleData, key=lambda k: k['start'])

    progress += 1
    sys.stdout.write('\r')
    sys.stdout.write("%s%%" % round(1.0*progress/fileCount*100,1))
    sys.stdout.flush()

    return sampleData

if PLOT:
    for fn in files:
        makeSamples(fn)

else:
    pool = ThreadPool()
    data = pool.map(makeSamples, files)
    pool.close()
    pool.join()

if SAVE_DATA:
    print("Writing data to file...")
    headings = ["parent", "group", "start", "dur", "power", "hz", "note", "octave"]
    rowCount = 0
    with open(OUTPUT_FILE, 'wb') as f:
        writer = csv.writer(f)
        writer.writerow(headings)
        for sdata in data:
            for entry in sdata:
                writer.writerow([entry[key] for key in headings])
                rowCount += 1
    print("Wrote %s rows to %s" % (rowCount, OUTPUT_FILE))


import pygame as pg
from PyQt5.QtWidgets import QApplication, QDialog, QListWidget, QVBoxLayout
from pathlib import Path
from threading import Thread
import sys, os

from header import *

class SearchBar:
    def __init__(self, x, y, text):
        # color, font, text, and various variables for this searchbar
        self.color = pg.Color(*COLOR_PAL[WHITE])
        self.font = pg.font.Font(CHALK_FONT, CHALK_FONTSIZE)
        self.default_text = text
        self.text = text
        self.active = False
        self.results = []
        # a text surface and a rectangle hitbox makes up a search bar
        self.text_surface = self.font.render(text, True, self.color)
        self.rect = pg.Rect(x, y, self.text_surface.get_width() + 10, CHALK_FONTSIZE + 5)

    def handle_event(self, event):
        interacted = False
        if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
            # click on search bar to activate it
            if self.rect.collidepoint(event.pos):
                interacted = True
                self.active = not self.active
            else:
                self.active = False
            # re-render the text for the search bar
            self.text = '' if self.active else self.default_text
            self.text_surface = self.font.render(self.text, True, self.color)
        # keyboard input when active
        elif self.active and event.type == pg.KEYDOWN:
            interacted = True
            # {ENTER} searches
            if event.key == pg.K_RETURN:
                Thread(target=self._search, args=(self.text,), daemon=True).start()
            # {BACKSPACE} removes 1 char, , or 1 word if {CTRL} is held down
            elif event.key == pg.K_BACKSPACE:
                keystates = pg.key.get_pressed()
                if keystates[pg.K_LCTRL]:
                    i = self.text.rstrip().rfind(" ")
                    self.text = self.text[:i+1] if i >= 0 else ''
                else:
                    self.text = self.text[:-1]
            # {ESC} clears the search bar
            elif event.key == pg.K_ESCAPE:
                if self.text == '':
                    self.active = False
                    self.text = self.default_text
                else:
                    self.text = ''
            # typing char
            else:
                self.text += event.unicode
            # render new text
            self.text_surface = self.font.render(self.text, True, self.color)
        return interacted

    def _search(self, file):
        # internal method for searching: uses SearchGUI to display search results
        q = QApplication(sys.argv)
        window = SearchGUI(self.results)
        Thread(target=window.searchItems, args=(file,), daemon=True).start()
        window.show()
        q.exec_()

    def get_search_results(self):
        # extract search results out
        if len(self.results) == 0:
            return self.results
        else:
            temp = self.results
            self.results = []
            return temp

    def move(self, x, y):
        self.rect = pg.Rect(x, y, self.text_surface.get_width() + 10, CHALK_FONTSIZE + 5)

    def update(self):
        # update the rectangle hitbox wrt the current text size
        self.rect.w = self.text_surface.get_width() + 10

    def draw(self, screen):
        # draw the text (surface), rectangle hitbox is not drawn
        screen.blit(self.text_surface, (self.rect.x+5, self.rect.y+5))

class SearchGUI(QDialog):
    def __init__(self, results):
        # QDialog widget
        super(SearchGUI, self).__init__()
        self.setStyleSheet("background-color: rgb(30, 30, 30);")
        self.results = results
        # contains a list widget to display the search results
        self.list_widget = QListWidget()
        self.list_widget.setStyleSheet("color: rgb(211, 211, 211);")
        self.list_widget.setFixedHeight(600)
        self.list_widget.setMinimumWidth(400)
        self.list_widget.itemDoubleClicked.connect(self.addAndReturn)
        # list widget sits on top of layout
        layout = QVBoxLayout(self)
        layout.addWidget(self.list_widget)

    def searchItems(self, file):
        for path in Path('/').rglob(file):
            self.list_widget.addItem(str(path))

    def addAndReturn(self, item):
        # quits GUI after picking
        self.results.append(item.text())
        QApplication.quit()

class OptionsMenu():
    def __init__(self, parent):
        # reference to the parent item
        self.p = parent
        self.options = []
        self.opt_rects = []
        self.rect_colors = []

    def setpos(self, x, y):
        h = OPTMENU_FONTSIZE + 10
        for i in range(len(self.options)):
            self.opt_rects[i].x, self.opt_rects[i].y = x, y + i*h

    def handle_event(self, event):
        # pressing any key closes the option menu
        if event.type == pg.KEYDOWN:
            self.p.options_opened = False
        # hovering over an option changes its color
        elif event.type == pg.MOUSEMOTION:
            for i in range(len(self.opt_rects)):
                if self.opt_rects[i].collidepoint(event.pos):
                    self.rect_colors[i] = OPTMENU_COLOR_HOVERED
                else:
                    self.rect_colors[i] = OPTMENU_COLOR

    def draw(self, screen):
        # draw the background of the options menu
        for i in range(len(self.opt_rects)):
            pg.Surface.fill(screen, self.rect_colors[i], self.opt_rects[i])
        # draw in the indivdual options
        for i in range(len(self.options)):
            screen.blit(self.options[i], (self.opt_rects[i].x + 2, self.opt_rects[i].y + 2))

class AppOptionsMenu(OptionsMenu):
    def __init__(self, app):
        super().__init__(app)
        # 4 options: launch, remove, rename, color
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Launch", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Remove", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Rename", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, pg.Color(*COLOR_PAL[self.p.color_idx])))
        # rectangles for the options
        w = self.options[3].get_width() + 10
        h = OPTMENU_FONTSIZE + 10
        for i in range(len(self.options)):
            self.opt_rects.append(pg.Rect(0, 0, w, h))
        # color of option rectangles (changes when hovered)
        self.rect_colors = [OPTMENU_COLOR] * len(self.opt_rects)

    def setpos(self, x, y):
        super().setpos(x, y)

    def handle_event(self, event):
        if event.type == pg.MOUSEBUTTONUP and event.button == 1:
            # click on launch option
            if self.opt_rects[0].collidepoint(event.pos):
                os.startfile(self.p.path)
                self.p.options_opened = False
            # click on remove option
            if self.opt_rects[1].collidepoint(event.pos):
                self.p.keep = False
                self.p.options_opened = False
            # click on rename option
            elif self.opt_rects[2].collidepoint(event.pos):
                self.p.text_color = pg.Color(*EDITING_COLOR)
                self.p.renaming = True
                self.p.text_surface = self.p.font.render(self.p.name, True, self.p.text_color)
                self.p.options_opened = False
            # click on color >>
            elif self.opt_rects[3].collidepoint(event.pos):
                self.p.color_idx = (self.p.color_idx + 1) % NUM_COLORS
                self.p.text_color = pg.Color(*COLOR_PAL[self.p.color_idx])
                self.options[3] = pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, self.p.text_color)
                self.p.text_surface = self.p.font.render(self.p.name, True, self.p.text_color)
            # clicking anywhere else closes this options menu
            else:
                self.p.options_opened = False
        super().handle_event(event)

    def draw(self, screen):
        super().draw(screen)

class ChalkTextOptionsMenu(OptionsMenu):
    def __init__(self, chalkText):
        super().__init__(chalkText)
        # 4 options: erase, edit, color, size
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Erase", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Edit", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, pg.Color(*COLOR_PAL[self.p.color_idx])))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("- Size +", True, OPTMENU_TEXT_COLOR))
        # rectangles for the options
        w = self.options[2].get_width() + 10
        h = OPTMENU_FONTSIZE + 10
        for i in range(len(self.options)):
            self.opt_rects.append(pg.Rect(0, 0, w, h))
        # special setup for size option
        self.opt_rects[-1].width = w//2
        self.opt_rects.append(pg.Rect(0, 0, w//2, h))
        self.w = w
        # color of option rectangles (changes when hovered)
        self.rect_colors = [OPTMENU_COLOR] * len(self.opt_rects)

    def setpos(self, x, y):
        super().setpos(x, y)
        # special setup for size option
        x, y = self.opt_rects[-2].x + self.w//2, self.opt_rects[-2].y
        self.opt_rects[-1].x, self.opt_rects[-1].y = x, y

    def handle_event(self, event):
        if event.type == pg.MOUSEBUTTONUP and event.button == 1:
            # click on erase option
            if self.opt_rects[0].collidepoint(event.pos):
                self.p.keep = False
                self.p.options_opened = False
            # click on edit option
            elif self.opt_rects[1].collidepoint(event.pos):
                self.p.active = True
                self.p.color = pg.Color(*EDITING_COLOR)
                self.p.text_surface = self.p.font.render(self.p.text, True, self.p.color)
                self.p.options_opened = False
            # click on color >>
            elif self.opt_rects[2].collidepoint(event.pos):
                self.p.color_idx = (self.p.color_idx + 1) % NUM_COLORS
                self.p.color = pg.Color(*COLOR_PAL[self.p.color_idx])
                self.options[2] = pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, self.p.color)
                self.p.text_surface = self.p.font.render(self.p.text, True, self.p.color)
            # click on size -
            elif self.opt_rects[3].collidepoint(event.pos):
                if self.p.fontsize > 2:
                    self.p.fontsize -= 2
                    self.p.font = pg.font.Font(CHALK_FONT, self.p.fontsize)
                    self.p.text_surface = self.p.font.render(self.p.text, True, self.p.color)
            # click on size +
            elif self.opt_rects[4].collidepoint(event.pos):
                self.p.fontsize += 2
                self.p.font = pg.font.Font(CHALK_FONT, self.p.fontsize)
                self.p.text_surface = self.p.font.render(self.p.text, True, self.p.color)
            # clicking anywhere else closes this options menu
            else:
                self.p.options_opened = False
        super().handle_event(event)

    def draw(self, screen):
        super().draw(screen)

class ChalkLineOptionsMenu(OptionsMenu):
    def __init__(self, chalkLine):
        super().__init__(chalkLine)
        # 4 options: erase, adjust, color, size
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Erase", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Adjust", True, OPTMENU_TEXT_COLOR))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, pg.Color(*COLOR_PAL[self.p.color_idx])))
        self.options.append(
            pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("- Size +", True, OPTMENU_TEXT_COLOR))
        # rectangles for the options
        w = self.options[2].get_width() + 10
        h = OPTMENU_FONTSIZE + 10
        for i in range(len(self.options)):
            self.opt_rects.append(pg.Rect(0, 0, w, h))
        # special setup for size option
        self.opt_rects[-1].width = w//2
        self.opt_rects.append(pg.Rect(0, 0, w//2, h))
        self.w = w
        # color of option rectangles (changes when hovered)
        self.rect_colors = [OPTMENU_COLOR] * len(self.opt_rects)

    def setpos(self, x, y):
        super().setpos(x, y)
        # special setup for size option
        x, y = self.opt_rects[-2].x + self.w//2, self.opt_rects[-2].y
        self.opt_rects[-1].x, self.opt_rects[-1].y = x, y

    def handle_event(self, event):
        if event.type == pg.MOUSEBUTTONUP and event.button == 1:
            # click on erase option
            if self.opt_rects[0].collidepoint(event.pos):
                self.p.keep = False
                self.p.options_opened = False
            # click on adjust option
            elif self.opt_rects[1].collidepoint(event.pos):
                self.p.drawn = False
                self.p.color = pg.Color(*EDITING_COLOR)
                self.p.options_opened = False
            # click on color >>
            elif self.opt_rects[2].collidepoint(event.pos):
                self.p.color_idx = (self.p.color_idx + 1) % NUM_COLORS
                self.p.color = pg.Color(*COLOR_PAL[self.p.color_idx])
                self.options[2] = pg.font.Font(OPTMENU_FONT, OPTMENU_FONTSIZE).render("Color >>", True, self.p.color)
            # click on size -
            elif self.opt_rects[3].collidepoint(event.pos):
                if self.p.width > 1:
                    self.p.width -= 1
            # click on size +
            elif self.opt_rects[4].collidepoint(event.pos):
                self.p.width += 1
            # clicking anywhere else closes this options menu
            else:
                self.p.options_opened = False
        super().handle_event(event)

    def draw(self, screen):
        super().draw(screen)


from django.shortcuts import render
from django.shortcuts import *
from django.http import *
from django.shortcuts import *
from django.urls import *
import traceback
import json
from .models import *
from django.db.utils import IntegrityError
from django.db.models import F
from .crypto import *
import math

MAX_PAGE_ITEMS = 6

def dealResponse(status_code, res_text={}):
    traceback.print_exc()
    dic = {
        400 : 'Decode Failed',
        406 : 'Verification Failed',
        200 : 'Standard Successed',
        201 : 'Create Resource Successed',
        409 : 'Confict Field or MultipleAcceptance',
        500 : 'Unknown Server Error',
        404 : 'Not Exist',
        416 : 'OutOfRange or CompletedTask',
        401 : 'Unauthorized',
        403 : 'TaskBeenFinished'
    }
    traceback.print_exc()
    print('[+] ' + dic[status_code])
    res_text['status_code'] = status_code
    resp = HttpResponse(encrypt(json.dumps(res_text)))
    # resp.status_code = status_code
    return resp

# Create your views here.
def get_balance(request):
    try:
        tusername = decrypt(request.GET['username'])
    except:
        return dealResponse(400)
    try:
        result = User.objects.get(username=tusername)
    except User.DoesNotExist:
        return dealResponse(404)
    return dealResponse(200, {'data':{'balance':result.wallet_set.first().balance}})