#!/usr/bin/env python3
__doc__ = '''Deletes glyphs from a UFO based on list. Can instead delete glyphs not in list.'''
__url__ = 'https://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2018 SIL International (https://www.sil.org)'
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
__author__ = 'Victor Gaultney'

from silfont.core import execute
from xml.etree import ElementTree as ET

argspec = [
    ('ifont', {'help': 'Input font file'}, {'type': 'infont'}),
    ('ofont', {'help': 'Output font file', 'nargs': '?'}, {'type': 'outfont'}),
    ('-i', '--input', {'help': 'Input text file, one glyphname per line'}, {'type': 'infile', 'def': 'glyphlist.txt'}),
    ('--reverse',{'help': 'Remove glyphs not in list instead', 'action': 'store_true', 'default': False},{}),
    ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'deletedglyphs.log'})]

def doit(args) :
    font = args.ifont
    listinput = args.input
    logger = args.logger

    glyphlist = []
    for line in listinput.readlines():
        glyphlist.append(line.strip())

    deletelist = []

    if args.reverse:
        for glyphname in font.deflayer:
            if glyphname not in glyphlist:
                deletelist.append(glyphname)
    else:
        for glyphname in font.deflayer:
            if glyphname in glyphlist:
                deletelist.append(glyphname)

    secondarylayers = [x for x in font.layers if x.layername != "public.default"]

    liststocheck = ('public.glyphOrder', 'public.postscriptNames', 'com.schriftgestaltung.glyphOrder')
    liblists = [[],[],[]]; inliblists = [[],[],[]]
    if hasattr(font, 'lib'):
        for (i,listn) in enumerate(liststocheck):
            if listn in font.lib:
                liblists[i] = font.lib.getval(listn)
    else:
        logger.log("No lib.plist found in font", "W")

    # Now loop round deleting the glyphs etc
    logger.log("Deleted glyphs:", "I")

    # With groups and kerning, create dicts representing then plists (to make deletion of members easier) and indexes by glyph/member name
    kgroupprefixes = {"public.kern1.": 1, "public.kern2.": 2}
    gdict = {}
    kdict = {}
    groupsbyglyph = {}
    ksetsbymember = {}

    groups = font.groups if hasattr(font, "groups") else []
    kerning = font.kerning if hasattr(font, "kerning") else []
    if groups:
        for gname in groups:
            group = groups.getval(gname)
            gdict[gname] = group
            for glyph in group:
                if glyph in groupsbyglyph:
                    groupsbyglyph[glyph].append(gname)
                else:
                    groupsbyglyph[glyph] = [gname]
    if kerning:
        for setname in kerning:
            kset = kerning.getval(setname)
            kdict[setname] = kset
            for member in kset:
                if member in ksetsbymember:
                    ksetsbymember[member].append(setname)
                else:
                    ksetsbymember[member] = [setname]

    # Loop round doing the deleting
    for glyphn in sorted(deletelist):
        # Delete from all layers
        font.deflayer.delGlyph(glyphn)
        deletedfrom = "Default layer"
        for layer in secondarylayers:
            if glyphn in layer:
                deletedfrom += ", " + layer.layername
                layer.delGlyph(glyphn)
        # Check to see if the deleted glyph is in any of liststocheck
        stillin = None
        for (i, liblist) in enumerate(liblists):
            if glyphn in liblist:
                inliblists[i].append(glyphn)
                stillin = stillin + ", " + liststocheck[i] if stillin else liststocheck[i]

        logger.log("  " + glyphn + " deleted from: " + deletedfrom, "I")
        if stillin: logger.log("  " + glyphn + " is still in " + stillin, "I")

        # Process groups.plist and kerning.plist

        tocheck = (glyphn, "public.kern1." + glyphn, "public.kern2." + glyphn)
        # First delete whole groups and kern pair sets
        for kerngroup in tocheck[1:]: # Don't check glyphn when deleting groups:
            if kerngroup in gdict: gdict.pop(kerngroup)
        for setn in tocheck:
            if setn in kdict: kdict.pop(setn)
        # Now delete members within groups and kern pair sets
        if glyphn in groupsbyglyph:
            for groupn in groupsbyglyph[glyphn]:
                if groupn in gdict: # Need to check still there, since whole group may have been deleted above
                    group = gdict[groupn]
                    del group[group.index(glyphn)]
        for member in tocheck:
            if member in ksetsbymember:
                for setn in ksetsbymember[member]:
                    if setn in kdict: del kdict[setn][member]
        # Now need to recreate groups.plist and kerning.plist
        if groups:
            for group in list(groups): groups.remove(group)  # Empty existing contents
            for gname in gdict:
                elem = ET.Element("array")
                if gdict[gname]: # Only create if group is not empty
                    for glyph in gdict[gname]:
                        ET.SubElement(elem, "string").text = glyph
                    groups.setelem(gname, elem)
        if kerning:
            for kset in list(kerning): kerning.remove(kset)  # Empty existing contents
            for kset in kdict:
                elem = ET.Element("dict")
                if kdict[kset]:
                    for member in kdict[kset]:
                        ET.SubElement(elem, "key").text = member
                        ET.SubElement(elem, "integer").text = str(kdict[kset][member])
                    kerning.setelem(kset, elem)

    logger.log(str(len(deletelist)) + " glyphs deleted. Set logging to I to see details", "P")
    inalist = set(inliblists[0] + inliblists[1] + inliblists[2])
    if inalist: logger.log(str(len(inalist)) + " of the deleted glyphs are still in some lib.plist entries.", "W")

    return font

def cmd() : execute("UFO",doit,argspec)
if __name__ == "__main__": cmd()

