#!/usr/bin/python
##
## license:BSD-3-Clause
## copyright-holders:Vas Crabb

from . import dbaccess

import os
import os.path
import subprocess
import xml.sax
import xml.sax.saxutils


class ElementHandlerBase(object):
    def __init__(self, parent, **kwargs):
        super(ElementHandlerBase, self).__init__(**kwargs)
        self.dbconn = parent.dbconn if parent is not None else None
        self.locator = parent.locator if parent is not None else None
        self.depth = 0
        self.childhandler = None
        self.childdepth = 0

    def startMainElement(self, name, attrs):
        pass

    def endMainElement(self, name):
        pass

    def mainCharacters(self, content):
        pass

    def mainIgnorableWitespace(self, whitespace):
        pass

    def startChildElement(self, name, attrs):
        pass

    def endChildElement(self, name):
        pass

    def childCharacters(self, content):
        pass

    def childIgnorableWitespace(self, whitespace):
        pass

    def endChildHandler(self, name, handler):
        pass

    def setChildHandler(self, name, attrs, handler):
        self.depth -= 1
        self.childhandler = handler
        self.childdepth += 1
        handler.startElement(name, attrs)

    def setDocumentLocator(self, locator):
        self.locator = locator

    def startElement(self, name, attrs):
        if self.childhandler is not None:
            self.childdepth += 1
            self.childhandler.startElement(name, attrs)
        else:
            self.depth += 1
            if 1 == self.depth:
                self.startMainElement(name, attrs)
            else:
                self.startChildElement(name, attrs)

    def endElement(self, name):
        if self.childhandler is not None:
            self.childdepth -= 1
            self.childhandler.endElement(name)
            if 0 == self.childdepth:
                self.endChildHandler(name, self.childhandler)
                self.childhandler = None
        else:
            self.depth -= 1
            if 0 == self.depth:
                self.endMainElement(name)
            else:
                self.endChildElement(name)

    def characters(self, content):
        if self.childhandler is not None:
            self.childhandler.characters(content)
        elif 1 < self.depth:
            self.childCharacters(content)
        else:
            self.mainCharacters(content)

    def ignorableWhitespace(self, content):
        if self.childhandler is not None:
            self.childhandler.ignorableWhitespace(content)
        elif 1 < self.depth:
            self.childIgnorableWitespace(content)
        else:
            self.mainIgnorableWitespace(content)


class ElementHandler(ElementHandlerBase):
    IGNORE = ElementHandlerBase(parent=None)


class TextAccumulator(ElementHandler):
    def __init__(self, parent, **kwargs):
        super(TextAccumulator, self).__init__(parent=parent, **kwargs)
        self.text = ''

    def mainCharacters(self, content):
        self.text += content


class DipSwitchHandler(ElementHandler):
    def __init__(self, parent, **kwargs):
        super(DipSwitchHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.machine = parent.id

    def startMainElement(self, name, attrs):
        self.mask = int(attrs['mask'])
        self.bit = 0
        self.id = self.dbcurs.add_dipswitch(self.machine, name == 'configuration', attrs['name'], attrs['tag'], self.mask)

    def startChildElement(self, name, attrs):
        if (name == 'diplocation') or (name == 'conflocation'):
            while (0 != self.mask) and not (self.mask & 1):
                self.mask >>= 1
                self.bit += 1
            self.dbcurs.add_diplocation(self.id, self.bit, attrs['name'], attrs['number'], attrs.get('inverted', 'no')  == 'yes')
            self.mask >>= 1
            self.bit += 1
        elif (name == 'dipvalue') or (name == 'confsetting'):
            self.dbcurs.add_dipvalue(self.id, attrs['name'], attrs['value'], attrs.get('default', 'no') == 'yes')
        self.setChildHandler(name, attrs, self.IGNORE)


class SlotHandler(ElementHandler):
    def __init__(self, parent, **kwargs):
        super(SlotHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.machine = parent.id

    def startMainElement(self, name, attrs):
        self.id = self.dbcurs.add_slot(self.machine, attrs['name'])

    def startChildElement(self, name, attrs):
        if name == 'slotoption':
            option = self.dbcurs.add_slotoption(self.id, attrs['devname'], attrs['name'])
            if attrs.get('default') == 'yes':
                self.dbcurs.add_slotdefault(self.id, option)
        self.setChildHandler(name, attrs, self.IGNORE)


class RamOptionHandler(TextAccumulator):
    def __init__(self, parent, **kwargs):
        super(RamOptionHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.machine = parent.id

    def startMainElement(self, name, attrs):
        self.name = attrs['name']
        self.default = attrs.get('default', 'no') == 'yes';

    def endMainElement(self, name):
        self.size = int(self.text)
        self.dbcurs.add_ramoption(self.machine, self.name, self.size)
        if self.default:
            self.dbcurs.add_ramdefault(self.machine, self.size)


class MachineHandler(ElementHandler):
    CHILD_HANDLERS = {
            'description':      TextAccumulator,
            'year':             TextAccumulator,
            'manufacturer':     TextAccumulator,
            'dipswitch':        DipSwitchHandler,
            'configuration':    DipSwitchHandler,
            'slot':             SlotHandler,
            'ramoption':        RamOptionHandler }

    def __init__(self, parent, **kwargs):
        super(MachineHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = self.dbconn.cursor()

    def startMainElement(self, name, attrs):
        self.shortname = attrs['name']
        self.sourcefile = attrs['sourcefile']
        self.isdevice = attrs.get('isdevice', 'no') == 'yes'
        self.runnable = attrs.get('runnable', 'yes') == 'yes'
        self.cloneof = attrs.get('cloneof')
        self.romof = attrs.get('romof')
        self.dbcurs.add_sourcefile(self.sourcefile)

    def endMainElement(self, name):
        self.dbcurs.close()

    def startChildElement(self, name, attrs):
        if name in self.CHILD_HANDLERS:
            self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
        else:
            if name == 'biosset':
                bios = self.dbcurs.add_biosset(self.id, attrs['name'], attrs['description'])
                if attrs.get('default', 'no') == 'yes':
                    self.dbcurs.add_biossetdefault(bios)
            elif name == 'device_ref':
                self.dbcurs.add_devicereference(self.id, attrs['name'])
            elif name == 'feature':
                self.dbcurs.add_featuretype(attrs['type'])
                status = 0 if 'status' not in attrs else 2 if attrs['status'] == 'unemulated' else 1
                overall = status if 'overall' not in attrs else 2 if attrs['overall'] == 'unemulated' else 1
                self.dbcurs.add_feature(self.id, attrs['type'], status, overall)
            elif name == 'softwarelist':
                self.dbcurs.add_machinesoftwarelist(self.id, attrs['name'], attrs['tag'], attrs['status'])
            elif name == 'rom':
                crc = attrs.get('crc')
                sha1 = attrs.get('sha1')
                if (crc is not None) and (sha1 is not None):
                    crc = int(crc, 16)
                    sha1 = sha1.lower()
                    self.dbcurs.add_rom(crc, sha1)
                    status = attrs.get('status', 'good')
                    self.dbcurs.add_romdump(self.id, attrs['name'], crc, sha1, status != 'good')
            elif name == 'disk':
                sha1 = attrs.get('sha1')
                if sha1 is not None:
                    sha1 = sha1.lower()
                    self.dbcurs.add_disk(sha1)
                    status = attrs.get('status', 'good')
                    self.dbcurs.add_diskdump(self.id, attrs['name'], sha1, status != 'good')
            self.setChildHandler(name, attrs, self.IGNORE)

    def endChildHandler(self, name, handler):
        if name == 'description':
            self.description = handler.text
            self.id = self.dbcurs.add_machine(self.shortname, self.description, self.sourcefile, self.isdevice, self.runnable)
            if self.cloneof is not None:
                self.dbcurs.add_cloneof(self.id, self.cloneof)
            if self.romof is not None:
                self.dbcurs.add_romof(self.id, self.romof)
        elif name == 'year':
            self.year = handler.text
        elif name == 'manufacturer':
            self.manufacturer = handler.text
            self.dbcurs.add_system(self.id, self.year, self.manufacturer)


class ListXmlHandler(ElementHandler):
    def __init__(self, dbconn, **kwargs):
        super(ListXmlHandler, self).__init__(parent=None, **kwargs)
        self.dbconn = dbconn

    def startDocument(self):
        pass

    def endDocument(self):
        pass

    def startMainElement(self, name, attrs):
        if name != 'mame':
            raise xml.sax.SAXParseException(
                    msg=('Expected "mame" element but found "%s"' % (name, )),
                    exception=None,
                    locator=self.locator)
        self.machines = 0

    def endMainElement(self, name):
        self.dbconn.commit()

    def startChildElement(self, name, attrs):
        if name != 'machine':
            raise xml.sax.SAXParseException(
                    msg=('Expected "machine" element but found "%s"' % (name, )),
                    exception=None,
                    locator=self.locator)
        self.setChildHandler(name, attrs, MachineHandler(self))

    def endChildHandler(self, name, handler):
        if name == 'machine':
            if self.machines >= 1023:
                self.dbconn.commit()
                self.machines = 0
            else:
                self.machines += 1

    def processingInstruction(self, target, data):
        pass


class DataAreaHandler(ElementHandler):
    def __init__(self, parent, **kwargs):
        super(DataAreaHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.part = parent.id

    def startChildElement(self, name, attrs):
        if name == 'rom':
            crc = attrs.get('crc')
            sha1 = attrs.get('sha1')
            if ('name' in attrs) and (crc is not None) and (sha1 is not None):
                crc = int(crc, 16)
                sha1 = sha1.lower()
                self.dbcurs.add_rom(crc, sha1)
                status = attrs.get('status', 'good')
                self.dbcurs.add_softwareromdump(self.part, attrs['name'], crc, sha1, status != 'good')
        self.setChildHandler(name, attrs, self.IGNORE)


class DiskAreaHandler(ElementHandler):
    def __init__(self, parent, **kwargs):
        super(DiskAreaHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.part = parent.id

    def startChildElement(self, name, attrs):
        if name == 'disk':
            sha1 = attrs.get('sha1')
            if sha1 is not None:
                sha1 = sha1.lower()
                self.dbcurs.add_disk(sha1)
                status = attrs.get('status', 'good')
                self.dbcurs.add_softwarediskdump(self.part, attrs['name'], sha1, status != 'good')
        self.setChildHandler(name, attrs, self.IGNORE)


class SoftwarePartHandler(ElementHandler):
    CHILD_HANDLERS = {
            'dataarea':         DataAreaHandler,
            'diskarea':         DiskAreaHandler }

    def __init__(self, parent, **kwargs):
        super(SoftwarePartHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = parent.dbcurs
        self.software = parent.id

    def startMainElement(self, name, attrs):
        self.id = self.dbcurs.add_softwarepart(self.software, attrs['name'], attrs['interface'])

    def startChildElement(self, name, attrs):
        if name in self.CHILD_HANDLERS:
            self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
        else:
            if name == 'feature':
                self.dbcurs.add_softwarepartfeaturetype(attrs['name'])
                self.dbcurs.add_softwarepartfeature(self.id, attrs['name'], attrs['value'])
            self.setChildHandler(name, attrs, self.IGNORE)


class SoftwareHandler(ElementHandler):
    CHILD_HANDLERS = {
            'description':      TextAccumulator,
            'year':             TextAccumulator,
            'publisher':        TextAccumulator,
            'notes':            TextAccumulator,
            'part':             SoftwarePartHandler }

    def __init__(self, parent, **kwargs):
        super(SoftwareHandler, self).__init__(parent=parent, **kwargs)
        self.dbcurs = self.dbconn.cursor()
        self.softwarelist = parent.id

    def startMainElement(self, name, attrs):
        self.shortname = attrs['name']
        self.cloneof = attrs.get('cloneof')
        self.supported = 0 if (attrs.get('supported', 'yes') == 'yes') else 1 if (attrs.get('supported', 'yes') == 'partial') else 2

    def endMainElement(self, name):
        self.dbcurs.close()

    def startChildElement(self, name, attrs):
        if name in self.CHILD_HANDLERS:
            self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
        else:
            if name == 'info':
                self.dbcurs.add_softwareinfotype(attrs['name'])
                self.dbcurs.add_softwareinfo(self.id, attrs['name'], attrs['value'])
            elif name == 'sharedfeat':
                self.dbcurs.add_softwaresharedfeattype(attrs['name'])
                self.dbcurs.add_softwaresharedfeat(self.id, attrs['name'], attrs['value'])
            self.setChildHandler(name, attrs, self.IGNORE)

    def endChildHandler(self, name, handler):
        if name == 'description':
            self.description = handler.text
        elif name == 'year':
            self.year = handler.text
        elif name == 'publisher':
            self.publisher = handler.text
            self.id = self.dbcurs.add_software(self.softwarelist, self.shortname, self.supported, self.description, self.year, self.publisher)
            if self.cloneof is not None:
                self.dbcurs.add_softwarecloneof(self.id, self.cloneof)
        elif name == 'notes':
            self.dbcurs.add_softwarenotes(self.id, handler.text)


class SoftwareListHandler(ElementHandler):
    CHILD_HANDLERS = {
            'notes':            TextAccumulator,
            'software':         SoftwareHandler }

    def __init__(self, dbconn, **kwargs):
        super(SoftwareListHandler, self).__init__(parent=None, **kwargs)
        self.dbconn = dbconn

    def startDocument(self):
        pass

    def endDocument(self):
        pass

    def startMainElement(self, name, attrs):
        if name != 'softwarelist':
            raise xml.sax.SAXParseException(
                    msg=('Expected "softwarelist" element but found "%s"' % (name, )),
                    exception=None,
                    locator=self.locator)
        self.shortname = attrs['name']
        self.description = attrs['description']
        self.entries = 0
        dbcurs = self.dbconn.cursor()
        self.id = dbcurs.add_softwarelist(self.shortname, self.description)
        dbcurs.close()

    def endMainElement(self, name):
        self.dbconn.commit()

    def startChildElement(self, name, attrs):
        if name in self.CHILD_HANDLERS:
            self.setChildHandler(name, attrs, self.CHILD_HANDLERS[name](self))
        else:
            raise xml.sax.SAXParseException(
                    msg=('Found unexpected element "%s"' % (name, )),
                    exception=None,
                    locator=self.locator)

    def endChildHandler(self, name, handler):
        if name == 'notes':
            dbcurs = self.dbconn.cursor()
            dbcurs.add_softwarelistnotes(self.id, handler.text)
            dbcurs.close()
        elif name == 'software':
            if self.entries >= 1023:
                self.dbconn.commit()
                self.entries = 0
            else:
                self.entries += 1

    def processingInstruction(self, target, data):
        pass


def load_info(options):
    dbconn = dbaccess.UpdateConnection(options.database)
    dbconn.prepare_for_load()
    parser = xml.sax.make_parser()

    parser.setContentHandler(SoftwareListHandler(dbconn))
    for path in options.softwarepath:
        files = [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.xml')]
        for filename in files:
            parser.parse(filename)

    parser.setContentHandler(ListXmlHandler(dbconn))
    if options.executable is not None:
        task = subprocess.Popen([options.executable, '-listxml'], stdout=subprocess.PIPE)
        parser.parse(task.stdout)
    else:
        parser.parse(options.file)

    dbconn.finalise_load()
