import os
import urllib.request, urllib.error, urllib.parse
import ufoLib

# Extract documentation strings from the UFO specification site

import urllib.request, urllib.error, urllib.parse

url = "http://unifiedfontobject.org/versions/ufo3/fontinfo.html"
docsite = urllib.request.urlopen(url)
text = docsite.read()

usableLines = None
for line in text.splitlines():
    line = line.strip()
    if line == "<h2>Converting Version 1 to Version 2</h2>":
        break
    if line == "<h2>Specification</h2>":
        usableLines = []
    if usableLines is None:
        continue
    usableLines.append(line)

attributeDocumentation = dict.fromkeys(ufoLib.fontInfoAttributesVersion3)

unencodes = {
    "&#8217;" : "'",
    "&#8220;" : '"',
    "&#8221;" : '"',
    "&#8211;" : "-",
    "<em>" : "",
    "</em>" : "",
    "<span class=\"caps\">" : "",
    "</span>" : "",
    "<strong>" : "",
    "</strong>" : "",
}

listening = False
currentAttr = []
for line in usableLines:
    if line == "<tr>":
        listening = True
        continue
    if line == "</tr>":
        listening = False
        if len(currentAttr) == 3:
            attr, typ, doc = currentAttr
            if attr in attributeDocumentation:
                assert attributeDocumentation[attr] is None
                attributeDocumentation[attr] = "%s This should be a %s. Setting this will post an *Info.Changed* notification." % (doc, typ)
        currentAttr = []
    if listening:
        if line.startswith("<td>"):
            line = line.replace("<td>", "").replace("</td>", "")
            for before, after in list(unencodes.items()):
                line = line.replace(before, after)
            line = line.replace('"', '\\"')
            currentAttr.append(line)

assert set(attributeDocumentation.keys()) == ufoLib.fontInfoAttributesVersion3
assert None not in list(attributeDocumentation.values())

# print the code

print("# this file was generated by %s." % os.path.basename(__file__))
print("# this file should not be edited by hand.")
print()

print("import weakref")
print("from warnings import warn")
print("import ufoLib")
print("from defcon.objects.base import BaseObject")
print("from defcon.objects.guideline import Guideline")
print()
print()

print("class Info(BaseObject):")

doc = """
    \"\"\"
    This object represents info values.

    **This object posts the following notifications:**

    ===========================
    Name
    ===========================
    Info.Changed
    Info.ValueChanged
    Info.GuidelinesChanged
    Info.GuidelineWillBeDeleted
    ===========================

    **Note:** The documentation strings here were automatically generated
    from the `UFO specification <http://unifiedfontobject.org/filestructure/fontinfo.html>`_.
    \"\"\"
"""
print(doc)

print("    changeNotificationName = \"Info.Changed\"")
print("    representationFactories = {}")
print()

print("    def __init__(self, font=None, guidelineClass=None):")
print("        if font is not None:")
print("            font = weakref.ref(font)")
print("        self._font = font")
print("        super(Info, self).__init__()")
print("        self.beginSelfNotificationObservation()")
print("        self._identifiers = set()")
print("        if guidelineClass is None:")
print("            guidelineClass = Guideline")
print("        self._guidelineClass = guidelineClass")

defaults = dict(
    guidelines=[],
#    openTypeGaspRangeRecords=[],
#    openTypeNameRecords=[],
    postscriptBlueValues=[],
    postscriptOtherBlues=[],
    postscriptFamilyBlues=[],
    postscriptFamilyOtherBlues=[],
    postscriptStemSnapH=[],
    postscriptStemSnapV=[],
#    woffMetadataExtensions=[]
)

for attr in sorted(ufoLib.fontInfoAttributesVersion3):
    print("        self._%s = %s" % (attr, str(defaults.get(attr))))
print()

print("    def __del__(self):")
print("        super(Info, self).__del__()")
print("        self._guidelines = None")
print()

print("    def getParent(self):")
print("        return self.font")
print()

print()
print("    def _get_font(self):")
print("        if self._font is not None:")
print("            return self._font()")
print("        return None")
print()
print("    font = property(_get_font, doc=\"The :class:`Font` that this object belongs to.\")")
print()

print("    # ----------")
print("    # Properties")
print("    # ----------")
print()

for attr in sorted(ufoLib.fontInfoAttributesVersion3):
    if attr == "guidelines":
        continue
    print("    def _get_%s(self):" % attr)
    if attr in defaults:
        print("        return self._%s" % attr)
    else:
        print("        return self._%s" % attr)
    print()
    print("    def _set_%s(self, value):" % attr)
    print("        oldValue = self._%s" % attr)
    print("        if oldValue == value:")
    print("            return")
    print("        if value is None:")
    print("            self._%s = None" % attr)
    print("        else:")
    print("            valid = ufoLib.validateFontInfoVersion3ValueForAttribute(\"%s\", value)" % attr)
    print("            if not valid:")
    print(("                raise ValueError(\"Invalid value (___) for attribute %s.\" %% repr(value))" % (attr)).replace("___", "%s"))
    print("            else:")
    print("                self._%s = value" % attr)
    print("        self.postNotification(\"Info.ValueChanged\", data=dict(attribute=\"%s\", oldValue=oldValue, newValue=value))" % attr)
    print("        self.dirty = True")
    print()
    print("    %s = property(_get_%s, _set_%s, doc=\"%s\")" % (attr, attr, attr, attributeDocumentation[attr]))
    print()

handBuilt = """
    # -----------
    # Identifiers
    # -----------

    def _get_identifiers(self):
        return self._identifiers

    identifiers = property(_get_identifiers, doc="Set of identifiers for the info. This is primarily for internal use.")

    # ----------
    # Guidelines
    # ----------

    def _get_guidelines(self):
        return list(self._guidelines)

    def _set_guidelines(self, value):
        self.clearGuidelines()
        self.holdNotifications()
        for guideline in value:
            self.appendGuideline(guideline)
        self.releaseHeldNotifications()

    guidelines = property(_get_guidelines, _set_guidelines, doc="An ordered list of :class:`Guideline` objects stored in the info. Setting this will post a *Info.Changed* notification along with any notifications posted by the :py:meth:`Info.appendGuideline` and :py:meth:`Info.clearGuidelines` methods.")

    def instantiateGuideline(self, guidelineDict=None):
        guideline = self._guidelineClass(
            fontInfo=self,
            guidelineDict=guidelineDict
        )
        return guideline

    def beginSelfGuidelineNotificationObservation(self, guideline):
        if guideline.dispatcher is None:
            return
        guideline.addObserver(observer=self, methodName="_guidelineChanged", notification="Guideline.Changed")

    def endSelfGuidelineNotificationObservation(self, guideline):
        if guideline.dispatcher is None:
            return
        guideline.endSelfNotificationObservation()
        guideline.removeObserver(observer=self, notification="Guideline.Changed")

    def appendGuideline(self, guideline):
        \"\"\"
        Append **guideline** to the info. The guideline must be a defcon
        :class:`Guideline` object or a subclass of that object. An error
        will be raised if the guideline's identifier conflicts with any of
        the identifiers within the info.

        This will post *Info.GuidelinesChanged* and *Info.Changed* notifications.
        \"\"\"
        self.insertGuideline(len(self._guidelines), guideline)

    def insertGuideline(self, index, guideline):
        \"\"\"
        Insert **guideline** into the info at index. The guideline
        must be a defcon :class:`Guideline` object or a subclass
        of that object. An error will be raised if the guideline's
        identifier conflicts with any of the identifiers within
        the info.

        This will post *Info.GuidelinesChanged* and *Info.Changed* notifications.
        \"\"\"
        try:
            assert guideline.fontInfo != self
        except AttributeError:
            pass
        if not isinstance(guideline, self._guidelineClass):
            guideline = self.instantiateGuideline(guidelineDict=guideline)
        assert guideline.fontInfo in (self, None), "This guideline belongs to another font."
        if guideline.fontInfo is None:
            assert guideline.glyph is None, "This guideline belongs to a glyph."
        if guideline.fontInfo is None:
            if guideline.identifier is not None:
                identifiers = self._identifiers
                assert guideline.identifier not in identifiers
                if guideline.identifier is not None:
                    identifiers.add(guideline.identifier)
            guideline.fontInfo = self
            guideline.beginSelfNotificationObservation()
        self.beginSelfGuidelineNotificationObservation(guideline)
        self._guidelines.insert(index, guideline)
        self.postNotification("Info.GuidelinesChanged")
        self.dirty = True

    def removeGuideline(self, guideline):
        \"\"\"
        Remove **guideline** from the info.

        This will post a *Glyph.Changed* notification.
        \"\"\"
        self.postNotification(notification="Info.GuidelineWillBeDeleted", data=dict(object=guideline))
        if guideline.identifier is not None:
            self._identifiers.remove(guideline.identifier)
        self._guidelines.remove(guideline)
        self.endSelfGuidelineNotificationObservation(guideline)
        self.postNotification("Info.GuidelinesChanged")
        self.dirty = True

    def guidelineIndex(self, guideline):
        \"\"\"
        Get the index for **guideline**.
        \"\"\"
        return self._guidelines.index(guideline)

    def clearGuidelines(self):
        \"\"\"
        Clear all guidelines from the info.

        This posts a *Glyph.Changed* notification.
        \"\"\"
        self.holdNotifications()
        for guideline in reversed(self._guidelines):
            self.removeGuideline(guideline)
        self.releaseHeldNotifications()

    # ------------------------
    # Notification Observation
    # ------------------------

    def endSelfNotificationObservation(self):
        if self.dispatcher is None:
            return
        for guideline in self.guidelines:
            self.endSelfGuidelineNotificationObservation(guideline)
        super(Info, self).endSelfNotificationObservation()
        self._font = None

    def _guidelineChanged(self, notification):
        self.postNotification("Info.GuidelinesChanged")
        self.dirty = True
""".strip()

print("    " + handBuilt)