#!/usr/bin/python

#
# This script is designed to install datasets from the IQOffice DVD
# and configure them for use with IQOffice, it assumes the installation
# is layed out as per Noggin's rpm package of the software (which is
# built from IQ_Office.zip)
#
# You will need to verify the config file (/etc/iqoffice/intech.conf)
# for duplicates / errors if using it to *update* an existing installation
#
# TODO: When installing a newer PAF the Default symlink should be updated
#

import sys
from iniparse import ConfigParser, RawConfigParser;
import re;
import os;
import fnmatch;
import StringIO;
import pprint;


args = sys.argv[1:]
VERBOSE=True
CONFIRM=False
if '--confirm' in sys.argv:
	CONFIRM=True
	VERBOSE=False
	args.remove('--confirm');
if '--verbose' in sys.argv:
	VERBOSE=True
	args.remove('--verbose')


def usage(srcpath='/path/to/DVD'):
	print
	print "Usage: %s [--confirm] [--verbose] %s  <dataset1>[ ..<datasetN>]" % (sys.argv[0], srcpath);
	print
	print "Where <dataset>(s) may be specified as shell patterns (fnmatch glob)"
	print
	print "If run without --confirm the script will operate in dry-run verbose mode"
	print

if len(args) < 1:
	usage();
	sys.exit(1);

# Installation source (data files)
SRCDIR = args[0].rstrip('/');

# Installation target (base data dir)
DATADIR='/var/lib/iqoffice';

# IQOffice runtime configuration file
IQCONF='/etc/iqoffice/intech.conf';

# The intech config has no sections, so we'll fake a "GLOBAL"
iqconf = RawConfigParser();
strbuf = "[GLOBAL]\n" + open(IQCONF, 'r').read();
strfp  = StringIO.StringIO(strbuf);
iqconf.readfp(strfp);


iqdata = ConfigParser();
parsed = iqdata.read(SRCDIR + '/IQData.ini');
if (len(parsed) == 0):
        print "Unable to load '%s' check the path to the DVD content" % (SRCDIR + '/IQData.ini');
        sys.exit(2);

def commitConf():
	buf = StringIO.StringIO();
	iqconf.write(buf)
	buf.seek(0);
	raw = "".join(buf.readlines()[1:]);
	raw = re.sub('\s+=\s+', '=', raw);	
	if VERBOSE:
		print "\n========== Final Config (%s) ==================\n" % IQCONF
		print raw
	if CONFIRM:
		# Make a backup...
		runCommand("cp -f '%s' '%s~'" % (IQCONF, IQCONF))
		# And then re-write
		fp = open(IQCONF, 'w');
		fp.write(raw);
		fp.close()
		
# "Fix" any path components that have mismatched case (eg Paf => PAF)
def caseMatchPath(root, path):
	inpath = path.split('/');
	outpath = list();
	for p in inpath:
		if not os.path.exists(root + '/' + '/'.join(outpath) + '/' + p):
			# Try case insensitively
			for c in os.listdir(root + '/' + '/'.join(outpath)):
				if c.lower() == p.lower():
					p = c;
					break
		outpath.append(p);
	return '/'.join(outpath);


def runCommand(cmd):
	if VERBOSE:
		print "exec: " + cmd;
	if CONFIRM:
		os.system(cmd);

def delConf(k, v):
	for i in iqconf.items('GLOBAL'):
		if fnmatch.fnmatch(i[0].lower(), k.lower()) and fnmatch.fnmatch(i[1].lower(), v.lower()):
			iqconf.remove_option('GLOBAL', i[0]);

def addConf(k, v):
	iqconf.set('GLOBAL', k, v)
	

# defaultdict wasn't added until 2.5
class defaultdict(dict):
	def __init__(self, default):
		self.default = default

	def __getitem__(self, key):
		if key in self:
			return self.get(key)
		else:
			return self.setdefault(key, self.default())

# ARF Config Blocks by ARFID
# 'subitems' is a dictionary of the ARF's subitems by section name
ARFs = {};

# A dictionary mapping all the ArfRegistryNames to an ARFID
ARFMap = {};

ARFNames = defaultdict(list);

# The ARF(ID) we are currently parsing 
LastARF = None;

# Index the ARFs
for s in iqdata.sections():
	if iqdata.get(s, 'DataType') == 'ARF':
		arfid = iqdata.get(s, 'ARFID');
		items = iqdata.items(s);

		LastARF = arfid;
		ARFs[arfid] = dict(items);
		ARFs[arfid]['_sectionname'] = s;
		ARFs[arfid]['targetdir'] = ARFs[arfid]['targetdir'].replace("\\", '/').replace('"', '');
		# Map the registry names
		for i in items:
			if re.match('^ArfRegistryName\d*$', i[0], re.IGNORECASE):
				ARFNames[arfid].append(i[1]);
				ARFMap[i[1]] = arfid;

	elif iqdata.get(s, 'SubItem') == 'True':
		# Assign to the most recent ARF
		if 'subitems' not in ARFs[LastARF].keys():
			ARFs[LastARF]['subitems'] = {};
		ARFs[LastARF]['subitems'][s] = dict(iqdata.items(s));

# Find what to install	
installid = set();
if len(args) >  1:
	install = set();
	for p in args[1:]:
		install |= set(fnmatch.filter(ARFMap.keys(), p));
	for n in install:
		installid |= set(ARFMap[n]);

if len(installid) < 1:
	usage(SRCDIR);
	print "\nAvailable Data Sets:"
	for i in ARFs.keys():
		print "\t" + ARFs[i]['_sectionname'] + ' (AKA: ' + ", ".join(ARFNames[i]) + ')';
	print
	sys.exit(4);

# Generate the iqconfig entries
for id in installid:
	arf = ARFs[id];

	# Install the files
	print "\n\n### Install  '%s'" % arf['_sectionname'];
	# Clean out the old data (including any symlinks to it)
	runCommand("rm -rf \"%s\"" % os.path.join(DATADIR, ARFs[id]['targetdir']));
	runCommand("find \"%s\" -type l -delete" % os.path.join(DATADIR, ARFs[id]['targetdir']));
	# TODO: Restore any 'system' symlinks that were previous overridden by the data we just removed
	delConf("ARF/*", os.path.join(DATADIR, ARFs[id]['targetdir']))
	# Recreate the target dir
	runCommand("mkdir -p \"%s\"" % os.path.join(DATADIR, ARFs[id]['targetdir']));
	# Copy the content from the source
	runCommand("cp -r %s '%s/'" % (
		os.path.join(SRCDIR, caseMatchPath(SRCDIR, ARFs[id]['sourcefiles'].replace("\\", '/'))),
		os.path.join(DATADIR, ARFs[id]['targetdir'])
	));
	# Make the directories writeable
	runCommand("find '%s' -type d -print0 | xargs --null chmod 0755" % os.path.join(DATADIR, ARFs[id]['targetdir']));
	
	# Ensure that the files are readable
	runCommand("find '%s' -type f -print0 | xargs --null chmod 0644" % os.path.join(DATADIR, ARFs[id]['targetdir']));

	# Symlink the grammars and subdirs
	for si in arf['subitems']:
		sub = arf['subitems'][si];
		for i in sub.keys():
			if re.match('^SourceFiles?\d*$', i, re.IGNORECASE):
				subsource = arf['targetdir'] + '/' + '/'.join(sub[i].replace("\\", '/').split('/')[1:]);
				if sub['datatype'] == 'Grammar':
					runCommand("dos2unix %s" % (DATADIR + '/' + subsource));
					target = DATADIR + '/' + 'grammars';
				else:
					target = DATADIR + '/' + arf['targetdir'];
				# Symlink the items, note the "--force", this is because eg GNAF may provide
				# Updated grammars that supercede the system copies.
				runCommand("ln --force -s -t '%s/'  %s" % (target, DATADIR + '/' + subsource));


	# Config entries
	names = ARFNames[id];
        names.sort();	
	for n in names:
		addConf("ARF/%s" % n, "%s/%s" % (DATADIR, ARFs[id]['targetdir']));
	

commitConf()
