#!/opt/anaconda1anaconda2anaconda3/bin/python
# -*- python -*-
# -*- coding: utf-8 -*-
# vim:filetype=python:
#
# Written by Jonas Juselius <jonas.juselius@chem.uit.no>
# University of Tromso, 2008
#
# Adapted to PCMSolver by Luca Frediani <luca.frediani@uit.no>
# University of Tromso, 2011
# Various modifications by Roberto Di Remigio <roberto.d.remigio@uit.no>
# University of Pisa, 2011-2012
# University of Tromso 2013-2014

"""
Parses the PCMSolver input file and generates a machine-readable input file.
Based on the Getkw library by J. Juselius.
The human-readable input file is first read and converted to uppercase.
Parsing occurs after case conversion, so that input reading is case-insensitive.

Conventions:
routine names my_perfect_routine
keyword names MYPERFECTKEYWORD
"""

isAngstrom = False
CODATAyear = 2010
# Dictionary with the au-to-Angstrom conversion factors for various CODATA sets of constants
CODATAdict = dict([(2010, 0.52917721092), (2006, 0.52917720859), (2002, 0.5291772108), (1998, 0.5291772083)])

import sys
import os
sys.path.append(os.path.dirname(__file__))

import docopt
from copy import deepcopy
import getkw
import re
import subprocess

options = """
Usage:
    ./pcmsolver.py [options] [<input_file>]
    ./pcmsolver.py (-h | --help)

Options:
  -x --exe                               Execute standalone program.
  <input_file>                           PCMSolver input file.
  -h --help                              Show this screen.
"""

def iequal(a, b):
    """
    Checks that strings a and b are equal, regardless of case.
    """
    try:
        return a.upper() == b.upper()
    except AttributeError:
        return a == b

def convert_to_upper_case(filename):
    """
    Reads contents of filename and converts them to uppercase.
    The case-converted contents are written back to filename.
    """
    contents = ''
    final    = ''
    with open(filename, 'r') as inputFile:
        contents  = inputFile.readlines()
    # In case the "restart" field is present the case conversion
    # has to be done a bit more carefully. The filename argument to
    # "restart" should not be touched, whereas the rest of the file
    # has to be put in uppercase.
    npz_file = re.compile('NPZFILE', re.IGNORECASE)
    dyadic_file = re.compile('DYADICFILE', re.IGNORECASE)
    for line in contents:
        if (npz_file.search(line)) or (dyadic_file.search(line)):
            rst_split = re.split(r"=", line)
            rst_split = rst_split[0].upper() + ' = ' + rst_split[1]
            line = ''.join(rst_split)
        else:
            line = line.upper()
        final += line
    # Convert also occurences of TRUE/FALSE to True/False,
    # Python will otherwise interpret them as strings!
    final = re.sub('FALSE', 'False', final)
    final = re.sub('TRUE',  'True',  final)

    # Write to file
    with open(filename, 'wb') as outputFile:
        outputFile.write(final)

def main():
    global topsect

    try:
        arguments = docopt.docopt(options, argv=None)
    except docopt.DocoptExit:
        sys.stderr.write('ERROR: bad input to %s\n' % sys.argv[0])
        sys.stderr.write(options)
        sys.exit(-1)
    inpfil = arguments['<input_file>']
    # Do the parsing and return parsed file name
    parsed = parse_pcm_input(inpfil)
    # Execute standalone
    if arguments['--exe']:
        execute(parsed)


def parse_pcm_input(inputFile):
    """
    Parses human-readable input to PCMSolver and writes to
    file the machine-readable parsed file.
    This function can be accessed from other Python scripts when
    importing the current file as a module.
    """
    global topsect

    # Set up valid keywords.
    valid_keywords = setup_keywords()

    # Convert to uppercase
    convert_to_upper_case(inputFile)

    # Set up a GetKw object and let it parse our input:
    # here is where the magic happens.
    input = getkw.GetkwParser()
    inkw  = input.parseFile(inputFile)
    inkw.sanitize(valid_keywords)
    topsect = inkw.get_topsect()
    inkw.run_callbacks(valid_keywords)

    # The parsed, machine-readable file is now saved.
    parsedFile = '@' + inputFile
    # If parsedFile already exists, it might be a leftover.
    # Remove it
    if (os.path.isfile(parsedFile)):
        os.remove(parsedFile)
    fd = open(parsedFile, 'w')
    print >> fd, inkw.top
    fd.close()

    return parsedFile

def execute(parsedFile):
    exename = "run_pcm" + ""
    executable = os.path.join(os.path.dirname(__file__), exename)
    if (executable):
        command = executable + ' ' + parsedFile
        p = subprocess.Popen(command, shell=True,
                             stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        s = p.communicate()[0]
        print(s)
    else:
        print('No executable available for standalone run')
        sys.exit(1)

def setup_keywords():
    """
    Sets up sections, keywords and respective callback functions.
    """
    # Top-level section
    top = getkw.Section('toplevel', callback = verify_top)
    top.set_status(True)
    # Define units of measure
    # Valid values: AU (atomic units) or ANGSTROM
    # Default: AU
    top.add_kw('UNITS',  'STR', 'AU')
    # Define set of CODATA constants
    # Valid values: 2010, 2006, 2002, 1998
    # Default: 2010
    top.add_kw('CODATA', 'INT', 2010)

    # Cavity section
    cavity = getkw.Section('CAVITY', callback = verify_cavity)
    # Type of the cavity
    # Valid values: GEPOL, WAVELET, TSLESS and RESTART
    cavity.add_kw('TYPE',        'STR')
    # Name of the file containing data for restarting a cavity
    # Valid for: Restart cavity
    # Default: empty string
    cavity.add_kw('NPZFILE',     'STR', '')
    # Name of the file containing the list of points for a wavelet cavity
    # Valid for: Wavelet cavity
    # Default: empty string
    cavity.add_kw('DYADICFILE',     'STR', '')
    # Patch level
    # Valid for: Wavelet cavity
    # Valid values: integer strictly greater than 0
    # Default: 2
    cavity.add_kw('PATCHLEVEL',  'INT', 2)
    # Coarsity
    # Valid for: Wavelet cavity
    # Valid values: double in ]0.0, 1.0[
    # Default: 0.5
    cavity.add_kw('COARSITY',    'DBL', 0.5)
    # Average area (in au^2)
    # Valid for: GePol and TsLess cavities
    # Valid values: double strictly greater than 0.01 au^2
    # Default: 0.3 au^2
    cavity.add_kw('AREA',        'DBL', 0.3)
    # Minimal distance between sampling points (in au)
    # Valid for: TsLess cavity
    # Valid values: double
    # Default: 0.1 au
    cavity.add_kw('MINDISTANCE', 'DBL', 0.1)
    # Derivative order for the weight function
    # Valid for: TsLess cavity
    # Valid values: integer
    # Default: 4
    cavity.add_kw('DERORDER',    'INT', 4)
    # Scaling of the atomic radii
    # Valid for: GePol, Wavelet and TsLess
    # Valid values: boolean
    # Default: True
    cavity.add_kw('SCALING',     'BOOL', True)
    # Built-in radii set
    # Valid for: GePol, Wavelet and TsLess
    # Valid values: BONDI or UFF
    # Default: BONDI
    cavity.add_kw('RADIISET',    'STR', 'BONDI')
    # Minimal radius of added spheres (in au)
    # Valid for: GePol and TsLess
    # Valid values: double greater than 0.4 au
    # Default: 100.0 au (no added spheres)
    cavity.add_kw('MINRADIUS',   'DBL', 100.0)
    # Spheres geometry creation mode
    # Valid for: GePol, TsLess and Wavelet
    # Valid values: EXPLICIT, ATOMS or IMPLICIT
    # Default: IMPLICIT
    cavity.add_kw('MODE',        'STR', 'IMPLICIT')
    # List of atoms with custom radius
    # Valid for: GePol, TsLess and Wavelet in MODE=ATOMS
    # Valid values: array of integers
    cavity.add_kw('ATOMS',       'INT_ARRAY')
    # List of custom radii
    # Valid for: GePol, TsLess and Wavelet in MODE=ATOMS
    # Valid values: array of doubles
    cavity.add_kw('RADII',       'DBL_ARRAY')
    # List of spheres
    # Valid for: GePol, TsLess and Wavelet in MODE=EXPLICIT
    # Valid values: array of doubles in format [x, y, z, R]
    cavity.add_kw('SPHERES',     'DBL_ARRAY', callback = verify_spheres)
    top.add_sect(cavity)

    # Medium section
    medium = getkw.Section('MEDIUM', callback = verify_medium)
    # Type of solver
    # Valid values: IEFPCM, CPCM, WAVELET or LINEAR
    medium.add_kw('SOLVERTYPE',   'STR', 'IEFPCM')
    # Whether nonequilibrium response is to be used
    # Valid for: IEFPCM, CPCM, WAVELET or LINEAR
    # Valid values: boolean
    # Default: False
    medium.add_kw('NONEQUILIBRIUM',   'BOOL', False)
    # Solvent
    # Valid for: IEFPCM, CPCM, Wavelet and PWL solvers
    # Valid values: string
    medium.add_kw('SOLVENT',      'STR')
    # Type of integral equation
    # Valid for: Wavelet and PWL
    # Valid values: FIRSTKIND, SECONDKIND or FULL
    medium.add_kw('EQUATIONTYPE', 'STR', 'SECONDKIND')
    # Symmetrization of the PCM matrix
    # Valid for: IEFPCM
    # Valid values: boolean
    # Default: True
    medium.add_kw('MATRIXSYMM',   'BOOL', True)
    # CPCM correction factor
    # Valid for: CPCM
    # Valid values: positive double greater than 0.0
    medium.add_kw('CORRECTION',   'DBL', 0.0)
    # Type of integrator for the diagonal of the boundary integral operators
    # Valid for: IEFPCM, CPCM
    # Valid values: COLLOCATION
    # Default: COLLOCATION
    # Notes: In future releases we will add PURISIMA and NUMERICAL as additional options
    medium.add_kw('DIAGONALINTEGRATOR', 'STR', 'COLLOCATION')
    # Scaling factor for diagonal of collocation matrix
    # Valid for: IEFPCM, CPCM
    # Valid values: positive double greater than 0.0
    # Default: 1.07
    # Notes: values commonly used in the literature are 1.07 and 1.0694
    medium.add_kw('DIAGONALSCALING', 'DBL', 1.07)
    # Radius of the solvent probe (in au)
    # Valid for: IEFPCM, CPCM, Wavelet and PWL
    # Valid values: double in [0.1, 100.0] au
    # Default: 1.0
    medium.add_kw('PROBERADIUS',  'DBL', 1.0)
    # A priori compression parameter a
    # Valid for: wavelet solvers
    # Valid values: double greater than 1.0
    # Default: 1.25
    medium.add_kw('A', 'DBL', 1.25)
    # A priori compression parameter d'
    # Valid for: wavelet solvers
    # Valid values: double greater than 1.0
    # Default: 1.25 (PWC) or 2.25 (PWL)
    medium.add_kw('DPRIME', 'DBL', 0.0)
    # A posteriori compression parameter b
    # Valid for: wavelet solvers
    # Valid values: double in ]0, 1[
    # Default: 0.001
    medium.add_kw('B', 'DBL', 0.001)
    top.add_sect(medium)

    # Green's function section
    green = getkw.Section('GREEN', callback = verify_green)
    # Green's function type
    # Valid values: VACUUM, UNIFORMDIELECTRIC, SPHERICALDIFFUSE, METALSPHERE, GREENSFUNCTIONSUM
    # Default: VACUUM
    green.add_kw('TYPE',           'STR', 'VACUUM')
    # Green's function derivative calculation strategy
    # Valid values: NUMERICAL, DERIVATIVE, GRADIENT, HESSIAN
    # Default: DERIVATIVE
    # Notes: all other values for debug purposes only
    green.add_kw('DER',            'STR', 'DERIVATIVE')
    # Static dielectric permittivity
    # Valid for: UNIFORMDIELECTRIC
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPS',            'DBL', 1.0)
    # Dynamic dielectric permittivity
    # Valid for: UNIFORMDIELECTRIC
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPSDYN',            'DBL', 1.0)
    # Real part of the metal's dielectric permittivity
    # Valid for: METALSPHERE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPSRE',          'DBL', 1.0)
    # Imaginary part of the metal's dielectric permittivity
    # Valid for: METALSPHERE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPSIMG',         'DBL', 1.0)
    # Radius of the metal sphere (in au)
    # Valid for: METALSPHERE
    # Valid values: positive double greater than 1.0 au
    # Default: 1.0
    green.add_kw('SPHERERADIUS',   'DBL', 1.0)
    # Position of the metal sphere (in au)
    # Valid for: METALSPHERE
    # Valid values: array of doubles
    green.add_kw('SPHEREPOSITION', 'DBL_ARRAY', [0.0, 0.0, 0.0])
    # Dielectric profile type
    # Valid for: SPHERICALDIFFUSE
    # Valid values: TANH, ERF
    # Default: TANH
    green.add_kw('PROFILE', 'STR', 'TANH')
    # Static dielectric permittivity inside the interface
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPS1',            'DBL', 1.0)
    # Dynamic dielectric permittivity inside the interface
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPSDYN1',            'DBL', 1.0)
    # Static dielectric permittivity outside the interface
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPS2',            'DBL', 1.0)
    # Dynamic dielectric permittivity outside the interface
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double greater than 1.0
    # Default: 1.0
    green.add_kw('EPSDYN2',            'DBL', 1.0)
    # Center of the diffuse profile
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double
    # Default: 100.0
    # Notes: for SPHERICALDIFFUSE, corresponds to the sphere radius
    green.add_kw('CENTER', 'DBL', 100.0)
    # Width of the diffuse profile
    # Valid for: SPHERICALDIFFUSE
    # Valid values: positive double
    # Default: 5.0
    # Notes: this is used differently for different profiles
    green.add_kw('WIDTH', 'DBL', 5.0)
    # Center of the diffuse interface
    # Valid for: SPHERICALDIFFUSE
    # Valid values: array of doubles
    green.add_kw('INTERFACEORIGIN', 'DBL_ARRAY', [0.0, 0.0, 0.0])
    # Maximum angular momentum value
    # Valid for: SPHERICALDIFFUSE
    # Valid values: integer
    green.add_kw('MAXL', 'INT', 30)
    medium.add_sect(green)

    green_part = deepcopy(green)
    green.add_sect(green_part)

    # Molecule section
    molecule = getkw.Section('MOLECULE')
    # List of geometry and classical point charges
    # Valid values: array of doubles in format [x, y, z, Q]
    # charges are in always in atomic units
    molecule.add_kw('GEOMETRY',     'DBL_ARRAY', callback = verify_geometry)
    top.add_sect(molecule)

    return top

def verify_top(section):
    global isAngstrom, CODATAyear
    allowed_units = ('AU', 'ANGSTROM')
    key = section.get('UNITS')
    val = key.get()
    if (val not in allowed_units):
        print('Units requested {} are not among the allowed units: {}'.format(val, allowed_units))
        sys.exit(1)
    if (val == 'ANGSTROM'):
        isAngstrom = True
    allowed_codata = (2010, 2006, 2002, 1998)
    CODATAyear = section.get('CODATA').get()
    if (CODATAyear not in allowed_codata):
        print('CODATA set requested {} is not among the allowed sets: {}'.format(CODATAyear, allowed_codata))
        sys.exit(1)

def verify_cavity(section):
    allowed = ('GEPOL', 'WAVELET', 'TSLESS', 'RESTART')
    type = section.get('TYPE')
    if (type.get() not in allowed):
        print('Requested {} cavity is not among the allowed types: {}'.format(type, allowed))
        sys.exit(1)

    # Convert units if input was given in Angstrom
    # The conversion functions check by themselves if the conversion is necessary or not!!
    if (section['AREA'].is_set()):
        convert_area_scalar(section['AREA'])
    if (section['MINRADIUS'].is_set()):
        convert_length_scalar(section['MINRADIUS'])
    if (section['MINDISTANCE'].is_set()):
        convert_length_scalar(section['MINDISTANCE'])


    if (type.get() == 'GEPOL'):
        area = section.get('AREA')
        a = area.get()
        if (a < 0.01):
            print('Requested area value too small: {}. Minimal value is: 0.01 au^2'.format(a))
            sys.exit(1)
        minRadius = section.get('MINRADIUS')
        mr = minRadius.get()
        if ( mr < 0.4 ):
            print('Requested minimal radius for added spheres too small: {}. Minimal value is: 0.4 au'.format(mr))
            sys.exit(1)
    elif (type.get() == 'WAVELET'):
        key = section.get('PATCHLEVEL')
        if (key.get() < 1):
            print('Invalid patch level requested: {}. Patch level must be greater than 0'.format(key.get()))
            sys.exit(1)
        key = section.get('COARSITY')
        if (key.get() < 0.0 or key.get() >= 1.0):
            print('Invalid coarsity requested: {}. Coarsity has to be within ]0, 1['.format(key.get()))
            sys.exit(1)
    elif (type.get() == 'TSLESS'):
        area = section.get('AREA')
        a = area.get()
        if (a < 0.01):
            print('Requested area value too small: {}. Minimal value is: 0.01 au^2'.format(a))
            sys.exit(1)
        minDistance = section.get('MINDISTANCE')
        md = minDistance.get()
        # Insert a sanity check on minimal distance!
        #if ( md < 0.4 ):
        #       print("Requested minimal distance between sampling points too small: {}. Minimal value is: 0.4 au".format(md))
        #       sys.exit(1)
        derOrder = section.get('DERORDER')
        do = derOrder.get()
        # Insert a check on derivative order
        #if ( do > 4 ):
        #       print("Invalid derivative order requested: {}. Derivative order has to be within [1, 5]".format(md))
        #       sys.exit(1)
    elif (type.get() == 'RESTART'):
        npzfile = section.get('NPZFILE')
        # Restart string is the filename, with extension, where the cavity specs were saved.
        # We only get the filename here, either an empty or a non-empty string,
        # further management of input is then done C++-side.
        if npzfile.get() == '':
            print('You need to specify a .npz filename for a restart...')
            sys.exit(1)

    radiiSet = section.get('RADIISET')
    allowed_sets = ('BONDI', 'UFF')
    if (radiiSet.get() not in allowed_sets):
        print('Radii set requested {} is not among the allowed sets: {}'.format(radiiSet.get(), allowed_sets))
        sys.exit(1)
    allowed_modes = ('EXPLICIT', 'ATOMS', 'IMPLICIT')
    mode = section.get('MODE')
    if (mode.get() not in allowed_modes):
        print('Cavity creation mode requested {} is not among the allowed modes: {}'.format(mode.get(), allowed_modes))
        sys.exit(1)

    atoms=section.get('ATOMS')
    at=atoms.get()
    radii = section.get('RADII')
    convert_length_array(radii);
    r = radii.get()

    if (mode.get() == 'ATOMS'):
        if (len(r) != len(at) or len(at) == 0):
            print('Incoherent input for Atoms keyword. Check that Atoms and Radii are consistent.')
            sys.exit(1)
        else:
            for i, v in enumerate(at):
                if (at.count(v) > 1):
                    print('Incoherent input for Atoms keyword. Too many spheres on the same atom(s).')
                    sys.exit(1)

def verify_medium(section):
    allowedSolvents = {'Water'                : ('WATER',                'H2O'),
                       'Methanol'             : ('METHANOL',             'CH3OH'),
                       'Ethanol'              : ('ETHANOL',              'CH3CH2OH'),
                       'Chloroform'           : ('CHLOROFORM',           'CHCL3'),
                       'Methylenechloride'    : ('METHYLENECHLORIDE',    'CH2CL2'),
                       '1,2-Dichloroethane'   : ('1,2-DICHLOROETHANE',   'C2H4CL2'),
                       'Carbon Tetrachloride' : ('CARBON TETRACHLORIDE', 'CCL4'),
                       'Benzene'              : ('BENZENE',              'C6H6'),
                       'Toluene'              : ('TOLUENE',              'C6H5CH3'),
                       'Chlorobenzene'        : ('CHLOROBENZENE',        'C6H5CL'),
                       'Nitromethane'         : ('NITROMETHANE',         'CH3NO2'),
                       'N-heptane'            : ('N-HEPTANE',            'C7H16'),
                       'Cyclohexane'          : ('CYCLOHEXANE',          'C6H12'),
                       'Aniline'              : ('ANILINE',              'C6H5NH2'),
                       'Acetone'              : ('ACETONE',              'C2H6CO'),
                       'Tetrahydrofurane'     : ('TETRAHYDROFURANE',     'THF'),
                       'Dimethylsulfoxide'    : ('DIMETHYLSULFOXIDE',    'DMSO'),
                       'Acetonitrile'         : ('ACETONITRILE',         'CH3CN'),
                       'Explicit'             : ('EXPLICIT',             'DUMMY')}
    solvent = section.get('SOLVENT')
    explicitSolvent = solvent.get() in allowedSolvents['Explicit']
    if (explicitSolvent):
        PRF = section.is_set('PROBERADIUS')
        GIF = section.is_set('GREEN<INSIDE>')
        GOF = section.is_set('GREEN<OUTSIDE>')
        if (not PRF):
            print('Error: Explicit solvent chosen but ProbeRadius not specified')
        if (not GIF):
            print('Error: Explicit solvent chosen but Green<inside> not specified')
        if (not GOF):
            print('Error: Explicit solvent chosen but Green<outside> not specified')
        if (not GIF or not GOF or not PRF):
            sys.exit(1)
    solventFound = False
    for i, v in allowedSolvents.iteritems():
        if (solvent.get() in v):
            solventName = i
            # Set name to the first value in the value pair
            # C++ will look for this name!
            solvent.set(v[0])
            solventFound = True
            break
    if (not solventFound):
        print('Unknown solvent {}'.format(solvent.get()))
        print('Choose a solvent from the following list:\n{}\n or specify the solvent data explicitly.'.format(allowedSolvents.keys()))
        sys.exit(1)

    correction = section.get('CORRECTION')
    if (correction.get() < 0.0):
        print('Correction for CPCM solver must be greater than 0.0')
        sys.exit(1)

    integrator = section.get('DIAGONALINTEGRATOR')
    if (integrator.get() is not 'COLLOCATION'):
        print('Only the collocation integrator is available')
        sys.exit(1)

    scaling = section.get('DIAGONALSCALING')
    if scaling.get() == 0.0:
        print('Scaling of diagonal for collocation matrices cannot be zero')
        sys.exit(1)

    convert_length_scalar(section.get('PROBERADIUS'))
    radius = section.get('PROBERADIUS')
    if (radius.get() < 0.1 or radius.get() > 100):
        print('Probe radius has to be within [0.1,100] Atomic Units')
        sys.exit(1)

    allowed_types = ('IEFPCM', 'CPCM', 'WAVELET', 'LINEAR')
    key = section.get('SOLVERTYPE')
    val = key.get()
    if (val not in allowed_types):
        print('Allowed types are: {}'.format(allowed_types))
        sys.exit(1)
    allowed_equations = ('FIRSTKIND', 'SECONDKIND', 'FULL')
    key = section.get('EQUATIONTYPE')
    val = key.get()
    if (val not in allowed_equations):
        print('Allowed equations are: {}'.format(allowed_equations))
        sys.exit(1)

    a = section.get('A')
    if (a.get() < 1.0):
        print('A priori compression parameter a has to be greater than 1.0')
        sys.exit(1)
    dprime = section.get('DPRIME')
    solver = section.get('SOLVERTYPE')
    if (dprime.get() == 0.0):
        if (solver.get() == 'WAVELET'):
            dprime.set(1.25)
        elif (solver.get() == 'LINEAR'):
            dprime.set(2.25)

    b = section.get('B')
    if (b.get() >= 1.0 and b.get() <= 0.0):
        print('A posteriori compression parameter has to be in ]0.0, 1.0[')
        sys.exit(1)

def verify_green(section):
    allowed     = ('VACUUM', 'UNIFORMDIELECTRIC', 'SPHERICALDIFFUSE', 'ALTERNATESPHERICALDIFFUSE', 'METALSPHERE', 'GREENSFUNCTIONSUM')
    allowed_der = ('NUMERICAL', 'DERIVATIVE', 'GRADIENT', 'HESSIAN')
    allowed_profiles = ('TANH', 'ERF')

    green1 = section.fetch_sect('GREEN<ONE>')
    green2 = section.fetch_sect('GREEN<TWO>')
    eps    = section.get('EPS')
    epsdyn = section.get('EPSDYN')
    epsimg = section.get('EPSIMG')
    epsre  = section.get('EPSRE')

    convert_length_array(section.get('SPHEREPOSITION'))
    position = section.get('SPHEREPOSITION')
    convert_length_scalar(section.get('SPHERERADIUS'))
    radius = section.get('SPHERERADIUS')
    convert_length_scalar(section.get('CENTER'))
    convert_length_scalar(section.get('WIDTH'))
    convert_length_array(section.get('INTERFACEORIGIN'))

    type = section.get('TYPE')
    if (type.get() not in allowed):
        print('Allowed Green\'s functions are: {}'.format(allowed))
        sys.exit(1)

    der = section.get('DER')
    if (der.get() not in allowed_der):
        print('Allowed derivatives strategies are: {}'.format(allowed))
        sys.exit(1)

    if (type.get() == 'UNIFORMDIELECTRIC'):
        if not eps.is_set():
            print('Eps not defined for UniformDielectric')
            sys.exit(1)
        if not epsdyn.is_set():
            print('EpsDyn not defined for UniformDielectric')
            sys.exit(1)

    if (type.get() == 'METALSPHERE'):
        if not (eps.is_set() and epsre.is_set and epsimg.is_set()):
            print('Eps and/or EpsImg not defined for MetalSphere')
            sys.exit(1)
        if not (position.is_set() and radius.is_set()):
            print('SpherePosition and/or SphereRadius not defined for MetalSphere')
            sys.exit(1)
        if (len(position.get()) != 3):
            print('SpherePosition error')
            sys.exit(1)
        if (radius.get() < 0.1):
            print('Minimum value allowed for Radius is 0.1 au')
            sys.exit(1)

    if (type.get() == 'GREENSFUNCTIONSUM'):
        if not (green1.is_set() and green2.is_set()):
            print('One or both components not defined for GreensFunctionSum')
            sys.exit(1)

    profile = section.get('PROFILE')
    if (profile.get() not in allowed_profiles):
        print('Allowed profiles are: {}'.format(allowed_profiles))
        sys.exit(1)

def verify_geometry(keyword):
    data = keyword.get()
    length=len(data)
    if (length % 4 != 0):
        print('Empty or incoherent geometry')
        sys.exit(1)
    # Convert only geometry to AU. Leave charges untouched
    j = 0
    for i in range(length/4):
        data[j] /= CODATAdict[CODATAyear]
        data[j+1] /= CODATAdict[CODATAyear]
        data[j+2] /= CODATAdict[CODATAyear]
        j += 4

def verify_spheres(keyword):
    length=len(keyword.get())
    if (length % 4 != 0):
        print('Empty or incoherent Spheres list.')
        sys.exit(1)
    convert_length_array(keyword)

def convert_length_array(keyword):
    length=len(keyword.get())
    if (isAngstrom):
        for i in range(length):
            keyword[i] /= CODATAdict[CODATAyear]

def convert_length_scalar(keyword):
    if (isAngstrom):
        keyword[0] /= CODATAdict[CODATAyear]

def convert_area_scalar(keyword):
    if (isAngstrom):
        keyword[0] /= (CODATAdict[CODATAyear] * CODATAdict[CODATAyear])

if __name__ == '__main__':
    main()
