#A* -------------------------------------------------------------------
#B* This file contains source code for the PyMOL computer program
#C* Copyright (c) Schrodinger, LLC. 
#D* -------------------------------------------------------------------
#E* It is unlawful to modify or remove this copyright notice.
#F* -------------------------------------------------------------------
#G* Please see the accompanying LICENSE file for further information. 
#H* -------------------------------------------------------------------
#I* Additional authors of this source file include:
#-* 
#-* 
#-*
#Z* -------------------------------------------------------------------

from __future__ import print_function

if __name__=='pymol.setting':
    
    import traceback
    import string
    import types
    from . import selector
    from .shortcut import Shortcut
    cmd = __import__("sys").modules["pymol.cmd"]
    from .cmd import _cmd,lock,lock_attempt,unlock,QuietException, \
          is_string, \
          _feedback,fb_module,fb_mask, \
          DEFAULT_ERROR, DEFAULT_SUCCESS, _raising, is_ok, is_error        
    import re
    
    boolean_type = 1
    int_type     = 2
    float_type   = 3
    float3_type  = 4

    self = cmd

    # name -> index mapping
    index_dict = _cmd.get_setting_indices()

    # index -> name mapping
    name_dict = dict((v,k) for (k,v) in index_dict.items())

    name_list = list(index_dict.keys())
    setting_sc = Shortcut(name_list)

    # legacy
    index_dict['ray_shadows'] =     index_dict['ray_shadow']

    # legacy, in case someone used that in a script
    class SettingIndex:
        def __getattr__(self, name):
            return index_dict[name]

    boolean_dict = {
        "true" : 1,
        "false": 0,
        "on"   : 1,
        "off"  : 0,
        "1"    : 1,
        "0"    : 0,
        "1.0"  : 1,
        "0.0"  : 0,
        }

    boolean_sc = Shortcut(boolean_dict.keys())

    def _get_index(name):
        '''Get setting index for given name. `name` may be abbreviated.
        Raises QuietException for unknown names or ambiguous abbreviations.'''
        if isinstance(name, int) or name.isdigit():
            return int(name)
        if name not in index_dict:
            name = setting_sc.auto_err(name, 'Setting')
        return index_dict[name]

    def _get_name(index):
        # legacy, in case someone used that in a script
        return name_dict.get(index, "")

    def get_index_list():
        # legacy, in case someone used that in a script (e.g. grepset)
        return list(name_dict.keys())

    def get_name_list():
        return name_list

    def _validate_value(type, value):
        if type==1: # boolean (also support non-zero float for truth)
            try: # number, non-zero, then interpret as TRUE
                return 1 if float(value) else 0
            except:
                pass
            return boolean_dict[boolean_sc.auto_err(str(value), "boolean")]
        if type < 4:
            if is_string(value) and boolean_sc.has_key(value):
                value = boolean_dict[boolean_sc.auto_err(str(value), "boolean")]
            if type == 2:
                return int(value)
            return float(value)
        if type==4: # float3 - some legacy handling req.
            if not is_string(value):
                v = value
            elif ',' in value:
                v = eval(value)
            else:
                v = value.split()
            return (float(v[0]), float(v[1]), float(v[2]))
        if type==5: # color
            return str(value)
        if type==6: # string
            v = str(value)
            # strip outermost quotes (cheesy approach)
            if len(v) > 1 and v[0] == v[-1] and v[0] in ('"', "'"):
                v = v[1:-1]
            return v
        raise Exception

    ###### API functions

    def set_bond(name, value, selection1, selection2=None,
                 state=0, updates=1, log=0, quiet=1, _self=cmd):
        ''' 
DESCRIPTION

    "set_bond" changes per-bond settings for all bonds which exist
    between two selections of atoms.

USAGE

    set_bond name, value, selection1 [, selection2 ]

ARGUMENTS

    name = string: name of the setting

    value = string: new value to use

    selection1 = string: first set of atoms

    selection2 = string: seconds set of atoms {default: (selection1)}

EXAMPLE

    set_bond stick_transparency, 0.7, */n+c+ca+o


NOTES

    The following per-bond settings are currently implemented.  Others
    may seem to be recognized but will currently have no effect when
    set at the per-bond level.
    
    * valence
    * line_width
    * line_color
    * stick_radius
    * stick_color
    * stick_transparency

    Note that if you attempt to use the "set" command with a per-bond
    setting over a selection of atoms, the setting change will appear
    to take, but no change will be observed.
    
PYMOL API

    cmd.set_bond ( string name, string value,
                   string selection1,
                   string selection2,
                   int state, int updates, log=0, quiet=1)

       '''
        r = DEFAULT_ERROR
        selection1 = selector.process(selection1)
        selection2 = selector.process(selection2) if selection2 else selection1
        index = _get_index(str(name))
        if log:
            name = name_dict.get(index, name)
            _self.log('', "cmd.set_bond('%s',%s,%s,%s,%s)\n" % (name, repr(value),
                repr(selection1), repr(selection2), state))
        if True:
            try:
                _self.lock(_self)
                type = _cmd.get_setting_type(index)
                if type < 0:
                    print("Error: unable to get setting type.")
                    raise QuietException
                try:
                    v = (type, _validate_value(type, value))
                    r = _cmd.set_bond(_self._COb,int(index),v,
                                 "("+selection1+")","("+selection2+")",
                                 int(state)-1,int(quiet),
                                 int(updates))
                except QuietException:
                    pass
                except:
                    if(_feedback(fb_module.cmd,fb_mask.debugging,_self)):
                        traceback.print_exc()
                    raise _self.pymol.CmdException("invalid value: %s" % repr(value))
            finally:
                _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException            
        return r

        
    def set(name, value=1, selection='', state=0, updates=1, log=0,
            quiet=1,_self=cmd):
        
        '''
DESCRIPTION

    "set" changes global, object, object-state, or per-atom settings.

USAGE

    set name [,value [,selection [,state ]]]

ARGUMENTS

    name = string: setting name

    value = string: a setting value {default: 1}

    selection = string: name-pattern or selection-expression
    {default:'' (global)}

    state = a state number {default: 0 (per-object setting)}

EXAMPLES

    set orthoscopic

    set line_width, 3

    set surface_color, white, 1hpv
    
    set sphere_scale, 0.5, elem C

NOTES

    The default behavior (with a blank selection) is global.  If the
    selection is "all", then the setting entry in each individual
    object will be changed.  Likewise, for a given object, if state is
    zero, then the object setting will be modified.  Otherwise, the
    setting for the indicated state within the object will be
    modified.

    If a selection is provided as opposed to an object name, then the
    atomic setting entries are modified.

    The following per-atom settings are currently implemented.  Others
    may seem to be recognized but will have no effect when set on a
    per-atom basis.
    
    * sphere_color
    * surface_color
    * mesh_color
    * label_color
    * dot_color
    * cartoon_color
    * ribbon_color
    * transparency (for surfaces)
    * sphere_transparency
    
    Note that if you attempt to use the "set" command with a per-bond
    setting over a selection of atoms, the setting change will appear
    to take, but no change will be observed.  Please use the
    "set_bond" command for per-bond settings.
    

PYMOL API

    cmd.set(string name, string value, string selection, int state,
            int updates, int quiet)

SEE ALSO

    get, set_bond
    
'''
        r = DEFAULT_ERROR
        selection = selector.process(selection)
        index = _get_index(name)
        if log:
            name = name_dict.get(index, name)
            _self.log('', "cmd.set('%s',%s,%s,%s)\n" % (name, repr(value), repr(selection), state))
        if True:
            try:
                _self.lock(_self)
                type = _cmd.get_setting_type(index)
                if type < 0:
                    print("Error: unable to get setting type.")
                    raise QuietException
                try:
                    v = (type, _validate_value(type, value))
                    r = _cmd.set(_self._COb,int(index),v,
                                     selection,
                                     int(state)-1,int(quiet),
                                     int(updates))
                except QuietException:
                    pass
                except:
                    if(_feedback(fb_module.cmd,fb_mask.debugging,_self)):
                        traceback.print_exc()
                    raise _self.pymol.CmdException("invalid value: %s" % repr(value))
            finally:
                _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException            
        return r

    def unset(name, selection='', state=0, updates=1, log=0, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "unset" clear non-global settings and zeros out global settings.

USAGE

    unset name [,selection [,state ]]

EXAMPLE

    unset orthoscopic

    unset surface_color, 1hpv

    unset sphere_scale, elem C
    
NOTES

    If selection is not provided, unset changes the named global
    setting to a zero or off value.

    If a selection is provided, then "unset" undefines per-object,
    per-state, or per-atom settings.

PYMOL API

    cmd.unset(string name, string selection, int state, int updates,
                int log)

SEE ALSO

    set, set_bond
    
        '''
        r = DEFAULT_ERROR
        selection = selector.process(selection)
        index = _get_index(str(name))
        if log:
            name = name_dict.get(index, name)
            _self.log('', "cmd.unset('%s',%s,%s)\n" % (name, repr(selection), state))
        if True:
                try:
                    _self.lock(_self)
                    try:
                        r = _cmd.unset(_self._COb,int(index),selection,
                                            int(state)-1,int(quiet),
                                            int(updates))
                    except:
                        if(_feedback(fb_module.cmd,fb_mask.debugging,_self)):
                            traceback.print_exc()
                            raise QuietException
                        print("Error: unable to unset setting value.")
                finally:
                    _self.unlock(r,_self)
        return r
    
    def unset_bond(name,selection1,selection2=None,state=0,updates=1,log=0,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "unset_bond" removes a per-bond setting for a given set of bonds.
    
USAGE

    unset name [,selection [, selection [,state ]]]

        '''
        r = DEFAULT_ERROR
        selection1 = selector.process(selection1)
        selection2 = selector.process(selection2) if selection2 else selection1
        index = _get_index(str(name))
        if log:
            name = name_dict.get(index, name)
            _self.log('', "cmd.unset_bond('%s',%s,%s,%s)\n" % (name,
                repr(selection1), repr(selection2), state))
        if True:
            try:
                _self.lock(_self)
                try:
                    r = _cmd.unset_bond(_self._COb,int(index),selection1,selection2,
                                   int(state)-1,int(quiet),
                                   int(updates))
                except:
                    if(_feedback(fb_module.cmd,fb_mask.debugging,_self)):
                        traceback.print_exc()
                        raise QuietException
                    print("Error: unable to unset setting value.")
            finally:
                _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException            
        return r

    def get_setting(name,object='',state=0,_self=cmd): # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_tuple(_self._COb,i,str(object),int(state)-1)
            typ = r[0]
            if typ<3: # boolean, int
                value = int(r[1][0])
            elif typ<4: # float
                value = r[1][0]
            elif typ<5: # vector
                value = r[1]
            else:
                value = r[1] # color or string
        finally:
            _self.unlock(r,_self)
        if is_ok(r):
            return value
        elif _self._raising(r,_self):
            raise QuietException                     
        return r

    def get(name, selection='', state=0, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get" prints out the current value of a setting.

USAGE

    get name [, selection [, state ]]
    
EXAMPLE

    get line_width

ARGUMENTS

    name = string: setting name

    selection = string: object name (selections not yet supported)

    state = integer: state number
    
NOTES

    "get" currently only works with global, per-object, and per-state
    settings.  Atom level settings get be queried with "iterate" (e.g.
    iterate all, print s.line_width)
    
PYMOL API

    cmd.get(string name, string object, int state, int quiet)

SEE ALSO

    set, set_bond, get_bond

    '''
        
        r = DEFAULT_ERROR
        state = int(state)
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_text(_self._COb,i,str(selection),state-1)
        finally:
            _self.unlock(r,_self)
        if is_ok(r) and (r!=None):
            if not int(quiet):
                name = name_dict.get(i, name)
                r_str = str(r)
                if len(r_str) > 200:
                    r_str = r_str[:185] + '... (truncated)'
                if(selection==''):
                    print(" get: %s = %s"%(name,r_str))
                elif state<=0:
                    print(" get: %s = %s in object %s"%(name,r_str,selection))
                else:
                    print(" get: %s = %s in object %s state %d"%(name,r_str,selection,state))
        if _self._raising(r,_self): raise QuietException
        return r
    
    def get_setting_tuple_new(name,object='',state=0,_self=cmd): # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_tuple(_self._COb,i,str(object),int(state)-1)
        finally:
            _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException
        return r

    def get_setting_tuple(name,object='',state=0,_self=cmd): # INTERNAL
        r = get_setting_tuple_new(name, object, state, _self)
        if r[0] != 4:
            # legacy API
            r = (r[0], (r[1],))
        return r
    
    def get_setting_boolean(name,object='',state=0,_self=cmd): # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_of_type(_self._COb,i,str(object),int(state)-1,1)
        finally:
            _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException
        return r
    
    def get_setting_int(name,object='',state=0,_self=cmd): # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_of_type(_self._COb,i,str(object),int(state)-1,2)
        finally:
            _self.unlock(r,_self)
        return r
    
    def get_setting_float(name,object='',state=0,_self=cmd): # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_of_type(_self._COb,i,str(object),int(state)-1,3)
        finally:
            _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException
        return r

    def get_setting_text(name,object='',state=0,_self=cmd):  # INTERNAL
        r = DEFAULT_ERROR
        i = _get_index(name)
        try:
            _self.lock(_self)
            r = _cmd.get_setting_text(_self._COb,i,str(object),int(state)-1)
        finally:
            _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException
        return r

    def get_setting_updates(object='', state=0, _self=cmd): # INTERNAL
        r = []
        if lock_attempt(_self):
            try:
                r = _cmd.get_setting_updates(_self._COb, object, state-1)
            finally:
                _self.unlock(r,_self)
        return r

    def get_bond(name, selection1, selection2=None,
                 state=0, updates=1, quiet=1, _self=cmd):
        ''' 
DESCRIPTION

    "get_bond" gets per-bond settings for all bonds which exist
    between two selections of atoms.

USAGE

    get_bond name, selection1 [, selection2 ]

ARGUMENTS

    name = string: name of the setting

    selection1 = string: first set of atoms

    selection2 = string: seconds set of atoms {default: (selection1)}

EXAMPLE

    get_bond stick_transparency, */n+c+ca+o


NOTES

    The following per-bond settings are currently implemented.  Others
    may seem to be recognized but will currently have no effect when
    set at the per-bond level.
    
    * valence
    * line_width
    * line_color
    * stick_radius
    * stick_color
    * stick_transparency

PYMOL API

    cmd.get_bond ( string name,
                   string selection1,
                   string selection2,
                   int state, int updates, quiet=1)

       '''
        state, quiet = int(state), int(quiet)
        r = DEFAULT_ERROR
        selection1 = selector.process(selection1)
        selection2 = selector.process(selection2) if selection2 else selection1

        index = _get_index(str(name))
        if True:
            try:
                _self.lock(_self)
                try:
                    r = _cmd.get_bond(_self._COb,int(index),
                                 "("+selection1+")","("+selection2+")",
                                 int(state)-1,int(quiet),
                                 int(updates))
                except:
                    traceback.print_exc()
                    if(_feedback(fb_module.cmd,fb_mask.debugging,_self)):
                        traceback.print_exc()
                        print("Error: unable to get_bond info.")
                    raise QuietException
            finally:
                _self.unlock(r,_self)
        if _self._raising(r,_self): raise QuietException            
        if not quiet:
            name = name_dict.get(index, name)
            suffix = ' state %d' % state if state > 0 else ''
            for model, vlist in r:
                print(' %s = %s for object %s' % (name, cmd.get(name, model), model))
                for idx1, idx2, value in vlist:
                    if value is None:
                        continue
                    print(' %s = %s between (%s`%d)-(%s`%d%s)' % (name,
                            value, model, idx1, model, idx2, suffix))
        return r

    def unset_deep(settings='', object='*', updates=1, quiet=1, _self=cmd):
        '''
DESCRIPTION

    Unset all object, object-state, atom, and bond level settings.

    Note: Does currently NOT unset atom-state level settings. Check for
    atom-state level settings with:
    PyMOL> iterate_state 1, *, print list(s)
    Unset e.g. atom-state level "label_screen_point" (index 728) with:
    PyMOL> alter_state 1, *, del s[728]

ARGUMENTS

    settings = str: space separated list of setting names or empty string
    for all settings {default: }

    object = str: name of one object or * for all objects {default: *}
        '''
        quiet = int(quiet)
        kwargs = {'quiet': quiet, 'updates': 0, '_self': _self}

        if not settings:
            settings = iter(name_dict) # index iterator
        elif _self.is_string(settings):
            settings = settings.split()

        if object in ['all', '*']:
            object = '*'
            selection = '(*)'
        else:
            selection = None
            try:
                if _self.get_type(object) in (
                        'object:group', 'object:molecule'):
                    selection = '(' + object + ')'
            except:
                pass

        # 0 (object-level) and 1-N (object-state-level)
        states = range(cmd.count_states(object) + 1)

        for setting in settings:
            try:
                for state in states:
                    unset(setting, object, state=state, **kwargs)
                if selection:
                    unset(setting, selection, **kwargs)
            except:
                if not quiet:
                    print(' Setting: %s unset failed' % setting)

        if int(updates):
            _self.rebuild(object)
