# --------------------------------------------------------------------------
# Module BPyRegistry version 0.1
#   Helper functions to store / restore configuration data.
# --------------------------------------------------------------------------
# $Id: BPyRegistry.py 14353 2008-04-07 20:56:45Z theeth $
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Copyright (C) 2004: Willian P. Germano, wgermano _at_ ig.com.br
#
# 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,
# --------------------------------------------------------------------------

# The Registry is a Python dictionary that is kept in Blender for as long as
# the program is running, where scripts can store / restore persistent data
# (data that is not lost when the script exits).  This module provides
# functions to save and restore Registry entries as config data in the
# bpydata/config folder.  Scripts just need to give an extra parameter to
# the Blender.Registry.Get/Set() functions to have their data automatically
# saved and restored when needed.
#
# Note: entries starting with an underscore are not saved, so script authors
# can use that fact to define data that is not meant to be stored in a
# config file.  Example: data to be passed to another script and references to
# invalid data, like Blender objects and any function or method.
#
# Check the Blender.Registry documentation for more information.

import Blender
from Blender import Registry, sys as bsys

_EXT = '.cfg' # file extension for saved config data

# limits:
#MAX_ITEMS_NUM = 60 # max number of keys per dict and itens per list and tuple
#MAX_STR_LEN = 300 # max string length (remember this is just for config data)

_CFG_DIR = ''
if Blender.Get('udatadir'):
	_CFG_DIR = Blender.sys.join(Blender.Get('udatadir'), 'config')
if not _CFG_DIR or not bsys.exists(_CFG_DIR):
	_CFG_DIR = Blender.sys.join(Blender.Get('datadir'), 'config')
if not bsys.exists(_CFG_DIR):
	_CFG_DIR = ''

# to compare against, so we don't write to a cvs tree:
_CVS_SUBPATH = 'release/scripts/bpydata/config/'
if bsys.dirsep == '\\':
	_CVS_SUBPATH = _CVS_SUBPATH.replace('/', '\\')

_KEYS = [k for k in Registry.Keys() if k[0] != '_']

# _ITEMS_NUM = 0

def _sanitize(o):
	"Check recursively that all objects are valid, set invalid ones to None"

	# global MAX_ITEMS_NUM, MAX_STR_LEN, _ITEMS_NUM

	valid_types = [int, float, bool, long, type]
	valid_checked_types = [str, unicode]
	# Only very simple types are considered valid for configuration data,
	# functions, methods and Blender objects (use their names instead) aren't.

	t = type(o)

	if t == dict:
		'''
		_ITEMS_NUM += len(o)
		if _ITEMS_NUM > MAX_ITEMS_NUM:
			return None
		'''
		for k, v in o.iteritems():
			o[k] = _sanitize(v)
	elif t in [list, tuple]:
		'''
		_ITEMS_NUM += len(o)
		if _ITEMS_NUM > MAX_ITEMS_NUM:
			return None
		'''
		return [_sanitize(i) for i in o]
	elif t in valid_types:
		return o
	elif t in valid_checked_types:
		'''
		if len(o) > MAX_STR_LEN:
			o = o[:MAX_STR_LEN]
		'''
		return o
	else: return None

	return o


def _dict_to_str(name, d):
	"Return a pretty-print version of the passed dictionary"
	if not d: return 'None' # d can be None if there was no config to pass
	
	if name: l = ['%s = {' % name]
	else: l = ['{']
	#keys = d.keys()
	for k,v in d.iteritems(): # .keys()
		if type(v) == dict:
			l.append("'%s': %s" % (k, _dict_to_str(None, v)))
		else:
			l.append("'%s': %s," % (k, repr(v)))
	if name: l.append('}')
	else: l.append('},')
	return "\n".join(l)

_HELP_MSG = """
Please create a valid scripts config dir tree either by
copying release/scripts/ tree to your <blenderhome> dir
or by copying release/scripts/bpydata/ tree to a user
defined scripts dir that you can set in the 
User Preferences -> Paths tab -> Python path input box.
"""

def _check_dir():
	global _CFG_DIR, _CVS_SUBPATH, _HELP_MSG

	if not _CFG_DIR:
		errmsg = "scripts config dir not found!\n%s" % _HELP_MSG
		raise IOError, errmsg
	elif _CFG_DIR.find(_CVS_SUBPATH) > 0:
		errmsg = """
Your scripts config dir:\n%s
seems to reside in your local Blender's cvs tree.\n%s""" % (_CFG_DIR, _HELP_MSG)
		raise SystemError, errmsg
	else: return


# API:

BPY_KEY_MISSING = 0
BPY_KEY_IN_REGISTRY = 1
BPY_KEY_IN_FILE = 2

def HasConfigData (key):
	"""
	Check if the given key exists, either already loaded in the Registry dict or
	as a file in the script data config dir.
	@type key: string
	@param key: a given key name.
	@returns:
		- 0: key does not exist;
		- 1: key exists in the Registry dict only;
		- 2: key exists as a file only;
		- 3: key exists in the Registry dict and also as a file.
	@note: for readability it's better to check against the constant bitmasks
		BPY_KEY_MISSING = 0, BPY_KEY_IN_REGISTRY = 1 and BPY_KEY_IN_FILE = 2.
	"""

	fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))

	result = BPY_KEY_MISSING
	if key in Registry.Keys(): result |= BPY_KEY_IN_REGISTRY
	if bsys.exists(fname): result |= BPY_KEY_IN_FILE

	return result


def LoadConfigData (key = None):
	"""
	Load config data from file(s) to the Registry dictionary.
	@type key: string
	@param key: a given key name.  If None (default), all available keys are
		loaded.
	@returns: None
	"""

	_check_dir()

	import os

	if not key:
		files = \
			[bsys.join(_CFG_DIR, f) for f in os.listdir(_CFG_DIR) if f.endswith(_EXT)]
	else:
		files = []
		fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))
		if bsys.exists(fname): files.append(fname)

	for p in files:
		try:
			f = file(p, 'r')
			lines = f.readlines()
			f.close()
			if lines: # Lines may be blank
				mainkey = lines[0].split('=')[0].strip()
				pysrc = "\n".join(lines)
				exec(pysrc)
				exec("Registry.SetKey('%s', %s)" % (str(mainkey), mainkey))
		except Exception, e:
			raise Warning(e) # Resend exception as warning


def RemoveConfigData (key = None):
	"""
	Remove this key's config file from the <(u)datadir>/config/ folder.
	@type key: string
	@param key: the name of the key to be removed.  If None (default) all
		available config files are deleted.
	"""

	_check_dir()

	if not key:
		files = \
			[bsys.join(_CFG_DIR, f) for f in os.listdir(_CFG_DIR) if f.endswith(_EXT)]
	else:
		files = []
		fname = bsys.join(_CFG_DIR, "%s%s" % (key, _EXT))
		if bsys.exists(fname): files.append(fname)

	import os

	try:
		for p in files:
			os.remove(p) # remove the file(s)
	except Exception, e:
		raise Warning(e) # Resend exception as warning


def SaveConfigData (key = None):
	"""
	Save Registry key(s) as file(s) in the <(u)datadir>/config/ folder.
	@type key: string
	@param key: the name of the key to be saved.  If None (default) all
		available keys are saved.
	"""

	global _KEYS, _CFG_DIR

	_check_dir()

	if key: keys = [key]
	else: keys = _KEYS

	for mainkey in keys:
		cfgdict = Registry.GetKey(mainkey).copy()
		for k in cfgdict: # .keys()
			if not k or k[0] == '_':
				del cfgdict[k]

		if not cfgdict: continue

		try:
			filename = bsys.join(_CFG_DIR, "%s%s" % (mainkey, _EXT))
			f = file(filename, 'w')
			output = _dict_to_str(mainkey, _sanitize(cfgdict))
			if output!='None':
				f.write(output)
				f.close()
		except Exception, e:
			raise Warning(e) # Resend exception as warning
