#!/usr/bin/python3
# pylint: disable=consider-using-with,redefined-outer-name,invalid-name,missing-function-docstring,missing-class-docstring,missing-module-docstring,no-else-return,line-too-long

#
# 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 io
import sys
import re
import os
import fnmatch
from iniparse import ConfigParser, RawConfigParser


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(f"""
                Usage: {sys.argv[0]} [--confirm] [--verbose] {srcpath} <dataset1>[ ..<datasetN>]
                Where <dataset>(s) may be specified as shell patterns (fnmatch glob)
                If run without --confirm the script will operate in dry-run verbose mode
        """)

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, mode='r', encoding='utf-8').read()
strfp  = io.StringIO(strbuf)
iqconf.readfp(strfp)


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

def commitConf():
    buf = io.StringIO()
    iqconf.write(buf)
    buf.seek(0)
    raw = "".join(buf.readlines()[1:])
    raw = re.sub(r'\s+=\s+', '=', raw)
    if VERBOSE:
        print(f"\n========== Final Config ({IQCONF}) ==================\n")
        print(raw)
    if CONFIRM:
        # Make a backup...
        runCommand(f"cp -f '{IQCONF}' '{IQCONF}~'")
        # And then re-write
        fp = open(IQCONF, mode='w', encoding='utf-8')
        fp.write(raw)
        fp.close()

# "Fix" any path components that have mismatched case (eg Paf => PAF)
def caseMatchPath(root, path):
    inpath = path.split('/')
    outpath = []
    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(f"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)


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(r'^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 list(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(list(ARFMap.keys()), p))
    for n in install:
        installid |= set(ARFMap[n])

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

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

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

    # Ensure that the files are readable
    runCommand(f"find {DATADIR}/{ARFs[iid]['targetdir']} -type f -print0 | xargs --null chmod 0644")

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

    # Config entries
    names = ARFNames[iid]
    names.sort()
    for n in names:
        addConf(f"ARF/{n}", f"{DATADIR}/{ARFs[iid]['targetdir']}")

commitConf()

