# GNU Solfege - eartraining for GNOME
# Copyright (C) 2000, 2001, 2002  Tom Cato Amundsen
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""
TODO:
    - cfg.get_list
    - cfg.set_list
    - watches outside ConfigUtils
>>> import cfg
>>> cfg.initialise("default.config", "~/.cfg-test")
>>> cfg.get_int("gui/button_height")
40
>>> cfg.set_string("section/stringvar", "value")
>>> cfg.get_string("section/stringvar")
'value'
>>> cfg.set_int("section/intvar", 14)
>>> cfg.get_int("section/intvar")
14
>>> cfg.set_float("section/floatvar", 3.141592)
>>> cfg.get_float("section/floatvar") == 3.141592
1
>>> cfg.set_bool("section/boolvar", 4)
>>> cfg.get_bool("section/boolvar")
1
>>> cfg.set_bool("section/boolvar", 0)
>>> cfg.get_bool("section/boolvar")
0
>>> c = ConfigUtils("section")
>>> c.get_string("section/stringvar")
'value'
>>> c.get_int("section/intvar")
14
>>> c.get_bool("section/boolvar")
0
>>> c.get_float("section/floatvar") == 3.141592
1
>>> cfg.initialise(None, "~/.solfege-cfg-testing")
>>> c.get_string("ss/a")
''
>>> c.get_string("ss/a=hei")
'hei'
>>> c.get_int("ss/a=3")
3
>>> c.get_list("ss/a")
[]
>>> c.get_list("ss/a=[1, 2, 3]")
[1, 2, 3]
"""
import string, re
import types
import os, os.path, sys

# It is a know fact that many windows lusers are $HOMEless
if sys.platform == 'win32':
    if not os.path.exists(os.path.expanduser("~")):
        os.makedirs(os.path.expanduser("~"))

section_re = re.compile("\[(.*?)\]")
value_re = re.compile("(.*?)=(.*)")
comment_re = re.compile("#.*")

# FIXME why did I comment out the lines below. Test in ms windows
# if this still works, and if so, delete the commented lines.
def split(key):
   #if "/" in key:
       return string.split(key, "/")
   #else:
   #    return string.split(key, "\\")

def parse_file_into_dict(dict, filename):
    file = open(filename, "r")
    line = file.readline()
    section = None
    while line:
        section_m = section_re.match(line)
        value_m = value_re.match(line)
        comment_m = comment_re.match(line)
        if section_m:
            section = section_m.groups()[0]
            if not dict.has_key(section):
                dict[section] = {}
        elif value_m:
            assert section
            dict[section][value_m.groups()[0]] = value_m.groups()[1]
        elif line == "\n":
            pass
        elif comment_m:
            pass
        else:
           raise "X"
        line = file.readline()
    file.close()
    return dict


def sync():
    dump(data, _user_filename)

def _maybe_create_key(key):
    global data
    section, k = split(key)
    if not data.has_key(section):
        data[section] = {}

########################
## set_XXXX functions ##
########################

def set_string(key, val):
    global data
    _maybe_create_key(key)
    section, k = split(key)
    data[section][k] = val

def set_int(key, val):
    global data
    assert type(val) == types.IntType
    _maybe_create_key(key)
    section, k = split(key)
    data[section][k] = str(val)

def set_float(key, val):
    global data
    assert type(val) == types.FloatType
    _maybe_create_key(key)
    section, k = split(key)
    data[section][k] = str(val)

def set_bool(key, val):
    global data
    _maybe_create_key(key)
    if val:
        set_string(key, "true")
    else:
        set_string(key, "false")


########################
## get_XXX functions ##
########################

def get_string(key):
    if len(string.split(key, "=")) == 2:
        key, default = string.split(key, "=")
    else:
        default = ""
    section, k = split(key)
    try:
        return data[section][k]
    except KeyError:
        return default

def get_int(key):
   if len(string.split(key, "=")) == 2:
      key, default = string.split(key, "=")
   else:
      default = 0
   section, k = split(key)
   try:
       return int(float(data[section][k]))
   except KeyError:
       return int(default)

def get_float(key):
   if len(string.split(key, "=")) == 2:
      key, default = string.split(key, "=")
   else:
      default = 0
   section, k = split(key)
   try:
       return float(data[section][k])
   except KeyError:
       return float(default)


def get_bool(key):
    if get_string(key) == 'true':
        return 1
    elif get_string(key) == 'false':
        return 0
    else:
        print "warning: cfg.get_bool: value of '%s' is not true or false" % key
        return 0

######################################
## reading and writing the database ##
######################################

def dump(dict, fn):
    f = open(fn, 'w')
    for section in dict.keys():
        f.write("[%s]\n" % section)
        for name in dict[section].keys():
            f.write("%s=%s\n" % (name, dict[section][name]))
        f.write("\n")
    f.close()

def drop_user_config():
    global data, _watches, _watch_counter
    data = {}
    _watches = {}
    _watch_counter = 0
    data = parse_file_into_dict(data, _system_filename)

def reread_data():
    global data, _watches, _watch_counter
    _watches = {}
    _watch_counter = 0
    if _app_defaults_filename:
        data = parse_file_into_dict({}, _app_defaults_filename)
    if _system_filename:
        data = parse_file_into_dict(data, _system_filename)
    if _user_filename and os.path.isfile(_user_filename):
        data = parse_file_into_dict(data, _user_filename)

def initialise(app_defaults_filename, system_filename, user_filename):
    """
    app_defaults_filename: file must exist!
    system_filename: sys admin can override app_defaults_filename
    user_filename: the state of the app is stored here, also user config.
    """
    global data, _watches, _watch_counter
    global _app_defaults_filename, _system_filename, _user_filename
    _watches = {}
    _watch_counter = 0
    _app_defaults_filename = app_defaults_filename
    _system_filename = system_filename
    _user_filename = user_filename = os.path.expanduser(user_filename)
    if app_defaults_filename:
        try:
            data = parse_file_into_dict({}, app_defaults_filename)
        except:
            raise "Solfege install is completely broken.\nCannot find required file %s" % app_defaults_filename
    else:
        data = {}
    if system_filename and os.path.isfile(system_filename):
        data = parse_file_into_dict(data, system_filename)
    if os.path.isfile(user_filename):
        data = parse_file_into_dict(data, user_filename)


class ConfigUtils:
    def __init__(self, exname):
        self.m_exname = exname
    def add_watch(self, name, callback):
        global _watches, _watch_counter
        name = self._expand_name(name)
        if not _watches.has_key(name):
            _watches[name] = {}
        _watches[name][_watch_counter] = callback
        _watch_counter = _watch_counter + 1
        return _watch_counter - 1
    def remove_watch(self, name, id):
        global _watches, _watch_counter
        name = self._expand_name(name)
        if _watches.has_key(name):
            if _watches[name].has_key(id):
                del _watches[name][id]
                if _watches[name] == {}:
                    del _watches[name]
            else:
                print "warning: remove_watch: id don't exist:", id
        else:
            print "warning: remove_watch: name is not watched:", name
    def _expand_name(self, name):
        if string.count(name, "/") == 0:
            return "%s/%s" % (self.m_exname, name)
        return name
    def _set(self, func, name, val):
        global _watches
        name = self._expand_name(name)
        oldval = get_string(name)
        func(name, val)
        newval = get_string(name)
        if _watches.has_key(name) and (oldval != newval):
            for cb in _watches[name].values():
               cb(name)
    def set_int(self, name, val):
        self._set(set_int, name, val)
    def set_float(self, name, val):
        self._set(set_float, name, val)
    def set_string(self, name, val):
        self._set(set_string, name, val)
    def set_list(self, name, val):
        self._set(set_string, name, str(val))
    def set_bool(self, name, val):
        self._set(set_bool, name, val)
    def _get(self, func, name, default=""):
        return func(self._expand_name(name)+default)
    def get_int(self, name):
        return self._get(get_int, name)
    def get_int_with_default(self, name, default):
        assert type(default) is type(0)
        return self.get_int(name+"=%i" % default)
    def get_float(self, name):
        return self._get(get_float, name)
    def get_string(self, name):
        r = self._get(get_string, name)
        if r is None:
            return ""
        else:
            return r
    def get_list(self, name):
        if '=' in name:
           return eval(self._get(get_string, name))
        return eval(self._get(get_string, name, "=[]"))
    def get_bool(self, name):
        return self._get(get_bool, name)


if __name__ == '__main__':
    import doctest, cfg
    print doctest.testmod(cfg)
